如何判断 git 分支在压缩后是否已完全合并

dig*_*doo 7 git merge git-squash

当使用 合并分支时--squash,git 似乎不再能够确定分支是否已完全合并。如何使用现有的 git 命令快速检查这一点?

要重现,请创建一个新的 git 存储库:

$ mkdir tmp
$ cd tmp
$ git init
$ echo "bla" > ans
$ git add .
$ git commit -m "First commit"
Run Code Online (Sandbox Code Playgroud)

创建new-branch带有提交的分支:

$ git checkout -b new-branch
$ echo "blabla" >> ans
$ echo "blupp" > zwa
$ git add .
$ git commit -m "Commit on new-branch"
Run Code Online (Sandbox Code Playgroud)

创建another-branch带有提交的分支:

$ git checkout master
$ git checkout -b another-branch
$ echo "test" >> ans
$ echo "three" > dra
$ git add .
$ git commit -m "Commit on another-branch"
Run Code Online (Sandbox Code Playgroud)

合并:

$ git checkout master
$ git merge --squash new-branch
$ git commit -m "Squash new-branch"
$ git merge --squash another-branch
$ git mergetool
$ git commit -m "Squash another-branch"
$ git clean -f
Run Code Online (Sandbox Code Playgroud)

两个分支已合并。我现在应该可以删除它们了,对吗?很不幸的是,不行:

$ git branch -d new-branch
> error: The branch 'new-branch' is not fully merged.
> If you are sure you want to delete it, run 'git branch -D new-branch'.
$ git branch -d another-branch
> error: The branch 'another-branch' is not fully merged.
> If you are sure you want to delete it, run 'git branch -D another-branch'.
Run Code Online (Sandbox Code Playgroud)

我可以使用 option -D,但通常其他人会合并我的东西,我想检查合并是否成功。尝试使用git diff:

$ git diff new-branch
$ git diff ..new-branch
$ git diff ...new-branch
$ git diff new-branch..
$ git diff new-branch...
Run Code Online (Sandbox Code Playgroud)

上面的所有语句都会产生一个非空差异。另一种尝试:

$ git branch --merged
> * master
Run Code Online (Sandbox Code Playgroud)

这两个分支没有出现在合并分支列表中,因此这也不起作用。

如何判断分支是否已合并?

tor*_*rek 7

你无法得到你想要的东西。您可以获得一些可能足够好也可能不够好的东西。特别是,如果您使用普通格式git merge(带有--no-ffif/when required),Git 会记录提交。Git 并不真正关心文件,因为它关心的是提交。Git 也不太关心分支:它仍然与提交有关。

\n\n

插图,带有图表

\n\n

使用您的示例,创建一个新的空存储库并在此时停止:

\n\n
\n
$ git commit -m "First commit"\n
Run Code Online (Sandbox Code Playgroud)\n
\n\n

您现在拥有一个存储库,其中只有一个提交。该提交有一些又大又难看的哈希 ID,但我将使用该字母A来代替它。存储库现在有一个名为分支 ,master它保存了这一提交的哈希 ID A,因此我们可以这样绘制它:

\n\n
A   <-- master (HEAD)\n
Run Code Online (Sandbox Code Playgroud)\n\n

现在我们运行第二组命令:

\n\n
\n
$ git checkout -b new-branch\n
Run Code Online (Sandbox Code Playgroud)\n
\n\n

此时我们有:

\n\n
A   <-- new-branch (HEAD), master\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n
$ echo "blabla" >> ans\n$ echo "blupp" > zwa\n$ git add .\n$ git commit -m "Commit on new-branch"\n
Run Code Online (Sandbox Code Playgroud)\n
\n\n

这将创建新的提交B,并将名称new-branch向前拖动:

\n\n
A   <-- master\n \\\n  B   <--  new-branch (HEAD)\n
Run Code Online (Sandbox Code Playgroud)\n\n

现在我们使用您的第三组命令(我将稍微删减其中一个):

\n\n
\n
$ git checkout -b another-branch master\n
Run Code Online (Sandbox Code Playgroud)\n
\n\n

这会切换回 commit A,创建一个指向它的新分支名称,附加HEAD到新分支,然后给我们留下:

\n\n
A   <-- another-branch (HEAD), master\n \\\n  B   <--  new-branch\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n
$ echo "test" >> ans\n$ echo "three" > dra\n$ git add .\n$ git commit -m "Commit on another-branch"\n
Run Code Online (Sandbox Code Playgroud)\n
\n\n

