Git 应该显示没有合并冲突
Git shows no merge conflicts when it should
根据我对合并冲突的理解,当两个人更改了同一个文件,and/or修改了该文件中的同一行时,就会发生合并冲突。所以当我做了
git pull origin master
我预计会发生合并冲突,因为同一行在两个版本中都不同,但看起来 git 决定覆盖我的本地文件。
要提供更多信息,
几天前,我在 Github 上推送了我的版本。然后有人将其拉出,对其进行处理,然后将其推回 github。我对另一个人修改的两个文件感兴趣。
第一个文件是一个配置文件,另一个人在里面修改了密码。因此,当我从 github 拉取时,本地版本的密码与 github 上的密码不同。但是,在我的终端中,它说
Auto-merging <filename>
而且,它覆盖了我的文件,密码是另一个人设置的。
第二个感兴趣的文件是用模板引擎 (PUG) 编写的 HTML 文件。另一个人在该文件中更改了很多东西,比如添加了很多 css 类,删除了一些我用过的 类,添加了指向 css 文件的链接以及所有.但是当我拉它时,终端甚至没有提到它正在自动合并它,只是覆盖了我本地存储库中的整个文件并使用了 Github.
中的那个
对于这两个文件,我的问题是,这是否是使用 git pull 的预期行为,还是我做错了什么?
以下是我使用的命令。
git checkout -b "misc"
git pull origin master
此外,我尝试只使用 fetch 然后手动 merge/commit 它,但是当我使用 fetch 时,没有任何反应。文件根本没有改变。
我以前使用过 git/github,但从未真正在使用分支机构的团队中广泛工作过,pushing/pulling 来自 github。
检查存储库中的 .git/config
,如果配置包含以下内容,git 也会选择其他人的更改:
[branch "master"]
mergeoptions = --strategy-option theirs
如果这是真的,删除 mergeoptions
行。
recursive
...This is the default merge strategy when pulling or
merging one branch. The recursive strategy can take the following options:
ours This option forces conflicting hunks to be auto-resolved cleanly
by favoring our version. Changes from the other tree that do not
conflict with our side are reflected to the merge result. For a binary
file, the entire contents are taken from our side.
...
theirs This is the opposite of ours.
Git 运行正常。这是预期的(虽然在您的情况下不是真正“期望的”)结果。
底部有一些关于如何使用 Git 使其对您真正有用的信息。
除了,还有一种可能性更大。你这样做了:
git checkout -b "misc"
git pull origin master
第一行很直白。第二个是 extra-complicated,因为 git pull
是 git fetch
后面是 git merge
,这两个本身都有点复杂。
绘制图表(参见Pretty git branch graphs)
每当您在 Git 中使用分支时,您 总是 使用分支,所以这实际上只是“每当您使用 Git"—牢记 提交图 很重要。图,或 DAG(有向无环图),总是在那里,通常潜伏在看不见的地方。要使用 git log
查看它,请使用 --graph
,通常使用 --oneline
。要使用可视化工具查看它,请使用 gitk
之类的东西或许多烦人的 GUI 之一,它会为您提供类似于 所示的视图(这只是 Whosebug 上的一个 randomly-chosen 问题,关于什么是参见 gitk
与 git-gui
).
图形决定了合并的工作方式,因此在当时非常重要。在其他时候,它大多只是潜伏,不碍事但 ever-present。 Git 中的几乎所有内容都围绕 添加 提交,这会向该图添加条目。1
所以,让我们画一点图,然后观察 git fetch
和 git merge
的作用。
这是一个只有 master
分支的存储库图,上面有四个提交:
o--o--o--o <-- master
master
分支“指向”tip-most 提交。在此图中,右侧是较新的提交,即 right-most 提交。
每个提交也向后指向它的父提交。也就是说,o--o--o
中的行实际上应该是箭头:o <- o <- o
。但是这些箭头都指向后面,这对人类来说很烦人而且几乎没有用,所以最好把它们画成线。问题是这些向后箭头是 如何 Git 找到更早的提交,因为分支名称 仅 指向 tip-most提交!
Git 也有名称 HEAD
,这是“当前提交”的符号。 HEAD 通常的工作方式是它实际上包含分支名称,然后分支名称指向 tip 提交。我们可以用一个单独的箭头画出来:
HEAD
|
v
o--o--o--o <-- master
但这太占地方了,所以我通常用这个:
o--o--o--o <-- master (HEAD)
Git 会发现 HEAD
“附加到”(包含名称)master
,然后按照从 master
的向后箭头到提示提交。
Hint: use git log --decorate
to show branch names and HEAD
. It's particularly good with --oneline --graph
: think of this as a friendly dog: Decorate, Oneline, Graph. In Git 2.1 and later, --decorate
happens automatically, so you don't have to turn it on yourself most of the time. See also this answer to Pretty git branch graphs.
Note that git log --decorate
prints the decoration as HEAD -> master
when HEAD
points to master
. When HEAD
points directly to a commit, Git calls this a detached HEAD, and you might see HEAD, master
instead. This formatting trick was new in Git 2.4: before that, it just showed HEAD, master
for both detached HEAD mode, and non-detached-HEAD mode, for this case. In any case, I call "non-detached" an attached HEAD, and I think master (HEAD)
shows this attachment pretty well.)
现在,git checkout -b misc
步骤创建了一个新的分支名称。默认情况下,这个新分支名称指向当前(HEAD)提交,所以现在我们有:
o--o--o--o <-- master, misc (HEAD)
1事实上,您永远无法更改 提交。看似改变提交的东西,实际上是通过添加一个类似于旧提交的 new 提交来工作的,然后它们会掩盖旧提交并向您展示新提交。这使得它 看起来像 提交已更改,但实际上并没有。您也不能 删除 提交,或者至少不能直接删除:您所能做的就是让它们 unreachable,从分支和标签名称和类似。一旦无法访问提交,Git 的维护“垃圾收集器”最终 将其删除。 git gc
现在删除它们可能很困难。 Git 非常努力地让您取回您的提交,即使您希望它们消失。
但是,所有这些仅适用于 提交 ,因此经验法则:“尽早并经常提交”。您实际提交的任何内容,Git 都会尝试让您稍后再次检索,通常最多 30 或 90 天。
git fetch
git fetch
的作用可以概括为:
- 召唤另一个Git;
- 问它它有哪些提交;和
- 收集这些提交,以及使这些提交合理所需的任何其他内容,并将它们添加到您的存储库中。
这样一来,Git就相当于The Borg。但是,Git 不会说:“我们是博格人。我们会将你的生物和技术独特性添加到我们自己的产品中,”而是说“我是 Git。你的 technologically-distinctive 提交将被添加到我自己的!"
那么,让我们看看当您 git fetch origin
时会发生什么。你有这个:
o--o--o--o <-- master, misc (HEAD)
他们有这个,他们的master
有几个额外的提交(我们现在不关心他们的 HEAD):
o--o--o--o--o--o <-- master
你的 Git 重命名 他们的主人,在你自己的那端称它为 origin/master
,这样你就可以让他们保持正直。他们的两个新提交是 adde到您的存储库,所有 Borg-like。这些新的提交指向现有的四个提交,带有通常的向后箭头,但现在绘制图形需要更多空间:
o--o--o--o <-- master, misc (HEAD)
\
o--o <-- origin/master
请注意,none 个 您的 个分支已更改。只有 origin
改变了。您的 Git 添加了它们的技术独特性,2 和 re-points 您的 origin/master
以跟踪“哪里master
我上次查看时在 origin
。"
2这就是那些又大又丑的 SHA-1 ID 的用武之地。散列是 Git 如何判断的哪些提交对于哪个存储库是唯一的。关键是相同的commit总是会产生相同的hash ID,所以如果theirGit 有提交 12ab9fc7...
,并且 你的 Git 有提交 12ab9fc7...
,你的 Git 已经有他们的提交,反之亦然。这一切背后的数学是相当深刻和美丽的。
git merge
git pull
的后半部分是运行git merge
。它 运行 相当于 git merge origin/master
的 3。 git merge
命令首先找到 合并基础 ,这就是图形突然变得重要的地方。
两次提交之间的 merge base 笼统地说是“图中所有线都回到一起的点”。通常这两个提交是由两个分支 names 进行的两次 branch-tips, pointed-to。一个典型的、非常明显的案例发生在:
o--o <-- branch1 (HEAD)
/
o--o--o--*
\
o--o--o <-- branch2
git merge
所做的是找到最近的 common-ancestor 提交,我在这里将其绘制为 *
而不是 o
。那就是合并基地。这只是两个分支“分叉”的点。
git merge
的 目标 是找出“你”改变了什么——自提交 [=81] 以来你在 branch1
中做了什么=]——以及“他们”发生了什么变化,即自提交 *
以来 branch2
发生了什么变化。要获得这些更改,Git 运行 两个 git diff
命令。
即使我们像这样绘制提交也是如此:
o--o--o--*--o--o <-- branch1 (HEAD)
\
o--o--o <-- branch2
这是同一个graph,所以也是同一个merge。 Git 将提交 *
与 branch1
的提示进行比较(“我们的两次提交发生了什么变化?”),并将 *
与 branch2
的提示进行比较( “他们的三个提交有什么变化?”)。然后 Git 尽最大努力 合并 这些更改,并根据结果进行新的 merge 提交。所有这一切的确切细节 combining-and-committing 还不重要,因为我们没有那样的图表。
我们有的是这个:
o--o--o--* <-- master, misc (HEAD)
\
o--o <-- origin/master
请注意,我在这里保留了 *
概念。那是因为 git merge
仍然找到合并基础。这里的问题是合并基础是分支提示:名称misc
直接指向提交*
.
如果Git 执行git diff <commit-*> <commit-*>
,diff 显然是空的。提交 *
与提交 *
相同。那么我们如何合并这些呢?
Git的回答是:我们根本不合并。我们做 Git 所谓的 快进 。请注意,虽然内部提交箭头都指向后方,但如果我们只是想象它们指向前方,那么现在很容易将 misc
branch-label 和 向前滑动 , 沿着 dog-leg 向下,然后向右。结果如下所示:
o--o--o--o <-- master
\
o--o <-- origin/master, misc (HEAD)
所以现在我们的配置文件是 HEAD
提交中的那个,它是 misc
的 tip-most 提交,与 origin/master
相同的提交。
换句话说,我们丢失了对配置文件的我们的更改,因为它们被他们对配置文件的更改覆盖了。
3为什么它实际上不使用git merge origin/master
的细节在这里大多无关紧要,但与历史有很大关系。在过去的 Git 版本 1.8.4 之前,一些 git fetch origin
从来没有真正费心去更新 origin/master
。这是一个糟糕的设计决定,在所有现代 Git 版本中,git fetch
会 更新它。
进行“真正的合并”而不是 fast-forward 会有帮助吗?
如果我们回到原来的设置(并删除名称 master
,因为它挡住了路):
o--o--o--* <-- misc (HEAD)
\
o--o <-- origin/master
我们可以,而不是让 git pull
运行 git merge
,运行 我们自己的 git merge --no-ff origin/master
,合并 origin/master 但 不允许Git做一个fast-forward。这会有帮助吗?
唉,没有。请记住,合并的目标 是合并 自merge-base 以来的所有更改。所以 Git 将 运行 两个差异:
git diff <commit-*> <commit-*> # this diff is empty
git diff <commit-*> origin/master # this is "what they changed"
Git 然后将我们的更改 (none) 与他们的更改合并,并进行新的合并提交:
o--o--o--o------o <-- misc (HEAD)
\ /
o--o <-- origin/master
我们有一个不同的 graph(它有点像汤勺或 Big Dipper),但我们采用了他们的更改,包括密码 cange,同时不保留我们的任何东西(自合并基础以来我们没有任何变化)。
使合并有用
我们需要的是确保“我们的”改变——它们必须是我们的改变,在Git的眼里——“看起来不同”他们的”变化。这意味着我们需要 Git 选择一个 不同的 合并基础。
正如我上面所说,合并基础是我们的提交和他们的提交开始分歧的点。这意味着我们需要创建自己的分支,并确保我们不会“快进”太多,甚至根本不会“快进”。
所以,我们可能 do 想避免 git pull
.4 我们也可能想选择一个更早的点我们创建自己的分支机构。我们希望我们的图的分支能够保持自己的独特性,就像它们的分支一样。我已经给出了其中的一些提交 letter-names 以便我可以谈论它们:
A-----B <-- misc (HEAD)
/ /
o--o--o--o <-- master
\
o--C <-- origin/master
在提交 A
中,我们更改配置文件以使用不同的密码。然后我们git merge
(不是fast-forward)master
的提示来获取新的东西,而不让密码改变。这一步可能是非常手动的,也可能是完全自动的,但是一旦提交,我们就完成了:提交是永久的;它们无法更改。5
现在我们可以让 master
像往常一样“快进”:
A-----B <-- misc (HEAD)
/ /
o--o--o--*--o--C <-- master, origin/master
现在,当我们 git merge origin/master
或 git merge master
、6 时,合并基础将是我标记为 *
的提交。如果我们没有将密码从 *
更改为 B
,而他们将密码从 *
更改为 C
,我们将接受他们的更改——但他们不应该更长 需要 来更改它,因为我们从不向他们发送提交 A
和 B
;我们将这些留给自己。所以密码从 *
到 C
应该没有变化,我们将在进行新合并时保留更改后的密码:
A-----B-----D <-- misc (HEAD)
/ / /
o--o--o--o--o--C <-- master, origin/master
稍后,我们将获取更多提交,将它们合并(快进)到 master
,并准备好再次合并:
A-----B-----D <-- misc (HEAD)
/ / /
o--o--o--o--o--C--o--o <-- master, origin/master
这一次,合并基础将提交 C
——它是在 misc
和它们的分支上最近的一个——并且 Git 将 diff C
与 origin/master
。估计他们还是不会改密码,因为我们还是没有给他们commit D
.
4我尽可能避免使用git pull
,但是根据你的处理方式,你可能无论如何都可以使用它,特别是对于master
.
5我们通过将分支标签移动到新提交来进行任何普通的新提交:记住分支名称只是指向 tip-most 提交。我们只是做了一个新的 tip 提交,它的父级是前一个 tip-most 提交,re-point 标签,向前移动了一步。但是看看当我们为它的父项进行指向 更远 的新提交时会发生什么,而不仅仅是旧的提示提交。现在我们通过隐藏一些以前的提交来“重写历史”。 (尝试绘制此图。)这就是 git commit --amend
和 git rebase
的工作方式。
6注意这些做同样的事情,作为master
的提示和[=68的提示=] 是 相同的提交 。一个区别是默认的提交消息会改变:一个会说“merge master”,另一个会说“merge origin/master”。 (在 Git 的提交消息格式中有一些繁琐的东西将 master
与其他所有内容区别对待,但我们可以忽略它。这只是一个历史产物。)
最后一点:提交中的配置和密码 = 错误
因为提交 如此永久,所以通常 非常 将密码放入其中是个坏主意。任何有权访问您的存储库的人都可以查看历史提交并找到密码。
配置文件通常也不应该提交,尽管这里没有真正的安全问题。相反,这是您遇到的问题 运行:每个人都需要不同的配置。将你的提交到共享存储库是没有意义的。如果它是一个 private 存储库,这更有意义,如果它是一个私有 branch 就没问题(如果在大多数情况下仍然是 sub-optimal例)。
需要某种 示例 配置或默认初始配置是很常见的。 这些确实应该提交。诀窍是确保示例或默认初始配置与“实时”配置分开。例如,对于某些系统,您将包括:
config.default
还有一点代码,比如:
[ -f .config ] || cp config.default .config
将默认配置设置为第一个 运行 上的 .config
文件。然后在 .gitignore
中使用 .config
,它永远不会被放入 到 存储库中,因此它永远不会出现在任何提交和 y你一开始就不会遇到这个问题。
根据我对合并冲突的理解,当两个人更改了同一个文件,and/or修改了该文件中的同一行时,就会发生合并冲突。所以当我做了
git pull origin master
我预计会发生合并冲突,因为同一行在两个版本中都不同,但看起来 git 决定覆盖我的本地文件。
要提供更多信息, 几天前,我在 Github 上推送了我的版本。然后有人将其拉出,对其进行处理,然后将其推回 github。我对另一个人修改的两个文件感兴趣。
第一个文件是一个配置文件,另一个人在里面修改了密码。因此,当我从 github 拉取时,本地版本的密码与 github 上的密码不同。但是,在我的终端中,它说
Auto-merging <filename>
而且,它覆盖了我的文件,密码是另一个人设置的。
第二个感兴趣的文件是用模板引擎 (PUG) 编写的 HTML 文件。另一个人在该文件中更改了很多东西,比如添加了很多 css 类,删除了一些我用过的 类,添加了指向 css 文件的链接以及所有.但是当我拉它时,终端甚至没有提到它正在自动合并它,只是覆盖了我本地存储库中的整个文件并使用了 Github.
中的那个对于这两个文件,我的问题是,这是否是使用 git pull 的预期行为,还是我做错了什么?
以下是我使用的命令。
git checkout -b "misc"
git pull origin master
此外,我尝试只使用 fetch 然后手动 merge/commit 它,但是当我使用 fetch 时,没有任何反应。文件根本没有改变。
我以前使用过 git/github,但从未真正在使用分支机构的团队中广泛工作过,pushing/pulling 来自 github。
检查存储库中的 .git/config
,如果配置包含以下内容,git 也会选择其他人的更改:
[branch "master"]
mergeoptions = --strategy-option theirs
如果这是真的,删除 mergeoptions
行。
recursive
...This is the default merge strategy when pulling or merging one branch. The recursive strategy can take the following options:
ours This option forces conflicting hunks to be auto-resolved cleanly by favoring our version. Changes from the other tree that do not conflict with our side are reflected to the merge result. For a binary file, the entire contents are taken from our side.
...
theirs This is the opposite of ours.
Git 运行正常。这是预期的(虽然在您的情况下不是真正“期望的”)结果。
底部有一些关于如何使用 Git 使其对您真正有用的信息。
除了
git checkout -b "misc"
git pull origin master
第一行很直白。第二个是 extra-complicated,因为 git pull
是 git fetch
后面是 git merge
,这两个本身都有点复杂。
绘制图表(参见Pretty git branch graphs)
每当您在 Git 中使用分支时,您 总是 使用分支,所以这实际上只是“每当您使用 Git"—牢记 提交图 很重要。图,或 DAG(有向无环图),总是在那里,通常潜伏在看不见的地方。要使用 git log
查看它,请使用 --graph
,通常使用 --oneline
。要使用可视化工具查看它,请使用 gitk
之类的东西或许多烦人的 GUI 之一,它会为您提供类似于 gitk
与 git-gui
).
图形决定了合并的工作方式,因此在当时非常重要。在其他时候,它大多只是潜伏,不碍事但 ever-present。 Git 中的几乎所有内容都围绕 添加 提交,这会向该图添加条目。1
所以,让我们画一点图,然后观察 git fetch
和 git merge
的作用。
这是一个只有 master
分支的存储库图,上面有四个提交:
o--o--o--o <-- master
master
分支“指向”tip-most 提交。在此图中,右侧是较新的提交,即 right-most 提交。
每个提交也向后指向它的父提交。也就是说,o--o--o
中的行实际上应该是箭头:o <- o <- o
。但是这些箭头都指向后面,这对人类来说很烦人而且几乎没有用,所以最好把它们画成线。问题是这些向后箭头是 如何 Git 找到更早的提交,因为分支名称 仅 指向 tip-most提交!
Git 也有名称 HEAD
,这是“当前提交”的符号。 HEAD 通常的工作方式是它实际上包含分支名称,然后分支名称指向 tip 提交。我们可以用一个单独的箭头画出来:
HEAD
|
v
o--o--o--o <-- master
但这太占地方了,所以我通常用这个:
o--o--o--o <-- master (HEAD)
Git 会发现 HEAD
“附加到”(包含名称)master
,然后按照从 master
的向后箭头到提示提交。
Hint: use
git log --decorate
to show branch names andHEAD
. It's particularly good with--oneline --graph
: think of this as a friendly dog: Decorate, Oneline, Graph. In Git 2.1 and later,--decorate
happens automatically, so you don't have to turn it on yourself most of the time. See also this answer to Pretty git branch graphs.Note that
git log --decorate
prints the decoration asHEAD -> master
whenHEAD
points tomaster
. WhenHEAD
points directly to a commit, Git calls this a detached HEAD, and you might seeHEAD, master
instead. This formatting trick was new in Git 2.4: before that, it just showedHEAD, master
for both detached HEAD mode, and non-detached-HEAD mode, for this case. In any case, I call "non-detached" an attached HEAD, and I thinkmaster (HEAD)
shows this attachment pretty well.)
现在,git checkout -b misc
步骤创建了一个新的分支名称。默认情况下,这个新分支名称指向当前(HEAD)提交,所以现在我们有:
o--o--o--o <-- master, misc (HEAD)
1事实上,您永远无法更改 提交。看似改变提交的东西,实际上是通过添加一个类似于旧提交的 new 提交来工作的,然后它们会掩盖旧提交并向您展示新提交。这使得它 看起来像 提交已更改,但实际上并没有。您也不能 删除 提交,或者至少不能直接删除:您所能做的就是让它们 unreachable,从分支和标签名称和类似。一旦无法访问提交,Git 的维护“垃圾收集器”最终 将其删除。 git gc
现在删除它们可能很困难。 Git 非常努力地让您取回您的提交,即使您希望它们消失。
但是,所有这些仅适用于 提交 ,因此经验法则:“尽早并经常提交”。您实际提交的任何内容,Git 都会尝试让您稍后再次检索,通常最多 30 或 90 天。
git fetch
git fetch
的作用可以概括为:
- 召唤另一个Git;
- 问它它有哪些提交;和
- 收集这些提交,以及使这些提交合理所需的任何其他内容,并将它们添加到您的存储库中。
这样一来,Git就相当于The Borg。但是,Git 不会说:“我们是博格人。我们会将你的生物和技术独特性添加到我们自己的产品中,”而是说“我是 Git。你的 technologically-distinctive 提交将被添加到我自己的!"
那么,让我们看看当您 git fetch origin
时会发生什么。你有这个:
o--o--o--o <-- master, misc (HEAD)
他们有这个,他们的master
有几个额外的提交(我们现在不关心他们的 HEAD):
o--o--o--o--o--o <-- master
你的 Git 重命名 他们的主人,在你自己的那端称它为 origin/master
,这样你就可以让他们保持正直。他们的两个新提交是 adde到您的存储库,所有 Borg-like。这些新的提交指向现有的四个提交,带有通常的向后箭头,但现在绘制图形需要更多空间:
o--o--o--o <-- master, misc (HEAD)
\
o--o <-- origin/master
请注意,none 个 您的 个分支已更改。只有 origin
改变了。您的 Git 添加了它们的技术独特性,2 和 re-points 您的 origin/master
以跟踪“哪里master
我上次查看时在 origin
。"
2这就是那些又大又丑的 SHA-1 ID 的用武之地。散列是 Git 如何判断的哪些提交对于哪个存储库是唯一的。关键是相同的commit总是会产生相同的hash ID,所以如果theirGit 有提交 12ab9fc7...
,并且 你的 Git 有提交 12ab9fc7...
,你的 Git 已经有他们的提交,反之亦然。这一切背后的数学是相当深刻和美丽的。
git merge
git pull
的后半部分是运行git merge
。它 运行 相当于 git merge origin/master
的 3。 git merge
命令首先找到 合并基础 ,这就是图形突然变得重要的地方。
两次提交之间的 merge base 笼统地说是“图中所有线都回到一起的点”。通常这两个提交是由两个分支 names 进行的两次 branch-tips, pointed-to。一个典型的、非常明显的案例发生在:
o--o <-- branch1 (HEAD)
/
o--o--o--*
\
o--o--o <-- branch2
git merge
所做的是找到最近的 common-ancestor 提交,我在这里将其绘制为 *
而不是 o
。那就是合并基地。这只是两个分支“分叉”的点。
git merge
的 目标 是找出“你”改变了什么——自提交 [=81] 以来你在 branch1
中做了什么=]——以及“他们”发生了什么变化,即自提交 *
以来 branch2
发生了什么变化。要获得这些更改,Git 运行 两个 git diff
命令。
即使我们像这样绘制提交也是如此:
o--o--o--*--o--o <-- branch1 (HEAD)
\
o--o--o <-- branch2
这是同一个graph,所以也是同一个merge。 Git 将提交 *
与 branch1
的提示进行比较(“我们的两次提交发生了什么变化?”),并将 *
与 branch2
的提示进行比较( “他们的三个提交有什么变化?”)。然后 Git 尽最大努力 合并 这些更改,并根据结果进行新的 merge 提交。所有这一切的确切细节 combining-and-committing 还不重要,因为我们没有那样的图表。
我们有的是这个:
o--o--o--* <-- master, misc (HEAD)
\
o--o <-- origin/master
请注意,我在这里保留了 *
概念。那是因为 git merge
仍然找到合并基础。这里的问题是合并基础是分支提示:名称misc
直接指向提交*
.
如果Git 执行git diff <commit-*> <commit-*>
,diff 显然是空的。提交 *
与提交 *
相同。那么我们如何合并这些呢?
Git的回答是:我们根本不合并。我们做 Git 所谓的 快进 。请注意,虽然内部提交箭头都指向后方,但如果我们只是想象它们指向前方,那么现在很容易将 misc
branch-label 和 向前滑动 , 沿着 dog-leg 向下,然后向右。结果如下所示:
o--o--o--o <-- master
\
o--o <-- origin/master, misc (HEAD)
所以现在我们的配置文件是 HEAD
提交中的那个,它是 misc
的 tip-most 提交,与 origin/master
相同的提交。
换句话说,我们丢失了对配置文件的我们的更改,因为它们被他们对配置文件的更改覆盖了。
3为什么它实际上不使用git merge origin/master
的细节在这里大多无关紧要,但与历史有很大关系。在过去的 Git 版本 1.8.4 之前,一些 git fetch origin
从来没有真正费心去更新 origin/master
。这是一个糟糕的设计决定,在所有现代 Git 版本中,git fetch
会 更新它。
进行“真正的合并”而不是 fast-forward 会有帮助吗?
如果我们回到原来的设置(并删除名称 master
,因为它挡住了路):
o--o--o--* <-- misc (HEAD)
\
o--o <-- origin/master
我们可以,而不是让 git pull
运行 git merge
,运行 我们自己的 git merge --no-ff origin/master
,合并 origin/master 但 不允许Git做一个fast-forward。这会有帮助吗?
唉,没有。请记住,合并的目标 是合并 自merge-base 以来的所有更改。所以 Git 将 运行 两个差异:
git diff <commit-*> <commit-*> # this diff is empty
git diff <commit-*> origin/master # this is "what they changed"
Git 然后将我们的更改 (none) 与他们的更改合并,并进行新的合并提交:
o--o--o--o------o <-- misc (HEAD)
\ /
o--o <-- origin/master
我们有一个不同的 graph(它有点像汤勺或 Big Dipper),但我们采用了他们的更改,包括密码 cange,同时不保留我们的任何东西(自合并基础以来我们没有任何变化)。
使合并有用
我们需要的是确保“我们的”改变——它们必须是我们的改变,在Git的眼里——“看起来不同”他们的”变化。这意味着我们需要 Git 选择一个 不同的 合并基础。
正如我上面所说,合并基础是我们的提交和他们的提交开始分歧的点。这意味着我们需要创建自己的分支,并确保我们不会“快进”太多,甚至根本不会“快进”。
所以,我们可能 do 想避免 git pull
.4 我们也可能想选择一个更早的点我们创建自己的分支机构。我们希望我们的图的分支能够保持自己的独特性,就像它们的分支一样。我已经给出了其中的一些提交 letter-names 以便我可以谈论它们:
A-----B <-- misc (HEAD)
/ /
o--o--o--o <-- master
\
o--C <-- origin/master
在提交 A
中,我们更改配置文件以使用不同的密码。然后我们git merge
(不是fast-forward)master
的提示来获取新的东西,而不让密码改变。这一步可能是非常手动的,也可能是完全自动的,但是一旦提交,我们就完成了:提交是永久的;它们无法更改。5
现在我们可以让 master
像往常一样“快进”:
A-----B <-- misc (HEAD)
/ /
o--o--o--*--o--C <-- master, origin/master
现在,当我们 git merge origin/master
或 git merge master
、6 时,合并基础将是我标记为 *
的提交。如果我们没有将密码从 *
更改为 B
,而他们将密码从 *
更改为 C
,我们将接受他们的更改——但他们不应该更长 需要 来更改它,因为我们从不向他们发送提交 A
和 B
;我们将这些留给自己。所以密码从 *
到 C
应该没有变化,我们将在进行新合并时保留更改后的密码:
A-----B-----D <-- misc (HEAD)
/ / /
o--o--o--o--o--C <-- master, origin/master
稍后,我们将获取更多提交,将它们合并(快进)到 master
,并准备好再次合并:
A-----B-----D <-- misc (HEAD)
/ / /
o--o--o--o--o--C--o--o <-- master, origin/master
这一次,合并基础将提交 C
——它是在 misc
和它们的分支上最近的一个——并且 Git 将 diff C
与 origin/master
。估计他们还是不会改密码,因为我们还是没有给他们commit D
.
4我尽可能避免使用git pull
,但是根据你的处理方式,你可能无论如何都可以使用它,特别是对于master
.
5我们通过将分支标签移动到新提交来进行任何普通的新提交:记住分支名称只是指向 tip-most 提交。我们只是做了一个新的 tip 提交,它的父级是前一个 tip-most 提交,re-point 标签,向前移动了一步。但是看看当我们为它的父项进行指向 更远 的新提交时会发生什么,而不仅仅是旧的提示提交。现在我们通过隐藏一些以前的提交来“重写历史”。 (尝试绘制此图。)这就是 git commit --amend
和 git rebase
的工作方式。
6注意这些做同样的事情,作为master
的提示和[=68的提示=] 是 相同的提交 。一个区别是默认的提交消息会改变:一个会说“merge master”,另一个会说“merge origin/master”。 (在 Git 的提交消息格式中有一些繁琐的东西将 master
与其他所有内容区别对待,但我们可以忽略它。这只是一个历史产物。)
最后一点:提交中的配置和密码 = 错误
因为提交 如此永久,所以通常 非常 将密码放入其中是个坏主意。任何有权访问您的存储库的人都可以查看历史提交并找到密码。
配置文件通常也不应该提交,尽管这里没有真正的安全问题。相反,这是您遇到的问题 运行:每个人都需要不同的配置。将你的提交到共享存储库是没有意义的。如果它是一个 private 存储库,这更有意义,如果它是一个私有 branch 就没问题(如果在大多数情况下仍然是 sub-optimal例)。
需要某种 示例 配置或默认初始配置是很常见的。 这些确实应该提交。诀窍是确保示例或默认初始配置与“实时”配置分开。例如,对于某些系统,您将包括:
config.default
还有一点代码,比如:
[ -f .config ] || cp config.default .config
将默认配置设置为第一个 运行 上的 .config
文件。然后在 .gitignore
中使用 .config
,它永远不会被放入 到 存储库中,因此它永远不会出现在任何提交和 y你一开始就不会遇到这个问题。