避免在git merge期间文件被覆盖

时间:2019-01-07 03:06:07

标签: git

请考虑以下情形。我有三个分支-MasterDevelopTest。这三个分支都有一个配置文件(例如JenkinsFile),该文件包含分支特定的配置。请注意,此文件中的配置对于所有三个分支都是不同的。现在,我在Master上创建了一个功能分支,进行了一些更改,然后将此功能分支与DevelopTest合并。

问题是-如何防止JenkinsFile被任何合并覆盖?我希望JenkinsFile保持不变,并且不受任何合并的影响。有没有一种方法可以“锁定”这些文件?在这种情况下gitignore是否有效?

干杯!

2 个答案:

答案 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 指向 HH保留其父项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提交。 合并基础的概念对于理解合并的工作方式至关重要。由于合并的目的是合并工作,可以通过比较快照中的快照来找到工作。一次向两个提示FH提交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 --continuegit commit---continue仅运行commit)。< / p>

您的问题:Jenkinsfile

假设在合并基础的提交F中,有一个名为Jenkinsfile的文件。具有相同名称的同一文件出现在提交HJ中。 HJ中的副本有所不同-您说过确实如此,因此我们假定它们确实如此。因此,至少一个F不同,也许两者都与F不同。

Git将假定两个分支提示中名为Jenkinsfile的文件是Jenkinsfile中名为F same 文件。显然,它不是完全相同的文件-内容有所不同-但Git会假设确实如此,并且您正在尝试将在此文件上完成的工作组合在一起。

因此,Git会将JenkinsfileF的版本与H中的版本进行比较,然后再次将其与J中的版本进行比较。会有一些更改。如果两个分支提示均发生更改,则Git会将它们合并(或声明冲突)。结果:。否则,Git将从“边”更改文件的那个版本获取文件的版本。那是你想要的一面吗?如果是这样,结果:。如果不是,则结果:

总结,对于这种情况,有三种可能的结果:

  • Base vs HEAD是唯一的变化:结果很好。
  • 唯一的变化是基础与他们的变化:结果很糟糕。
  • Base vs HEAD和base vs their都有变化:结果可能不好。

当然,合并基础提交F可能有名为Jenkinsfile no 文件。并且, commit 之一或两者都没有这样的文件。在这种情况下,它会变得有些棘手。我们待会儿讨论。

解决方案(以及到达那里的一些问题)

此处的解决方案是,在该文件旨在成为与分支相关的情况下,避免在所有提交中使用单个固定名称文件,例如Jenkinsfile。相反,假定提交F包含Jenkinsfile.masterJenkinsfile.developJenkinsfile.test。然后提交H也会有Jenkinsfile.masterJenkinsfile.developJenkinsfile.test,并且F更改为{{1} H中的}是您要保留的。由于提交Jenkinsfile.master在分支J中,因此它应该始终具有相同更改(有时从develop导入),或者在全部。因此,在两种情况下,Git的合并将做正确的事情。

相同的逻辑适用于其他每个此类文件。请注意,此时,所有分支提示标识的提交都应该完全没有名为master no 文件(不带后缀)。当然,这是一个理想的目标状态:要达到目标状态,您实际上必须在每个分支中进行新的提交,重命名现有的Jenkinsfile。但这对所有现有提交都完全没有影响。您存储库中的所有历史记录将一直冻结。这意味着在某个时候,您将运行Jenkinsfile,而git merge将找到仅包含git merge,没有Jenkinsfile且没有{{1} }或任何其他后缀。

现在让我们假设您已经在Jenkinsfile.masterJenkinsfile.develop中进行了重命名,但是在合并基础H中,您没有-显然,因为这是历史性的提交。因此J有一个F并且没有重命名的文件,而FJenkinsfile no H但确实有重命名的文件

现在,请记住上面我们展示了J运行的Jenkinsfile的位置,以找出自合并基础以来发生了什么变化。参数之一是git diff。比较{{1}时,这会将Git指示为猜测 git merge中的文件--find-renamesJenkinsfile中的F是“相同”文件}}和Jenkinsfile.masterHF的比较也是如此:旧的H与新的F是同一个文件吗?

如果您点击了指向https://en.wikipedia.org/wiki/Ship_of_Theseus的链接,您将发现对身份随时间变化的问题没有哲学上正确的答案。但是Git的答案是 ,即:如果文件的相似性索引为50%或更高,则它是同一文件。我们无需在这里担心关于Git如何计算相似度指数(有点复杂);在这两种情况下,Git都很有可能检测到重命名。

这在实践中意味着什么是您第一次运行J第一次,Git会立即声明合并冲突,这是我喜欢的类型称为高级冲突。也就是说,Git会说Jenkinsfile两个分支中都被重命名,但是改名为两个不同的名称。 Git不知道是要使用主版本,还是要使用开发版本,或者两者都使用,或者都不使用,或者什么。它只会因合并冲突而停止。 可以,因为它使您有机会解决冲突,可以通过选择Jenkinsfile.developgit merge中出现的Jenkinsfile文件来解决此冲突分支,选择Jenkinsfile.mastermaster分支中出现的--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尝试合并两个潜在的符号链接目标更改时,您将回到同一个合并问题。)