这将创建第三次提交C,并将名称another-branch向前拖动:

\n\n
  C   <-- another-branch (HEAD)\n /\nA   <-- master\n \\\n  B   <--  new-branch\n
Run Code Online (Sandbox Code Playgroud)\n\n

现在我们来面对最困难的部分,这两个merge命令。我们从以下开始:

\n\n
\n
$ git checkout master\n
Run Code Online (Sandbox Code Playgroud)\n
\n\n

这会将提交的内容提取A到索引/暂存区域和工作树中,并将名称附加HEAD到名称master:

\n\n
  C   <-- another-branch\n /\nA   <-- master (HEAD)\n \\\n  B   <--  new-branch\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n
$ git merge --squash new-branch\n
Run Code Online (Sandbox Code Playgroud)\n
\n\n

这会执行一个合并操作,其中包括比较提交A与提交A(当然没有显示任何差异),然后是提交A与提交B(显示一些差异)。在这两个差异中发现的更改被组合起来\xe2\x80\x94,没有任何冲突,因为一组更改是“不执行任何操作”\xe2\x80\x94,并且 Git 停止进行新的提交,因此我们需要:

\n\n
\n
$ git commit -m "Squash new-branch"\n
Run Code Online (Sandbox Code Playgroud)\n
\n\n

这确实进行了新的提交。我称之为D:

\n\n
  C   <-- another-branch\n /\nA---D   <-- master (HEAD)\n \\\n  B   <--  new-branch\n
Run Code Online (Sandbox Code Playgroud)\n\n

请注意, commit与 commitD没有向后看的联系B;它只记住现有提交的哈希 ID A。您是否使用过:

\n\n
git merge --no-ff new-branch\n
Run Code Online (Sandbox Code Playgroud)\n\n

为了使D,我们有一条连接线(应该是箭头,但箭头字体并不总是在每个浏览器上都能正常工作)从 到 向右D向下B:

\n\n
A---D\n \\ /\n  B\n
Run Code Online (Sandbox Code Playgroud)\n\n

但我们不这样做。

\n\n

下一个:

\n\n
\n
$ git merge --squash another-branch\n
Run Code Online (Sandbox Code Playgroud)\n
\n\n

这次的合并操作包括比较 commit A(合并基础)和 commit D,以查看我们更改了什么,然后比较 commitA与 commit C(的尖端提交another-branch),以查看它们更改了什么。合并实际上是冲突的\xe2\x80\x94我们都更改了文件ans,在文件末尾的行中\xe2\x80\x94所以大多数形式的合并将在此处停止并出现冲突。因此你需要:

\n\n
\n
$ git mergetool\n
Run Code Online (Sandbox Code Playgroud)\n
\n\n

为了解决冲突,我们可以在 shell 中使用以下命令来解决:

\n\n
$ cat << END > ans\nbla\nblabla\ntest\nEND\n$ git add ans\n
Run Code Online (Sandbox Code Playgroud)\n\n

(您可以在此处文档部分中的 之前替换任何您想要的合并结果END)。这里的最后一步是提交合并。由于它是挤压合并,而不是真正的合并,因此我们再次没有指向 commit 的向后链接C,即使我们使用commitC进行合并:

\n\n
\n
$ git commit -m "Squash another-branch"\n
Run Code Online (Sandbox Code Playgroud)\n
\n\n

这会产生新的 commit/snapshot E,所以让我们画一下:

\n\n
  C   <-- another-branch\n /\nA---D--E   <-- master (HEAD)\n \\\n  B   <--  new-branch\n
Run Code Online (Sandbox Code Playgroud)\n\n

请注意,新提交E没有返回到 的连接C。使用名称master,Git 从 commit 开始E,返回到D,然后返回到A。提交A没有父\xe2\x80\x94,这是我们所做的第一个提交,毕竟\xe2\x80\x94,所以操作在此时停止。提交B并C在此过程中未找到。

\n\n

为什么git branch -d失败

\n\n

如果我们不使用git mergetool,我们不需要git clean清理它的垃圾文件,所以我将跳过它并继续:

\n\n
\n
$ git branch -d new-branch\n> error: The branch \'new-branch\' is not fully merged.\n> If you are sure you want to delete it, run \'git branch -D new-branch\'.\n
Run Code Online (Sandbox Code Playgroud)\n
\n\n

