如何轻松修复过去的提交?

Fre*_*abe 100 git rewrite

我刚刚读过修改git过去提交中的单个文件,但不幸的是,接受的解决方案"重新排序"了提交,这不是我想要的.所以这是我的问题:

我偶尔会在处理(无关)功能时发现我的代码中存在错误.然后快速git blame揭示该错误已经被引入了一些提交之前(我提交了很多,所以通常它不是引入该错误的最新提交).此时,我通常这样做:

git stash                      # temporarily put my work aside
git rebase -i <bad_commit>~1   # rebase one step before the bad commit
                               # mark broken commit for editing
vim <affected_sources>         # fix the bug
git add <affected_sources>     # stage fixes
git commit -C <bad_commit>     # commit fixes using same log message as before
git rebase --continue          # base all later changes onto this
Run Code Online (Sandbox Code Playgroud)

然而,这种情况经常发生,上述序列变得烦人.特别是'互动式底板'很无聊.上面的序列是否有任何快捷方式,这可以让我修改过去的任意提交和分阶段的更改?我完全清楚这会改变历史,但我经常犯错误,所以我真的很喜欢这样的事情.

vim <affected_sources>             # fix bug
git add -p <affected_sources>      # Mark my 'fixup' hungs for staging
git fixup <bad_commit>             # amend the specified commit with staged changes,
                                   # rebase any successors of bad commit on rewritten 
                                   # commit.
Run Code Online (Sandbox Code Playgroud)

也许是一个可以使用管道工具重写提交的智能脚本?

Fre*_*abe 149

更新的答案

不久之前,--fixup添加了一个新参数git commit,可用于构造一个适合日志消息的提交git rebase --interactive --autosquash.因此,现在修复过去提交的最简单方法是:

$ git add ...                           # Stage a fix
$ git commit --fixup=a0b1c2d3           # Perform the commit to fix broken a0b1c2d3
$ git rebase -i --autosquash a0b1c2d3~1 # Now merge fixup commit into broken commit
Run Code Online (Sandbox Code Playgroud)

原始答案

这是我前一段时间写的一个Python脚本,它git fixup在我原来的问题中实现了我希望的逻辑.该脚本假定您暂存了一些更改,然后将这些更改应用于给定的提交.

注意:此脚本是特定于Windows的; 它使用查找git.exe和设置GIT_EDITOR环境变量set.根据其他操作系统的需要进行调整.

使用这个脚本,我可以准确地实现我要求的'修复损坏的源,阶段修复,运行git fixup'工作流程:

#!/usr/bin/env python
from subprocess import call
import sys

# Taken from http://stackoverflow.com/questions/377017/test-if-executable-exists-in python
def which(program):
    import os
    def is_exe(fpath):
        return os.path.exists(fpath) and os.access(fpath, os.X_OK)

    fpath, fname = os.path.split(program)
    if fpath:
        if is_exe(program):
            return program
    else:
        for path in os.environ["PATH"].split(os.pathsep):
            exe_file = os.path.join(path, program)
            if is_exe(exe_file):
                return exe_file

    return None

if len(sys.argv) != 2:
    print "Usage: git fixup <commit>"
    sys.exit(1)

git = which("git.exe")
if not git:
    print "git-fixup: failed to locate git executable"
    sys.exit(2)

broken_commit = sys.argv[1]
if call([git, "rev-parse", "--verify", "--quiet", broken_commit]) != 0:
    print "git-fixup: %s is not a valid commit" % broken_commit
    sys.exit(3)

if call([git, "diff", "--staged", "--quiet"]) == 0:
    print "git-fixup: cannot fixup past commit; no fix staged."
    sys.exit(4)

if call([git, "diff", "--quiet"]) != 0:
    print "git-fixup: cannot fixup past commit; working directory must be clean."
    sys.exit(5)

