请考虑以下情形。我有三个分支-Master
,Develop
和Test
。这三个分支都有一个配置文件(例如JenkinsFile
),该文件包含分支特定的配置。请注意,此文件中的配置对于所有三个分支都是不同的。现在,我在Master
上创建了一个功能分支,进行了一些更改,然后将此功能分支与Develop
和Test
合并。
问题是-如何防止JenkinsFile
被任何合并覆盖?我希望JenkinsFile
保持不变,并且不受任何合并的影响。有没有一种方法可以“锁定”这些文件?在这种情况下gitignore
是否有效?
干杯!
答案 0 :(得分:1)
在分支之间保持不同的文件非常困难,尤其是时不时更改的文件。
一个更好的解决方案是让settings.environment.json文件包含针对不同环境的设置,并使您的软件根据运行位置使用不同的设置文件。
话虽如此,最好不要将生产设置保留在git中。部署管道应包含密码等,而不是您的版本控制系统。在这种情况下,所有分支中的设置文件都包含DEV设置(可以公开),并且当管道准备将程序包部署到目标环境时,管道会使用TEST和PROD值覆盖设置。
答案 1 :(得分:0)
问题是-如何防止JenkinsFile被任何合并覆盖?我希望JenkinsFile保持不变,并且不受任何合并的影响。有没有办法“锁定”这些文件?
否。
但是,有一个完全不同的方法可以解决整个问题。实际上,有多种方法,但我只展示其中一种。在使进入一切都按需运行的状态方面存在一个不幸的问题,但是一旦到达目标,就可以了。此处的最终目标是不拥有一个名为Jenkinsfile
(或JenkinsFile
的提交文件,但我使用了下面的小写F拼写),其内容取决于分支。取而代之的是一个 uncommitted 仅限工作树的文件,其名称为Jenkins[Ff]ile
,其内容取决于分支。使 committed 文件具有其他名称。
从根本上说,git merge
通过完成的工作进行工作,即,从某个共同的起点开始,将更改合并到某些文件中。但是Git不会存储更改; Git存储快照。这为git merge
带来了一个问题,该解决方案要求您了解Git的提交图的工作方式。
Git存储库中的几乎每个提交都至少具有一个 parent 提交,这是该提交的直接前身。大多数人的父母完全是 个;类型为“ merge”的提交至少有两个,通常恰好两个。实际上,将一个提交定义为合并提交的原因是不止一个父级。另一个常见的特殊情况是,存储库中的第一次提交没有父对象,因为它没有父对象,因为它是第一次提交。 (有三个或更多父母的学校被称为<章鱼章鱼合并,但是它们不能像常规合并那样做,所以它们主要是为了炫耀。:-))
这些链接中,一个提交存储了其父级的哈希ID(请记住,每个提交都是通过其唯一的哈希ID找到的,即在您提交该提交时Git为其分配的)形成了向后链。这些向后链 是存储库中的历史记录。历史就是承诺;提交是历史。分支名称只是标识我们希望声明属于该分支的(单个) last 提交:
... <-F <-G <-H <--master
在这里,我使用了代表每次提交的单个大写字母,而不是实际的哈希ID。名称master
保存着提交H
的实际哈希ID。我们说master
指向 H
。 H
保留其父项G
的哈希ID,因此H
指向G
,后者指向F
,依此类推,向后移动。
任何提交内的任何内容都无法更改,因此我们不需要内部箭头,我们只需要记住它们会向后移动。实际上,在Git中前进非常困难:几乎所有操作都从末尾开始并向后进行。一旦我们有多个分支,将得到如下图所示:
G--H <-- master
/
...--E--F
\
I--J <-- develop
\
K <-- test
到git checkout
分支意味着*从该分支的尖端提交中提取快照. So
git checkout master extracts the snapshot from commit
H , while
git checkout development or
git checkout测试extracts those snapshots in turn. Also, doing a
git checkout of some branch name attaches the special name
HEAD`到该分支。这就是Git如何知道哪个分支并提交的-当前分支。
运行git merge
时,将其他提交的名称赋予Git。不必一定是分支名称-可以使用任何提交名称-但给它一个分支名称可以很好地工作,因为它可以命名该分支的最尖端提交。因此,如果您先git checkout master
然后运行git merge develop
,那么您将从:
G--H <-- master (HEAD)
/
...--E--F
\
I--J <-- develop
\
K <-- test
,Git找到提交J
。然后,Git从当前的提交H
和命名的提交J
进行反向工作,以找到这两个提交的合并基础。
松散地,合并基础是我们从这两个技巧中获得的第一个提交。这是在两个分支上的提交,在这种情况下,显然是F
提交。 合并基础的概念对于理解合并的工作方式至关重要。由于合并的目的是合并工作,可以通过比较快照中的快照来找到工作。一次向两个提示F
和H
提交J
,一次比较:
git diff --find-renames <hash-of-F> <hash-of-H> # what we changed
git diff --find-renames <hash-of-F> <hash-of-J> # what they changed
要合并更改,Git从F
中的所有文件开始,然后查看我们更改了哪些文件以及更改了哪些文件。如果我们都更改了不同文件,则Git会适当考虑我们的文件。如果我们都更改了相同的 文件(this eventually brings up a philosophical problem),稍后我们将返回到该文件,则Git会尝试将我们的更改及其更改一起粉碎,方法是假设我们接触了某些源行,而他们没有,则应该采用我们的,如果他们碰到某些源代码行而我们没有,则也应采用我们的。如果我们都碰到了 same 文件的 same 行,那么我们要么对这些行执行完全相同的操作-在这种情况下,Git只需一份更改,否则就会发生冲突。
如果没有冲突,Git会将这些合并的更改应用于合并基础中的快照(此处为F
),并使用生成的文件写出新快照。该新快照是具有两个父级的 merge commit 类型的提交。第一个父级是我们之前所做的提交H
,第二个父级是我们使用参数J
命名的提交,因此合并看起来像这样:
G--H
/ \
...--E--F L <-- master (HEAD)
\ /
I--J <-- develop
\
K <-- test
请注意,任何现有提交或任何其他分支名称都不会发生任何变化。仅移动我们自己的分支名称master
(附加了HEAD
); master
现在指向Git刚刚进行的新合并提交。
如果由于合并冲突而导致合并失败,Git将留下一团糟。我不会在这里讨论的 index 将包含所有冲突的输入文件,工作树将包含Git的合并尝试以及冲突标记。您的工作是清理混乱,修复索引并完成合并(使用git merge --continue
或git commit
---continue
仅运行commit
)。< / p>
Jenkinsfile
假设在合并基础的提交F
中,有一个名为Jenkinsfile
的文件。具有相同名称的同一文件出现在提交H
和J
中。 H
和J
中的副本有所不同-您说过确实如此,因此我们假定它们确实如此。因此,至少一个与F
不同,也许两者都与F
不同。
Git将假定两个分支提示中名为Jenkinsfile
的文件是Jenkinsfile
中名为F
的 same 文件。显然,它不是完全相同的文件-内容有所不同-但Git会假设确实如此,并且您正在尝试将在此文件上完成的工作组合在一起。
因此,Git会将Jenkinsfile
中F
的版本与H
中的版本进行比较,然后再次将其与J
中的版本进行比较。会有一些更改。如果两个分支提示均发生更改,则Git会将它们合并(或声明冲突)。结果:坏。否则,Git将从“边”更改文件的那个版本获取文件的版本。那是你想要的一面吗?如果是这样,结果:好。如果不是,则结果:坏。
总结,对于这种情况,有三种可能的结果:
当然,合并基础提交F
可能有名为Jenkinsfile
的 no 文件。并且, commit 之一或两者都没有这样的文件。在这种情况下,它会变得有些棘手。我们待会儿讨论。
此处的解决方案是,在该文件旨在成为与分支相关的情况下,避免在所有提交中使用单个固定名称文件,例如Jenkinsfile
。相反,假定提交F
包含Jenkinsfile.master
和Jenkinsfile.develop
和Jenkinsfile.test
。然后提交H
也会有Jenkinsfile.master
和Jenkinsfile.develop
和Jenkinsfile.test
,并且将从F
更改为{{1} H
中的}是您要保留的。由于提交Jenkinsfile.master
在分支J
中,因此它应该始终具有相同更改(有时从develop
导入),或者在全部。因此,在两种情况下,Git的合并将做正确的事情。
相同的逻辑适用于其他每个此类文件。请注意,此时,所有分支提示标识的提交都应该完全没有名为master
的 no 文件(不带后缀)。当然,这是一个理想的目标状态:要达到目标状态,您实际上必须在每个分支中进行新的提交,重命名现有的Jenkinsfile
。但这对所有现有提交都完全没有影响。您存储库中的所有历史记录将一直冻结。这意味着在某个时候,您将运行Jenkinsfile
,而git merge
将找到仅包含git merge
,没有Jenkinsfile
且没有{{1} }或任何其他后缀。
现在让我们假设您已经在Jenkinsfile.master
和Jenkinsfile.develop
中进行了重命名,但是在合并基础H
中,您没有-显然,因为这是历史性的提交。因此J
有一个F
并且没有重命名的文件,而F
和Jenkinsfile
有 no H
但确实有重命名的文件
现在,请记住上面我们展示了J
运行的Jenkinsfile
的位置,以找出自合并基础以来发生了什么变化。参数之一是git diff
。比较{{1}时,这会将Git指示为猜测 git merge
中的文件--find-renames
与Jenkinsfile
中的F
是“相同”文件}}和Jenkinsfile.master
。 H
与F
的比较也是如此:旧的H
与新的F
是同一个文件吗?
如果您点击了指向https://en.wikipedia.org/wiki/Ship_of_Theseus的链接,您将发现对身份随时间变化的问题没有哲学上正确的答案。但是Git的答案是 ,即:如果文件的相似性索引为50%或更高,则它是同一文件。我们无需在这里担心关于Git如何计算相似度指数(有点复杂);在这两种情况下,Git都很有可能检测到重命名。
这在实践中意味着什么是您第一次运行J
时第一次,Git会立即声明合并冲突,这是我喜欢的类型称为高级冲突。也就是说,Git会说Jenkinsfile
在两个分支中都被重命名,但是改名为两个不同的名称。 Git不知道是要使用主版本,还是要使用开发版本,或者两者都使用,或者都不使用,或者什么。它只会因合并冲突而停止。 可以,因为它使您有机会解决冲突,可以通过选择Jenkinsfile.develop
或git merge
中出现的Jenkinsfile
文件来解决此冲突分支,和选择Jenkinsfile.master
或master
分支中出现的--ours
文件作为合并结果。将这两个文件放入索引中,同时删除原始名称:
Jenkinsfile.develop
现在,您通过选择将两个文件都保留在两个分支提示中而解决了冲突。您现在可以提交结果。
每次进行合并时,都会使用历史记录中的单个develop
提交之一,则需要检查合并结果是否正确,或解决任何冲突。 (如果不正确,则在合并之后,您可以立即将其修复到位,并使用--theirs
将原始合并搁置一旁,然后选择新结果作为合并提交。如果您没有发现不良合并,则为有点痛苦,但最终的恢复还是相似的。记住Git是如何合并的,并完成两个git rm --cached Jenkinsfile
git checkout --ours Jenkinsfile.master
git checkout --theirs Jenkinsfile.develop
git add Jenkinsfile.master Jenkinsfile.develop
的工作,以了解如何将正确的结果放入 any 技巧中提交会将您带到您需要去的地方。)
Jenkinsfile
现在没有名为git commit --amend
的文件,您将必须重定向任何要使用该文件的软件。有多种解决方案(取决于软件和操作系统),包括建立从git diff
到正确的每个分支结帐的符号链接。 (确保符号链接不会被提交,否则当Git尝试合并两个潜在的符号链接目标更改时,您将回到同一个合并问题。)