这告诉您的是,从提交E\xe2\x80\x94 到您现在所在的位置\xe2\x80\x94,无法找到 commit B。这是真的; 我们不会B在步行中找到从E回到根源的地方。事实上,名称new-branch是我们找到 commit 的唯一B方法。(记住,B代表一些我们永远无法猜测的随机哈希 ID。)如果我们删除name new-branch,我们将丢失 commit B。

\n\n

由于 Git 的核心就是提交,因此丢失提交会很糟糕。Git 不会丢弃该名称,从而丢失提交,除非您强制执行。

\n\n

commit 也是如此,只能通过 nameC查找。该命令将拒绝删除它,因为 commit不是当前 commit 的祖先。another-branchgit branch -dCE

\n\n

如果我们第一次使用常规的git merge\xe2\x80\x94 --no-ff,否则 Git 会用快进而不是合并 \xe2\x80\x94 来作弊,此时我们将得到以下图表:

\n\n
  C___  <-- another-branch\n /    \\\nA---D--E   <-- master (HEAD)\n \\ /\n  B   <--  new-branch\n
Run Code Online (Sandbox Code Playgroud)\n\n

现在,删除名称new-branch和的每个请求another-branch都是“安全的”,因为从提交开始E,Git 可以返回到提交D 和 C。从 开始D,Git 可以返回到提交A 和 B。提交B和C因此位于分支 上master,以及分别位于分支new-branch和上another-branch。删除该名称 是安全的,new-branch因为提交B受到 on 的保护master。删除该名称 是安全的,another-branch因为提交C受到 on 的保护master。

\n\n

提交已成为历史

\n\n

从根本上来说,使用git merge --squash是一种告诉 Git 的方式:我将丢弃一些提交/历史记录。 如果我们有:

\n\n
...--A--B--C--D--E   <-- branch1 (HEAD)\n            \\\n             F--G--H   <-- branch2\n
Run Code Online (Sandbox Code Playgroud)\n\n

然后我们运行git merge --squash branch2,我们将在当前分支上进行新的提交,这是将我们在\xe2\x80\x94上所做的操作与到\ xe2\x80\的差异相branch1结合的结果x94他们在 上做了什么。成功进行新提交后:CEbranch1CHbranch2

\n\n
...--A--B--C--D--E--FGH   <-- branch1 (HEAD)\n            \\\n             F--G--H   <-- branch2\n
Run Code Online (Sandbox Code Playgroud)\n\n

唯一明智的做法branch2就是删除它。Git 不会立即删除它,因为我们可能对其某些提交有其他计划\xe2\x80\x94例如,也许我们想在其他G分支中挑选或H作为新提交\xe2\x80\x94但是最终我们应该消灭它。但是我们新的组合提交不记得提交的哈希值,因此删除该名称将丢失提交、、和;因此要求我们强制删除。FGHHbranch2 FGHgit branch

\n\n

请注意,如果在 之外 还有一些额外的提交H,则可以通过其他名称找到:

\n\n
...--A--B--C--D--E--FGH   <-- branch1 (HEAD)\n            \\\n             F--G--H   <-- branch2\n                    \\\n                     I--J   <-- branch3\n
Run Code Online (Sandbox Code Playgroud)\n\n

这个附加名称将使提交保持J活动状态,并将J保留I哪个保留H哪个保留G哪个保留F。在这里,从\xe2\x80\x94git branch -d branch2仍然会失败,commit 不是提交\xe2\x80\x94 的祖先,但从 运行时会成功。branch1HFGHbranch3

\n\n

随着时间的推移,何时必须强制删除分支名称的精确定义已经发生了一些变化。Git 过去只使用当前提交 ( HEAD) 和分支提示来决定删除是否安全。现在,如果分支有上游设置,它还会考虑分支的上游设置。如果分支提示提交是分支上游提示提交的祖先,但不是当前提交的祖先,Git 现在会删除该分支并发出警告。提交是安全的\xe2\x80\x94它们受到上游名称的保护,至少目前是这样\xe2\x80\x94但是Git不确定你真的打算这样做,所以它会打印一条警告,包括存储在分支名称中的哈希 ID,以允许您使用该哈希 ID 恢复分支名称。

\n


小智 -1

您的分支已成功合并。您收到以下消息 错误:分支“new-branch”未完全合并。 因为代码还没有推送到远程分支。请在 gitbranch -d new-branch 命令正常运行后推送代码,不会出现任何错误。

-d 选项代表 --delete,只有当您已经将本地分支推送并与远程分支合并时,它才会删除本地分支。-D 选项代表 --delete --force,它会删除分支,无论其推送和合并状态如何,所以要小心使用这个选项!