简短版:虽然下面的内容都很重要,但查看合并的最简单方法是进行合并。只需确保一切干净(例如,git status
没有需要检查的内容),然后运行合并并查看其结果。如果不喜欢结果,则可以使用以下命令中的一个中止合并(如果失败并需要帮助)或撤消合并(如果成功但您不喜欢结果):
git merge --abort
或者:
git reset --hard HEAD^ # after merge succeeds but you don't like the result
“Deleted”表示在比较两个文件树时,“左侧”比较的树中有某些内容,而“右侧”比较的树不再有该内容。
“模式”标识文件对象的类型,“120000”具体指“符号链接”。因此,这意味着在将版本“a”与版本“b”进行比较时,在版本“a”中曾经存在名为“flash/controllers”的符号链接,在版本“b”中已不再存在。
如果你只运行
git diff origin/master
,左侧的树(tree
a
)是您当前的工作目录(不是特定的提交!),右侧的树(tree
b
)是由
origin/master
标识的提交中的树(使用
git rev-parse origin/master
查看此提交的实际SHA-1)。如果您命名两个提交,例如
git diff master origin/master
,则左侧的树是由第一个SHA-1命名的树(再次使用
git rev-parse
查看原始SHA-1),右侧的树是由第二个SHA-1命名的树。
“这是否意味着如果我合并后文件会被删除?”
也许;也可能不会。确定一个文件是否会在合并时被删除的因素是合并基础和被合并项之间的差异。
假设,举例来说,上面的
a
代表您当前分支末端(通常命名为
master
),而
b
代表您的名为
origin/master
的分支末端(这是一个“远程分支”,但实际上它是您自己的存储库中的一个分支;您只是从
origin
复制了它)。
我们不知道master
和origin/master
之间的合并基础。那将是某个提交 - 我们只是不知道是哪一个。
假设如果我们将提交图绘制出来,看起来像这样:
...-o-o-B-u <-- HEAD=master
\
t <-- origin/master
在这里,您在您的
master
分支上做了一个提交。 我将此标记为
u
,表示
我们
。 同时,“他们”(无论他们是谁)在他们的分支上进行了一次提交,他们称之为
master
,而您将其称为
origin/master
; 我将此提交标记为
t
,表示
他们
。 共同的起点 - 您和他们分歧的地方 - 是我在这里标记为
B
的提交。
您可以使用
git merge-base
命令找到此提交的SHA-1 ID:
git merge-base HEAD origin/master
如果你这样做:
git diff -M --name-status $(git merge-base HEAD origin/master) origin/master
假定你使用类 Unix 的命令行(例如
sh
或
bash
),
1 git 会告诉你在他们的提交
t
中添加、修改、删除和重命名的文件。也就是说,它比较了提交
B
与
origin/master
的尾端提交
t
。
如果你将提交
B
与当前分支的尾端提交(即
HEAD
提交)进行比较,你会看到在你的提交
u
中所做的更改。
然后你可以执行以下操作:
git merge origin/master
Git会尝试合并“你所做的”和“他们所做的”。以下是符号链接(或任何文件)发生的关键,一般有三种可能性,但只有两种是可能的:
如果符号链接在B
中存在,则您可能什么也没做,他们删除了它。在这种情况下,git merge
将删除它:您什么也没做,他们删除了它,将它们合并很容易:删除它。
如果符号链接在B
中不存在,则您添加了它,他们可能什么也没做。在这种情况下,git merge
将添加(保留)它。同样,这很容易组合:您添加了,他们什么也没做,两者的组合是“添加”(或者说是“保留”)。
如果符号链接在B
中存在,但您更改了它,而他们删除了它,git merge
将声明冲突并让您自己解决问题。这对于git来说不容易组合,就像如果您和他们都以不同的方式更改了它一样,它不知道该怎么做。2
(我们知道,或者至少根据原始的
git diff
输出相信,与提交
HEAD
相关联的树具有符号链接,而与提交
origin/master
相关联的树没有,因此我们不必担心“两者都创建”或“两者都修改”的情况。)
这很令人困惑,因为我在gitignore中有/flash/controllers/
。
但是那是带有尾随斜杠的flash/controllers/
(前导斜杠无论如何都不重要,我认为——尽管忽略规则有时仍会让我感到困惑)。gitignore文档包括以下内容:
• If the pattern ends with a slash, it is removed for the purpose of
the following description, but it would only find a match with a
directory. In other words, foo/ will match a directory foo and
paths underneath it, but will not match a regular file or a
symbolic link foo (this is consistent with the way how pathspec
works in general in Git).
(还有一个问题,如果一个文件已经被追踪,将其添加到
.gitignore
中并不会使其成为未跟踪的。
.gitignore
内容仅用于防止文件意外地被跟踪,并避免
git status
抱怨它们。)
1事实上,针对合并基准的差异具有特殊的git diff
语法,重新利用了gitrevisions
中的三个点A...B
语法。要找到A
和B
之间的合并基准并将其与提交B
的内容进行比较,可以编写git diff A...B
。一如既往,省略一个端点等同于指定HEAD
,因此git diff ...origin/master
将合并基准(HEAD
和origin/master
的)与origin/master
进行比较;而git diff origin/master...
将合并基准(相同的两个提交)与HEAD
进行比较。添加-M
以检测重命名,你就得到了git merge
使用的比较。
(我在这里故意省略了一些有关“递归定义”的虚拟合并基础的额外细节。如果存在两个“同样有效”的合并基础祖先,git diff
将任意选择一个,但使用recursive
策略的git merge
将构建一个虚拟合并基础。这会变得有点复杂。)
2对于大多数常规文件,如果您和他们都修改了它们,则git会尝试通过决定哪些更改是独立的(git保留每个这样更改的副本)以及哪些更改是相同的(git也保留那些更改的一个副本,而不是放入两个副本)。git是否实际上正确地判断哪些更改是“相同的”和哪些更改是“不同的”——这是另一回事。它通常做得很好,但它只是一个计算机算法,有时会出错,因此您作为用户必须以某种方式检查结果(例如,“它仍然构建,它是否通过测试,它看起来还好”,您拥有的任何测试),以确保真正确定。