Git - 合并整个分支 - 一行命令合并

28

在编写新代码时,我会进行许多小的提交以跟踪我的更改。然而,我的公司更喜欢将每个功能作为单个提交提交。因此,解决方案是将整个(本地)分支压缩为单个提交。

如何在不使用 git rebase --interactive 并更改所有提交的 picksquash 的情况下将整个分支压缩?


5
也许可以尝试使用 git merge --squash,这样就不需要事先将整个分支压缩成一个提交了。 - twalberg
@twalberg,请把那个变成答案。 - jthill
1
如果您使用Github拉取请求来进行大多数分支合并,则git merge --squash无法正常工作,而这是非常常见的情况。 - user229044
3
@meagar 即使如此,只需稍加努力即可使其正常工作。特别是为集成而设的第二个分支。您可以在dev分支上完成所有工作,然后在integration分支上执行git --merge squash,并将integration用作拉取请求的源。 - twalberg
我能想到的最好的方法是先使用 git reset --soft,然后再使用 git commit - Alexey
显示剩余2条评论
8个回答

19

这是一个完美的应用场景,可以使用 git reset --soft 命令。

假设您有一个提交历史记录

D   Your latest patch
C   Your second patch
B   Your first patch
A   Someone else's work

你当前没有暂存的更改,而且 git statusgit loggit show 命令告诉你现在所在的提交是 D。

接着,执行 git reset --soft B 命令将把提交 CD 的累积更改暂存起来以备提交。然后,执行 git commit --amend 命令将这些更改“合并”到提交 B 中。

使用方法如下:

git reset --soft B
git commit --amend
第二个命令将打开您的编辑器并有机会编辑提交消息。
请注意,如果在开始此过程之前已经暂存了更改(即您已经执行了 git add XXX 但没有跟进 git commit ),则这些暂存的更改也将合并到提交中。

2
我认为这是一个非常被低估的答案,不仅对于合并操作有用,而且对其他方面也很有用。 - Rob Cannon
1
如果你这样做,你将无法再从远程拉取或推送。你会收到错误信息:fatal: refusing to merge unrelated histories - Chris Moschini
@ChrisMoschini,这个错误只会在用户操作已经被推送的补丁时发生。OP询问的是如何操作本地补丁(那些还没有被推送的)。在我的例子中,补丁B、C和D仅为本地补丁。补丁A代表服务器上分支的HEAD。补丁B、C和D被合并成一个单独的补丁。 - ianinini

18
我的首选方法是两行代码(不包括下面的步骤1和4)。好处是你不需要知道/记录任何提交ID,你可以编写一个简单的别名来执行所有涉及的步骤,并且你实际上将整个分支移动到origin/master,这样合并到master的实际过程可以是快进的,不会有任何冲突。
首先,我的假设是:
- 你正在一个名为my-feature-branch的分支上工作。该分支已经与master分支分叉了几个提交;这是当前检出的分支。 - 你的本地master跟踪远程分支origin/master。 - 你想要将my-feature-branch上的所有提交压缩成一个单独的提交,放在当前状态的origin/master之上(而不是你的本地master,可能已经过时)。 - 你的所有更改都已经提交,没有未暂存的更改(它们将在git reset --hard期间丢失)。
我的步骤如下:
  1. 获取最新的 origin/master 分支:

    $ git fetch
    
  2. 通过将本地分支重置到 origin/master 来丢弃所有提交:

    $ git reset --mixed origin/master
    
  3. 将之前分支状态下的所有更改合并到索引中:

    $ git merge --squash HEAD@{1}
    
  4. 提交您的更改 - Git 将使用包含所有合并提交消息的提交消息预填充您的编辑器

我提到的简单别名是:
alias squash="git fetch; git reset --mixed origin/master; git merge --squash HEAD@{1}"

1
为什么使用 git reset --mixed 而不是 --hard?在我的情况中,混合重置保留了工作副本中获取的所有文件,但合并步骤失败了。 - Mariano Desanze

9
可能最好的选择是在合并时使用git merge --squash。这将保留您的分支开发历史记录,这通常更容易排除故障,因为您会知道“我正在提交Z中更改特定功能”,并且查看该特定提交,您可以了解到您对多个文件所做的任何更改的所有上下文 - 查看单个提交,它是您开发路径压缩结果,使得记住“哦,是的,我还必须在另一个文件中更改这一点...”变得更加困难。此外,当您有整个路径可用时,您还可以使用git bisect获得好处 - 在压缩情况下,它只能告诉您“这个巨大的提交在这里破坏了某些东西”。
使用git merge --squash的结果是在您“合并”的分支上的单个提交,其中包含来自您的分支的累积更改,但它不会影响您的原始分支。

8
找到你开始分支之前的提交哈希值,并将其复制到剪贴板中。然后进行重置,回到那个哈希值。
$ git reset --soft [hash]

然后只需在一条消息中重新添加和提交更改。
$ git add -A
$ git commit -m 'EVERYTHING SQUASHED'

2
如果您想进行软重置,需要显式使用 --soft 标志 (git reset --soft <commit>),因为 Git 默认执行混合重置。 - jub0bs
只需一个默认的混合重置即可实现所需的压缩。 - onlythefinestwilldo
3
好的,但是你写道:“对那个哈希值进行软重置”,这有误导性。 - jub0bs

3

编辑你的 git 配置文件 ~/.gitconfig 并在别名部分添加以下内容:

[alias]
    squash = "!f(){ CUR=`git rev-parse HEAD` && git reset --soft ${1} && git commit -m \"$(git log --format=%B ${1}..${CUR})\"; };f"

这个别名获取当前的 HEAD 提交哈希值,重置回到您指定的提交,并创建一个新的提交,保留所有提交信息。

用法:

git squash <refspec>

refspec可以是任何有效的提交引用,例如提交哈希值、分支名称、标签名称、HEAD^HEAD~3


0

我认为这不是这个问题的正确答案。

但是在向远程上游主分支发出拉取请求之前,我通常会使用以下命令压缩我的分支:

git rebase -i master

但是你仍然需要选择哪一个要挑选和压缩。


0

从主分支的原始点简化

我喜欢的压缩方式是通过重新分组提交,而不需要变基,这样我之后只需变基即可。

# Squash current branch from its departure point in main branch. You must run
# this command when on your branch's last commit.
git-squash() {
    # https://dev59.com/32Ml5IYBdhLWcg3wbGh1#48524405
    local main_commit="$(git merge-base HEAD main)"
    local last_branch_commit="$(git rev-parse HEAD)"
    git reset --soft "$main_commit"
    # Preserve commits
    git commit -em "$(git log --reverse --format=%B ${main_commit}..${last_branch_commit})"
}

源代码(以及在dotfiles中的集成)。受troymass的另一个答案启发。

当然,现在你可以使用git rebase main进行变基了。

注意:如果您是zsh用户,可以在此处将main替换为git_main_branch。这将选择主分支(main)或主干(master)。


0
最好的做法是进行硬重置并将先前的 HEAD 合并为 squash。这是一个别名:
[alias]
  squash = "!f() { git reset --hard $1; git merge --squash HEAD@{1}; git commit; }; f"

这样你就可以这样调用它:

git squash master

或者从另一个分支(如dev)合并:

git squash dev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接