我刚刚读过修改git过去提交中的单个文件,但不幸的是,接受的解决方案"重新排序"了提交,这不是我想要的.所以这是我的问题:
我偶尔会在处理(无关)功能时发现我的代码中存在错误.然后快速git blame
揭示该错误已经被引入了一些提交之前(我提交了很多,所以通常它不是引入该错误的最新提交).此时,我通常这样做:
git stash # temporarily put my work aside git rebase -i~1 # rebase one step before the bad commit # mark broken commit for editing vim # fix the bug git add # stage fixes git commit -C # commit fixes using same log message as before git rebase --continue # base all later changes onto this
然而,这种情况经常发生,上述序列变得烦人.特别是'互动式底板'很无聊.上面的序列是否有任何快捷方式,这可以让我修改过去的任意提交和分阶段的更改?我完全清楚这会改变历史,但我经常犯错误,所以我真的很喜欢这样的事情.
vim# fix bug git add -p # Mark my 'fixup' hungs for staging git fixup # amend the specified commit with staged changes, # rebase any successors of bad commit on rewritten # commit.
也许是一个可以使用管道工具重写提交的智能脚本?
不久之前,--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
和设置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" 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 ... # 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卖给我的功能.它对这个以及更多......非常有用
派对有点晚了,但这里有一个解决方案,正如作者想象的那样.
将其添加到.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
但是,如果您有未分级的更改,则必须在rebase之前存储它们.
git add -p git stash --keep-index git fixup HEAD~5 git stash pop
您可以自动修改别名以隐藏,而不是发出警告.但是,如果修复不能完全应用,则需要在修复冲突后手动弹出存储.手动执行保存和弹出似乎更一致,更少混淆.
要修正一个提交:
git commit --fixup a0b1c2d3 . git rebase --autosquash -i HEAD~2
其中a0b1c2d3是要修复的提交,而2是要更改的+1粘贴的提交数。
注意:不带-i的git rebase --autosquash不起作用,但带-i则起作用,这很奇怪。
更新:现在可以在这里找到更清晰的脚本版本: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
现在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" fi }
此代码首先显示所有当前更改(如果您希望自己暂存文件,则可以删除此部分).然后创建fixup(也可以使用squash,如果这是你需要的)提交.之后,它会启动一个交互式rebase,并--autosquash
在您提供的提交的父级作为参数.这将打开您配置的文本编辑器,因此您可以验证所有内容是否符合预期,只需关闭编辑器即可完成此过程.
使用if [[ "$1" == HEAD* ]]
部分(从这里借用),因为如果你使用HEAD~2作为你的提交(你想要修改当前更改的提交)引用,那么在创建fixup commit之后HEAD将被替换你需要使用HEAD~3来引用同一个提交.