【问题标题】:Trimming Git Commits/Squashing Git History修剪 Git 提交/压缩 Git 历史
【发布时间】:2011-01-19 03:39:21
【问题描述】:

我每隔几分钟左右将我的代码检查到 Git 分支中,而 cmets 最终会变成“一切都坏了重新开始”和其他荒谬的东西。

然后每隔几分钟/几小时/几天,我都会做一个认真的提交,并给出一个真实的评论,比如“修复了错误 #22.55,第三次”。 我如何区分这两个概念?我希望能够删除我所有的频繁提交并留下严重的。

【问题讨论】:

    标签: git branch


    【解决方案1】:

    使用 git rebase -i 来挑选和压缩你的提交。

    【讨论】:

    • 所以如果我有两个提交,42636015569ef315059d52df87740 我怎样才能消除这两个? git rebase -i 只是吐出帮助。谢谢你的回答。
    【解决方案2】:

    编辑答案现在(在本条目的后半部分)新的 Git1.7 修复! action 和 --autosquash 选项用于快速提交重新排序和消息编辑。


    首先,经典的挤压过程,就像 Git1.7 之前所做的那样。
    (Git1.7 具有相同的过程,只是通过自动提交重新排序而不是手动重新排序的可能性以及更清晰的压缩消息使速度更快)

    我希望能够删除我所有的频繁签入并留下严重的签入。

    这称为压缩提交
    在这篇Git ready 文章中,您有一些“提交清理”的好例子:
    (注意:rebase 交互功能came along since September 2007,并允许压缩或拆分或删除或重新排序提交:另见GitPro page

    注意事项:仅对尚未推送到外部存储库的提交执行此操作。如果其他人基于您要删除的提交进行工作,则可能会发生大量冲突。如果您的历史已与他人共享,请不要重写。

    最后 4 次提交如果被打包在一起会更快乐

    $ git rebase -i HEAD~4
    
    pick 01d1124 Adding license
    pick 6340aaa Moving license into its own file
    pick ebfd367 Jekyll has become self-aware.
    pick 30e0ccb Changed the tagline in the binary, too.
    
    # Rebase 60709da..30e0ccb onto 60709da
    #
    # Commands:
    #  p, pick = use commit
    #  e, edit = use commit, but stop for amending
    #  s, squash = use commit, but meld into previous commit
    #
    # If you remove a line here THAT COMMIT WILL BE LOST.
    # However, if you remove everything, the rebase will be aborted.
    #
    

    使用HEADHEAD~4 所在位置的最后四个提交进行变基
    我们只是将所有内容压缩到一个提交中。
    所以,把文件的前四行改成这样就行了:

    pick 01d1124 Adding license
    squash 6340aaa Moving license into its own file
    squash ebfd367 Jekyll has become self-aware.
    squash 30e0ccb Changed the tagline in the binary, too.
    

    基本上,这告诉 Git 将所有四个提交合并到列表中的第一个提交中。完成并保存后,会弹出另一个编辑器,其中包含以下内容:

    # This is a combination of 4 commits.
    # The first commit's message is:
    Adding license
    
    # This is the 2nd commit message:
    
    Moving license into its own file
    
    # This is the 3rd commit message:
    
    Jekyll has become self-aware.
    
    # This is the 4th commit message:
    
    Changed the tagline in the binary, too.
    
    # Please enter the commit message for your changes. Lines starting
    # with '#' will be ignored, and an empty message aborts the commit.
    # Explicit paths specified without -i nor -o; assuming --only paths...
    # Not currently on any branch.
    # Changes to be committed:
    #   (use "git reset HEAD <file>..." to unstage)
    #
    #   new file:   LICENSE
    #   modified:   README.textile
    #   modified:   Rakefile
    #   modified:   bin/jekyll
    #
    

    由于我们合并了如此多的提交,Git 允许您根据流程中涉及的其余提交修改新提交的消息。根据需要编辑消息,然后保存并退出。
    完成后,您的提交已成功压缩!

    Created commit 0fc4eea: Creating license file, and making jekyll self-aware.
     4 files changed, 27 insertions(+), 30 deletions(-)
      create mode 100644 LICENSE
        Successfully rebased and updated refs/heads/master.
    

    如果我们再看一遍历史……


    注意:出于“commit squashing”的目的,Git1.7(2010 年 2 月)引入了 2 个新元素(如评论中 Dustin 所述):

    • git rebase -i”学到了新的操作“fixup”,它可以压制更改但不影响现有的日志消息。
    • git rebase -i”还学习了--autosquash 选项,该选项与新的“修复”操作一起使用。

    这两个(修复操作和--autosquash 选项)都在此Thechnosorcery Networks blog entry 中进行了说明。这些功能自 last June 2009 以来一直在烹饪,并在 last December 进一步讨论。

    fixup 操作或指令用于压缩您在rebase --interactive 的提交编辑列表中手动重新排序的提交,同时忽略第二条提交消息,这将使消息编辑步骤更快(您可以保存它:压缩的提交将只有 first 提交消息)
    生成的提交消息只会是第一个提交消息。

      # s, squash = use commit, but meld into previous commit
      # f, fixup = like "squash", but discard this commit's log message
    

    --autosquash 选项是关于自动为您进行提交重新排序过程:

    如果您知道要将某些内容压缩到哪个提交中,可以使用“squash! $other_commit_subject”消息进行提交。然后,如果你运行@git rebase --interactive --autosquash commitish@,该行将自动设置为 squash,并置于主题为 $other_commit_subject 的提交下方。

    (其实squash!只能使用另一个commit message的开始

    $ vim Foo.txt
    $ git commit -am "Change all the 'Bar's to 'Foo's"
    [topic 8374d8e] Change all the 'Bar's to 'Foo's
     1 files changed, 2 insertions(+), 2 deletions(-)
    $ vim Bar.txt
    $ git commit -am "Change all the 'Foo's to 'Bar's"
    [topic 2d12ce8] Change all the 'Foo's to 'Bar's
     1 files changed, 1 insertions(+), 1 deletions(-)
    
    $ vim Foo.txt
    $ git commit -am "squash! Change all the 'Bar's"
    [topic 259a7e6] squash! Change all the 'Bar's
     1 files changed, 2 insertions(+), 1 deletions(-)
    

    看到了吗?这里的 third 提交只使用了 first 提交消息的开头。
    rebase --interactive --autosquash 会将压扁的提交移到相关提交的下方:

    pick 8374d8e Change all the 'Bar's to 'Foo's
    squash 259a7e6 squash! Change all the 'Bar's
    pick 2d12ce8 Change all the 'Foo's to 'Bar's
    

    消息版本是:

    # This is a combination of 2 commits.
    # The first commit's message is:
    
    Change all the 'Bar's to 'Foo's
    
    # This is the 2nd commit message:
    
    squash! Change all the 'Bar's
    

    意味着默认情况下,您会将压缩操作记录在提交消息中。
    但随着修复!指令,您可以在提交消息中保持该挤压“不可见”,同时仍然受益于使用 --autosquash 选项的自动提交重新排序(并且您的第二个提交消息基于您想要被挤压的第一个提交这一事实)。

    pick 8374d8e Change all the 'Bar's to 'Foo's
    fixup cfc6e54 fixup! Change all the 'Bar's
    pick 2d12ce8 Change all the 'Foo's to 'Bar's
    

    默认的消息是:

    # This is a combination of 2 commits.
    # The first commit's message is:
    
    Change all the 'Bar's to 'Foo's
    
    # The 2nd commit message will be skipped:
    
    #    fixup! Change all the 'Bar's
    

    请注意,fixup! 提交的消息已被注释掉。
    您可以按原样保存该消息,您的原始提交消息将被保留
    当您意识到自己忘记添加早期提交的一部分时,非常方便地包含更改。

    现在,如果您想根据您刚刚所做的上一次提交进行修复或压缩,Jacob Helwig(Technosorcery Networks 博客条目的作者)推荐以下别名:

    [alias]
        fixup = !sh -c 'git commit -m \"fixup! $(git log -1 --format='\\''%s'\\'' $@)\"' -
        squash = !sh -c 'git commit -m \"squash! $(git log -1 --format='\\''%s'\\'' $@)\"' -
    

    对于进行变基交互,这将始终受益于要被压缩的提交的自动重新排序:

    [alias]
        ri = rebase --interactive --autosquash
    

    Git 2.18 更新(2018 年第二季度):“git rebase -i”有时会留下中间“# This is a combination of N commits”消息,供人类使用 在某些极端情况下,在最终结果中的编辑器内,其中 已修复。

    参见Johannes Schindelin (dscho)commit 15ef693commit dc4b5bccommit e12a7efcommit d5bc6f2(2018 年 4 月 27 日)。
    (由 Junio C Hamano -- gitster -- 合并到 commit 4a3bf32,2018 年 5 月 23 日)

    rebase --skip:在修复/壁球失败后清理提交消息

    在一系列 fixup/squash 命令期间,交互式 rebase 构建 使用 cmets 提交提交消息。这将呈现给用户 如果这些命令中至少有一个是squash,则编辑器。

    无论如何,提交消息最​​终都会被清理,删除 所有这些中间 cmet,在这样的修复/壁球链的最后一步。

    但是,如果此类链中的最后一个 fixup/squash 命令失败并显示 合并冲突,如果用户决定跳过它(或解决它 到一个干净的工作树,然后继续变基),当前代码 无法清理提交消息。

    此提交修复了该行为。

    修复比看上去要复杂得多,因为它是 不仅是关于我们是否git rebase --skiping 的问题 修复或壁球。这也是关于删除跳过的修复/壁球的 来自累积的提交消息的提交消息。这也是关于 我们是否应该让用户编辑最终提交的问题 消息与否(“链中是否有一个壁球不是 跳过?”)。

    例如,在这种情况下,我们将要修复提交消息,但是 不要在编辑器中打开它:

    pick  <- succeeds
    fixup   <- succeeds
    squash  <- fails, will be skipped
    

    这是新引入的current-fixups 文件真正出现的地方 便利。快速浏览一下,我们可以确定是否有未跳过的 壁球。我们只需要确保在以下方面保持最新 跳过修复/壁球命令。作为奖励,我们甚至可以避免犯 不必要的,例如当只有一个修复,它失败了,并且 被跳过了。

    仅修复最终提交消息未清理的错误 正确,但不修复其余部分,会更复杂 而不是一次性修复它,因此这个提交比 一个问题。


    Git 2.19(2018 年第三季度)修复了一个错误:当“git rebase -i”被告知将两个或多个提交压缩为一个时,它会用其编号标记每个提交的日志消息。
    它正确地将第一个称为“第一次提交”,但下一个是“commit #1”,它是逐个(!)。

    参见Phillip Wood (phillipwood)commit dd2e36e(2018 年 8 月 15 日)。
    (由 Junio C Hamano -- gitster -- 合并于 commit 36fd1e8,2018 年 8 月 20 日)

    rebase -i: 修复壁球消息中的编号

    提交e12a7ef ("rebase -i: 处理"&lt;n&gt; 提交的组合" with GETTEXT_POISON", 2018-04-27, Git 2.18) 改变了个人提交的方式 将提交压缩在一起时会标记消息。
    在这样做的过程中,引入了回归,其中消息的编号减一。此提交修复了该问题并添加了编号测试。

    【讨论】:

    • 请注意,git 1.7 添加了“fixup”,它可以在不编辑所有提交消息的情况下进行压缩。
    • @Dustin:好点。我编辑了我的答案以反映和解释这两个新元素:fixup! action 和 --autosquash option。
    • @VonC 感谢您为此工作。答案现在太大了,IMO。这个周末我也试过了(仅限第一部分),但对我来说没有用。原因是我遇到了“自动挑选樱桃失败”。有什么简单的方法可以在不重新访问我的代码的情况下解决这个问题?
    • @yar:有一个与压缩有关的错误 (mail-archive.com/debian-bugs-dist@lists.debian.org/…),但如果您使用的是最新版本的 Git,则已修复。否则,这似乎与合并冲突有关。参见例如blog.robseaman.com/2009/1/15/upgrading-to-mephisto-0-8-1,它说明了该消息弹出的一些合并场景。
    • @VonC,我提出了自己的答案,它采用了不同的非 GIT 方法。谢谢!
    【解决方案3】:

    使用软重置而不是变基来压缩 GIT 历史记录

    我认为 VonC 的回答长度足以说明git rebase 的复杂程度。这是我对another answer to a question of mine 的扩展。

    1. 您有一个分支 ticket-201,它是从 master 分支出来的。您想假装来自 ticket-201 的所有提交从未发生过,而是您一口气完成了所有工作。
    2. 使用git reset --soft hash 软重置到分支点,其中hash 应该是ticket-201 日志中的提交哈希。
    3. 使用 add then commit 提交您的更改。现在分支历史将只有第一次提交和带有新内容的新提交。

    从不同分支的任意提交中编造历史

    使用重置,您可以根据需要重写历史记录,但您的编辑将失去拥有正确时间戳的魅力。假设您不关心这一点(文件上的时间/日期可能就足够了?),或者如果您想随时修改提交,您可以按照以下步骤操作:

    1. commit0 签出一个新分支(假设这是一个哈希):git checkout -b new-history commit0
    2. 现在您可以从commit5 获取文件:git reset --hard commit5
    3. 切换回您的索引点:git reset --soft commit0
    4. 提交,这将是分支中的第二次提交。

    这个想法简单、有效、灵活。

    【讨论】:

    • @yar:有趣的反馈。 +1
    • @yar:有趣的更新:soft reset:我应该考虑一下。
    • @yar: 这样你就会留下悬空提交,这些提交将在未来git gc 期间被清除。
    • @Yar - 什么时候需要git push --force?我建议的工作流程总是为“问题”创建新分支。工作,承诺,承诺,承诺。完成后,git pull --rebase master 然后git reset --soft 然后git commit,然后是结帐大师和git merge branch。希望通过这种流程,我将保持历史整洁,并且永远不会损害任何“公共提交”。有什么想法或警告吗?
    • @Terry,如果你在服务器上的一个分支中并且你变基(我建议的方式,或者其他方式)你将不得不做--force。但是,您是对的:如果您不在其他人正在使用的分支中,则不会有任何问题。如果您的分支不在服务器上,而您只是重置 --soft 等,然后合并到服务器上的分支,您将没有问题。总之,你很好理解这一点:)
    【解决方案4】:

    改用 Squash

    最近,我一直在另一个分支工作并使用squash。另一个分支称为 temp,然后我使用git merge temp --squash 将其带入真正的分支,然后推送到服务器。

    工作流程是这样的,假设我在Ticket65252工作:

    git branch -d temp #remove old temp bbranch
    git checkout -b temp
    # work work work, committing all the way
    git checkout Ticket65252
    git merge temp --squash
    git commit -m "Some message here"
    

    与使用rebase相比的优势?方式不那么复杂。

    比使用reset --hardreset --soft 的优势?更少的混乱和更不容易出错。

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-04-25
    • 1970-01-01
    • 1970-01-01
    • 2014-09-13
    • 2016-03-11
    • 1970-01-01
    • 1970-01-01
    • 2012-11-20
    相关资源
    最近更新 更多