假设你有:
A-B-C
现在你的构建/测试失败了。该修复程序应合并到 A 中。我当前的工作流程是这样的:
$ git commit -m "fixA"
A-B-C-fixA
$ git rebase -i A~1
并在 A 中压缩 fixA,结果为:
A'-B-C
是否有命令可以执行以下操作:
A-B-C + (index with fix for A)
$ git commit -supperdupper A
结果:
A'-B-C
假设你有:
A-B-C
现在你的构建/测试失败了。该修复程序应合并到 A 中。我当前的工作流程是这样的:
$ git commit -m "fixA"
A-B-C-fixA
$ git rebase -i A~1
并在 A 中压缩 fixA,结果为:
A'-B-C
是否有命令可以执行以下操作:
A-B-C + (index with fix for A)
$ git commit -supperdupper A
结果:
A'-B-C
如果您只是在寻找修复早期提交的简单解决方案,请阅读问题!它解释了一切。但是由于 Elmarco 要求采用一种巧妙的方式,所以我们开始:
从 Git 1.7.0 开始,有一个--autosquash
选项rebase
,可以满足您的要求。还有使事情变得更容易的--fixup
和--squash
选项。commit
通过一些别名,您甚至可以将整个事情变成一个命令。
我建议升级到最新的 Git 以获得最大的效果:
git/Documentation/RelNotes $ grep -i -A1 autosquash\\\|fixup *
1.7.0.txt: * "git rebase -i" learned new action "fixup" that squashes the change
1.7.0.txt- but does not affect existing log message.
--
1.7.0.txt: * "git rebase -i" also learned --autosquash option that is useful
1.7.0.txt: together with the new "fixup" action.
1.7.0.txt-
--
1.7.3.txt: * "git rebase -i" peeks into rebase.autosquash configuration and acts as
1.7.3.txt: if you gave --autosquash from the command line.
1.7.3.txt-
--
1.7.4.txt: * "git commit" learned --fixup and --squash options to help later invocation
1.7.4.txt- of the interactive rebase.
--
1.7.4.txt: * "git rebase --autosquash" can use SHA-1 object names to name which
1.7.4.txt: commit to fix up (e.g. "fixup! e83c5163").
1.7.4.txt-
我创建了一些别名,以便更容易使用git 1.7 中添加的git commit --fixup
和命令。git commit --squash
将这些添加到您的~/.gitconfig
:
[alias]
fixup = !sh -c 'REV=$(git rev-parse $1) && git commit --fixup $@ && git rebase -i --autosquash $REV^' -
squash = !sh -c 'REV=$(git rev-parse $1) && git commit --squash $@ && git rebase -i --autosquash $REV^' -
用法:
$ git commit -am 'bad commit'
$ git commit -am 'good commit'
$ git add . # Stage changes to correct the bad commit
$ git fixup HEAD^ # HEAD^ can be replaced by the SHA of the bad commit
错误的提交可以是多次提交。
我当前的 git 工作流程是如此--fixup
/--squash
密集,以至于我编写了一个新git-fixup
命令来自动处理大多数烦人的位:
git fixup
显示在最新提交下分组的修改后的文件,这些文件涉及相同的文件git fixup -a
将所有这些更改作为--fixup
更改与其相应的“父”提交提交git fixup -r
git rebase --autosquash
对所有修复提交执行自动操作很多变化使得仅上面的三个命令就足以完成工作,无需复制粘贴 commit-id 或通过阅读git log
来找到正确的--fixup
目标。
如果你想压缩最后两个提交,那么你必须调用
git rebase --interactive <3rd last commit>
然后,您需要选择最后一个提交并压缩倒数第二个提交。您无法压缩历史的最高提交。
如果您正在与其他人共享您正在进行更改的分支,那么您正在做的事情是危险的。在您的情况下,您正在重写提交 A 并将 B 和 C 重新定位在新 A 之上,这是一个全新的对象。任何已经将旧 A 拉入其存储库的人最终都可能破坏他们的存储库,因为存储库中的历史将“神奇地”改变。他们的 git 无法知道历史已被改写。
由于这个事实,Git 开发人员故意不让这件事变得容易,因为您必须意识到执行此类操作的后果。
我认为问题的根源在于 git(以及通常的版本控制)迫使您根据更改的顺序进行思考,但是变更集或功能分支或任何您称之为有凝聚力的相关更改组通常在逻辑上不是顺序的. 编写代码的顺序是偶然的,不一定与阅读它的顺序有关。
我没有解决方案,但我编写了一个Perl 脚本来帮助自动化重写历史的过程。它类似于@MikaEloranta 的 Python 脚本,我在编写它时没有看到它。
commit --fixup
并且rebase --autosquash
很棒,但他们做得还不够。当我有一系列提交A-B-C
并且我在我的工作树中编写了更多属于一个或多个现有提交的更改时,我必须手动查看历史记录,确定哪些更改属于哪些提交,暂存它们并创建fixup!
提交。但是 git 已经获得了足够的信息来为我做所有这些事情。
对于git diff
脚本中的每个块,git blame
用于查找最后触及相关行的提交,并调用git commit --fixup
以编写适当的fixup!
提交,基本上与我之前手动执行的操作相同。
如果脚本无法将一个大块解析为一个单一的、明确的提交,它会将其报告为一个失败的大块,您将不得不回退到那个手动方法。如果您在两个单独的提交中两次更改了一行,则该脚本会将对该行的更改解析为这些提交中的最新一次,这可能并不总是正确的解决方案。恕我直言,在“正常形式”功能分支中,您不应该在两次不同的提交中两次更改一行,每次提交都应该呈现它所触及的行的最终版本,以帮助审阅者。但是,它可能发生在错误修复分支中,以设计一个示例,该行foo(bar());
可以被提交 A(重命名foo
为fox
)和提交 B(重命名bar
为baz
)触及。
If you find the script useful, please feel free to improve and iterate on it and maybe one day we'll get such a feature in git
proper. I'd love to see a tool that can understand how a merge conflict should be resolved when it has been introduced by an interactive rebase.