call([git, "commit", "--fixup=" + broken_commit])
call(["set", "GIT_EDITOR=true", "&&", git, "rebase", "-i", "--autosquash", broken_commit + "~1"], shell=True)
Run Code Online (Sandbox Code Playgroud)

  • 你可以在你的rebase周围使用`git stash`和`git stash pop`,不再需要一个干净的工作目录 (2认同)
  • @TobiasKienzler关于在 rebase 周围使用 `git stash` 和 `git stash pop` :现在有一个用于这个 `--autostash` 的标志 [[参见 git 文档](https://git-scm.com/docs/git -rebase#Documentation/git-rebase.txt---autostash)] (2认同)

Kri*_*ins 29

我所做的是:

git add ...           # Add the fix.
git commit            # Committed, but in the wrong place.
git rebase -i HEAD~5  # Examine the last 5 commits for rebasing.

您的编辑器将打开最近5次提交的列表,随时可以插入.更改:

pick 08e833c Good change 1.
pick 9134ac9 Good change 2.
pick 5adda55 Bad change!
pick 400bce4 Good change 3.
pick 2bc82n1 Fix of bad change.

...至:

pick 08e833c Good change 1.
pick 9134ac9 Good change 2.
pick 5adda55 Bad change!
f 2bc82n1 Fix of bad change. # Move up, and change 'pick' to 'f' for 'fixup'.
pick 400bce4 Good change 3.

保存并退出编辑器,修复程序将被压缩回它所属的提交.

在你做了几次之后,你会在睡眠中在几秒钟内完成.交互式变基是真正让我用git卖给我的功能.它对这个以及更多......非常有用

  • 显然你可以将HEAD~5更改为HEAD~n以进一步返回.你不会想干涉你上游的任何历史记录,所以我通常输入'git rebase -i origin/master'来确保我只改变未按下的历史记录. (8认同)
  • 这很像我一直以来做的事情; FWIW,您可能对`git rebase`的`--autosquash`开关感兴趣,它会自动为您重新编写编辑器中的步骤.请参阅我对脚本的响应,该脚本利用它来实现`git fixup`命令. (4认同)

dsc*_*ter 20

派对有点晚了,但这里有一个解决方案,正如作者想象的那样.

将其添加到.gitconfig:

[alias]
    fixup = "!sh -c '(git diff-files --quiet || (echo Unstaged changes, please commit or stash with --keep-index; exit 1)) && COMMIT=$(git rev-parse $1) && git commit --fixup=$COMMIT && git rebase -i --autosquash $COMMIT~1' -"
Run Code Online (Sandbox Code Playgroud)

用法示例:

git add -p
git fixup HEAD~5
Run Code Online (Sandbox Code Playgroud)

但是,如果您有未分级的更改,则必须在rebase之前存储它们.

git add -p
git stash --keep-index
git fixup HEAD~5
git stash pop
Run Code Online (Sandbox Code Playgroud)

您可以自动修改别名以隐藏,而不是发出警告.但是,如果修复不能完全应用,则需要在修复冲突后手动弹出存储.手动执行保存和弹出似乎更一致,更少混淆.

  • 修改的问题是我不记得该如何拼写。我总是想,是修改还是修改,这不好。 (3认同)

Sér*_*gio 9

要修正一个提交:

git commit --fixup a0b1c2d3 .
git rebase --autosquash -i HEAD~2
Run Code Online (Sandbox Code Playgroud)

其中a0b1c2d3是要修复的提交,而2是要更改的+1粘贴的提交数。

注意:不带-i的git rebase --autosquash不起作用,但带-i则起作用,这很奇怪。

  • 正如手册页所述:*此选项仅在使用 --interactive 选项时有效。*但是有一个简单的方法可以跳过编辑器:`EDITOR=true git rebase --autosquash -i` (3认同)

Dei*_*win 6

更新:现在可以在这里找到更清晰的脚本版本:https://github.com/deiwin/git-dotfiles/blob/docs/bin/git-fixup.

我一直在寻找类似的东西.但是,这个Python脚本似乎太复杂了,因此我将自己的解决方案拼凑在一起:

首先,我的git别名看起来像那样(从这里借来):

[alias]
  fixup = !sh -c 'git commit --fixup=$1' -
  squash = !sh -c 'git commit --squash=$1' -
  ri = rebase --interactive --autosquash
Run Code Online (Sandbox Code Playgroud)

现在bash函数变得非常简单:

function gf {
  if [ $# -eq 1 ]
  then
    if [[ "$1" == HEAD* ]]
    then
      git add -A; git fixup $1; git ri $1~2
    else
      git add -A; git fixup $1; git ri $1~1
    fi
  else
    echo "Usage: gf <commit-ref> "
  fi
}
Run Code Online (Sandbox Code Playgroud)

此代码首先显示所有当前更改(如果您希望自己暂存文件,则可以删除此部分).然后创建fixup(也可以使用squash,如果这是你需要的)提交.之后,它会启动一个交互式rebase,并--autosquash在您提供的提交的父级作为参数.这将打开您配置的文本编辑器,因此您可以验证所有内容是否符合预期,只需关闭编辑器即可完成此过程.

使用if [[ "$1" == HEAD* ]]部分(从这里借用),因为如果你使用HEAD~2作为你的提交(你想要修改当前更改的提交)引用,那么在创建fixup commit之后HEAD将被替换你需要使用HEAD~3来引用同一个提交.


小智 6

修复工作流程真正困扰我的是,我必须自己弄清楚每次我想将更改压缩到哪个提交中。我创建了一个“git fixup”命令来帮助解决这个问题。

此命令创建修复提交,并使用git-deps自动查找相关提交,因此工作流程通常归结为:

# discover and fix typo in a previously committed change
git add -p # stage only typo fix
git fixup

# at some later point squash all the fixup commits that came up
git rebase --autosquash master
Run Code Online (Sandbox Code Playgroud)

仅当分阶段更改可以明确归因于工作树(master 和 HEAD 之间)上的特定提交时,这才有效。我发现这种情况经常发生在我使用它进行的小更改类型中,例如注释中的拼写错误或新引入(或重命名)的方法的名称。如果不是这种情况,它至少会显示候选提交的列表。

我在日常工作流程中经常使用它,以快速将对先前更改的行进行小的更改集成到我的工作分支上的提交中。该脚本并不像它应有的那么漂亮,而且它是用 zsh 编写的,但它已经为我完成了很长一段时间的工作,现在我从来没有觉得需要重写它:

https://github.com/Valodim/git-fixup


Pet*_*ski 6

我推荐https://github.com/tummychow/git-absorb

电梯推介

您有一个包含一些提交的功能分支。您的队友审查了该分支并指出了一些错误。您已经修复了错误,但您不想将它们全部推入一个显示修复的不透明提交中,因为您相信原子提交。不要手动查找 的提交 SHA git commit --fixup,或运行手动交互式变基,而是执行以下操作:

  • git add $FILES_YOU_FIXED

  • git absorb --and-rebase

  • 或者:git rebase -i --autosquash master

git absorb将自动识别哪些提交可以安全修改,以及哪些索引更改属于每个提交。然后它会写fixup!承诺每一项更改。如果您不信任它,您可以手动检查其输出,然后使用 git 的内置 autosquash 功能将修复程序折叠到您的功能分支中。


sep*_*ehr 6

这是基于已接受答案的git 别名,其工作原理如下:

git fixup          # fixup staged & unstaged changes into the last commit
git fixup ac1dc0d3 # fixup staged & unstaged changes into the given commit
Run Code Online (Sandbox Code Playgroud)

更新您的~/.gitconfig文件并添加此别名:

[alias]
    fixup = "!git add . && git commit --fixup=${1:-$(git rev-parse HEAD)} && GIT_EDITOR=true git rebase --interactive --autosquash ${1:-$(git rev-parse HEAD~2)}~1"
Run Code Online (Sandbox Code Playgroud)