我怎样才能轻松修复过去的提交?
我只是读过过去在git中修改一个文件,但不幸的是接受的解决scheme“重新sorting”提交,这不是我想要的。 所以这是我的问题:
我偶尔会注意到在处理一个(不相关的)特性的时候,我的代码中存在一个错误。 一个快速的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
然而,这种情况经常发生,以至于上述顺序变得烦人。 特别是“互动式底牌”是无聊的。 上述顺序有没有什么捷径可以让我修改过去随意进行的任意提交? 我完全知道这改变了历史,但是我经常犯错误,所以我真的很喜欢这样的事情
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.
也许一个聪明的脚本,可以重写使用pipe道工具提交提交?
更新的答案
前一阵子,一个新的--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
原来的答案
这里有一个我刚刚写的一个Python脚本,它实现了我希望在我原来的问题中使用的这个git fixup
逻辑。 该脚本假定您执行了一些更改,然后将这些更改应用于给定的提交。
注 :该脚本是Windows特定的; 它会查找git.exe
并使用set
GIT_EDITOR
环境variables。 根据其他操作系统的需要进行调整。
使用这个脚本,我可以精确地实现我所要求的“修复损坏的来源,阶段修复,运行git fixup”工作stream程:
#!/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)
我所做的是:
git add ...#添加修正。 git commit#提交,但在错误的地方。 git rebase -i HEAD〜5#检查最近5次提交重build的提交。
您的编辑器将打开最后5个提交列表,随时可以进行操作。 更改:
挑08e833c好改1。 挑9134ac9好改2。 挑5adda55坏的变化! 选400bce4好改3。 select2bc82n1修复不好的变化。
…至:
挑08e833c好改1。 挑9134ac9好改2。 挑5adda55坏的变化! f 2bc82n1修复不好的变化。 #向上移动,并将“选取”更改为“修正”的“f”。 选400bce4好改3。
保存并退出您的编辑器,修复程序将被压回到它所属的提交中。
做了几次之后,你可以在睡梦中几秒钟完成。 互动重新绑定是真正在git上销售我的function。 这对于这个和更多…是非常有用的
晚会有点晚了,但是这是一个按照作者想象的方式的解决scheme。
添加到您的.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' -"
用法示例:
git add -p git fixup HEAD~5
但是,如果您有未分期更改,则必须在分期付款之前将其冻结。
git add -p git stash --keep-index git fixup HEAD~5 git stash pop
您可以修改别名以自动保存,而不是发出警告。 但是,如果修复程序不能干净地应用,则需要在修复冲突之后手动popup存储。 手动执行保存和popup似乎更加一致并且不那么令人困惑。
更新:现在可以在这里find一个清洁版本的脚本: https : //github.com/deiwin/git-dotfiles/blob/docs/bin/git-fixup 。
我一直在寻找类似的东西。 这个Python脚本看起来太复杂了,所以我把自己的解决scheme拼凑在一起:
首先,我的git别名看起来像(从这里借来的):
[alias] fixup = !sh -c 'git commit --fixup=$1' - squash = !sh -c 'git commit --squash=$1' - ri = rebase --interactive --autosquash
现在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 }
这段代码首先将所有当前的变化分解(如果你想自己分析文件,你可以删除这部分)。 然后创buildfixup(squash也可以使用,如果这是你所需要的)提交。 之后,它会以您提供的参数的父--autosquash
上的--autosquash
标志开始一个交互式--autosquash
。 这将打开你configuration的文本编辑器,所以你可以validation一切都如你所料,只需closures编辑器将完成该过程。
使用if [[ "$1" == HEAD* ]]
部分(从这里借用),因为如果使用HEAD〜2作为提交(您想要修改当前更改的提交)在创build修复提交之后HEAD将被移位,并且您将需要使用HEAD〜3来引用相同的提交。
修正一个提交:
git commit --fixup a0b1c2d3 . git rebase --autosquash -i
其中0b1c2d3是你想修复的提交。
注意:git rebase –autosquash没有-i不起作用,但用-i工作,这很奇怪。
您可以通过使用“空”编辑器来避免交互式阶段:
$ EDITOR=true git rebase --autosquash -i ...
这将使用/bin/true
作为编辑器,而不是/usr/bin/vim
。 它总是接受任何build议,没有提示。
对于修复工作stream程,真正困扰我的是我必须弄清楚自己每次想要把变化压缩到什么地步。 我创build了一个“git fixup”命令来帮助解决这个问题。
这个命令创build了fixup提交,增加了使用git-deps自动查找相关提交的魔法,所以工作stream程通常归结为:
# 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
这只有在分阶段更改可以明确归因于工作树上的特定提交(主控与HEAD之间)时才有效。 我发现这种情况经常发生在我使用这种小改动的情况下,例如注释中的拼写错误或新引入(或重命名)方法的名称。 如果不是这样,它至less会显示候选提交列表。
我在日常工作stream程中使用了很多 ,可以快速地将以前更改过的行中的小改动集成到工作分支上的提交中。 这个脚本不够美观,而且是用zsh编写的,但是现在已经为我工作了很长一段时间了,所以我从来没有觉得有必要重写它:
commit --fixup
和rebase --autosquash
是伟大的,但他们做得不够。 当我有一系列的提交ABC
并且在我的工作树中写入了一些属于一个或多个现有提交的更改时,我必须手动查看历史logging,确定哪些更改属于哪些提交,将它们放置并创buildfixup!
提交。 但是git已经可以获得足够的信息来完成所有这些工作,所以我写了一个Perl脚本来完成这个工作。
对于git diff
的每个大块,脚本使用git blame
来查找最后一次触及相关行的git commit --fixup
,并调用git commit --fixup
来编写适当的fixup!
提交,实质上是做我以前手动做的同样的事情。
如果你觉得它有用,请随时改进和迭代它,也许有一天,我们会得到这样的function在适当的git
。 我很乐意看到一个工具,它可以理解合并冲突如何通过交互式底图引入来解决。
我没有意识到自动化的方式,但是这里有一个解决scheme,可能更容易让人类变得僵化:
git stash # write the patch git add -p <file> git commit -m"whatever" # message doesn't matter, will be replaced via 'fixup' git rebase -i <bad-commit-id>~1 # now cut&paste the "whatever" line from the bottom to the second line # (ie below <bad-commit>) and change its 'pick' into 'fixup' # -> the fix commit will be merged into the <bad-commit> without changing the # commit message git stash pop
您可以使用此别名为特定文件创build修补程序。
[alias] ... # fixup for a file, using the commit where it was last modified fixup-file = "!sh -c '\ [ $(git diff --numstat $1 | wc -l) -eq 1 ] && git add $1 && \ [ $(git diff --cached --numstat $1 | wc -l) -eq 1 ] || (echo No changes staged. ; exit 1) && \ COMMIT=$(git log -n 1 --pretty=format:"%H" $1) && \ git commit --fixup=$COMMIT && \ git rebase -i --autosquash $COMMIT~1' -"
如果你在myfile.txt
做了一些修改,但是你不想把它们放在一个新的提交中,那么git fixup-file myfile.txt
将会创build一个fixup!
对于myfile.txt
最后被修改的提交,然后它会rebase --autosquash
。
我使用一个小小的shell函数将提交和修复自动化到一个命令中:
$ git add -p *select hunks* $ gcf <earlier_commit_id> *get back to coding*
它会立即进行重新分配,如果有必要的话,将会进行任何无法改变的变动。
你可以在这里得到gcf
。