在不使用交互式重置的情况下,压缩 Git 历史记录中间的两个提交

14

我正在将一个旧的SVN仓库转换为Git,这包括尝试将所有分支/标签放置在正确的位置。这部分工作进展得很顺利,但有时我想在我的脚本中添加一个提交历史记录,然后希望将其与下一个提交合并。问题是我不是逐个抓取提交,而是作为一组大的提交,因此我无法在从SVN仓库中提取它们时合并它们。最终,我的仓库看起来像这样:

* (branch_2, HEAD) commit 5
* commit 4
* commit 3
* SQUASH ME!
* (branch_1) commit 2
* commit 1

我想要通过使用交互式 rebase 很容易地压缩commit 3,但是在脚本内部更具挑战性。 我遇到的主要问题似乎是虽然很容易检出branch_1或它之前的任何提交,但很难以编程方式请求其后的提交,并且很难预测我需要从branch_2回退多少次提交。 我真的希望能够做到:

git checkout branch_1+2

有什么建议吗?


请查看“rebase”命令的“onto”选项,以执行像压缩等无需交互的操作(非常适合脚本):http://blog.pivotal.io/labs/labs/git-rebase-onto - Jonathan.Brink
1
@Jonathan.Brink 这并不是我想要的。那篇文章强调了能够删除提交。我想要压缩(或修复)SQUASH ME!提交,因为我确实需要这些更改。我只是希望它们与commit 3合并。 - Wesley Bland
让脚本在交互式变基期间充当编辑器可能会更简单。 - coredump
@coredump 如果只是进行交互式变基,那肯定会更简单,但这样就失去了脚本的意义。我希望能够将其提供给其他人,让他们在没有任何干扰的情况下完成操作。 - Wesley Bland
1
@WesleyBland 我的意思是,让脚本与git进行交互,通过将GIT_EDITOR设置为您选择的程序。您的程序打开文件,删除/修改某些行并退出。将整个过程包装在另一个脚本中:GIT_EDITOR =./scripts/editor-bot git rebase --interactive $0或适合您需求的等效命令。 - coredump
1
我刚刚读了这个,意识到我的答案说的完全一样,我甚至为你写了脚本。 :-) - ams
2个回答

8
你所说的不是“压缩”,而是“修正”,因为压缩会交互式地要求您输入提交消息,而“修正”使用HEAD提交的提交消息。以下是一个无需干预即可执行的脚本:/usr/bin/git-fixup
#/bin/bash
# This command fixesup one commit onto his parent
set -e

# We need a commit from the first argument of that command
commit=${1:?No commit given as first argument}
startingbranch=$(git rev-parse --abbrev-ref HEAD)

# Checkout the parent of the asked commit
git checkout "$commit"
git checkout HEAD~

# Merge the commit into it's parent, keeping the commit message of the parent
git merge --squash "$commit"
git add .
git add --update
git commit --amend --no-edit

# Store the current commit
newcommit=$(git rev-parse HEAD)

# Rebase the starting branch onto the new commit
git checkout "$startingbranch"
git rebase "$newcommit"

使用它与
git fixup <commit-id>

例如,如果您的历史记录是:
ce0e2fd (master, HEAD) commit 4
72ab3c4 commit 3
8150939 commit 2
301c1e1 commit 1

你可以使用命令 git fixup 72ab3c4,将“commit 3”和“commit 2”合并为一次提交,提交信息为“commit 2”,并将你放回主分支。

我建议添加 set -e 以便在工作目录不干净时中止。第一个 git checkout 将会报错并且整个脚本将退出。 - coredump
谢谢您的建议,我已经采纳了。 - edi9999
@edi9999,我看到你提供了一个名为git-fixup的脚本,然后建议使用命令git fixup(有空格)——这是一个四年前的错别字吗?如果不是,请解释一下如何将它们一起使用。 - Jonathan Cross
你好@JonathanCross,这不是打字错误,这是git的特定功能(尽管我在文档中找不到)。当您运行git任何命令时,如果您的路径中存在“git-anything”,它将运行该命令。例如,git-extras就是这样做的:https://github.com/tj/git-extras/tree/master/bin - edi9999
@edi9999 感谢分享,git-extras看起来很有用。但是它不是git的一部分,而是你在个人机器上安装的git辅助脚本。它对其他人没有用(除非他们也安装了这些辅助程序)。有趣的是,git-fixup并未出现在你发送的列表中。 - Jonathan Cross

7

来自 git rebase --help:

--autosquash

当提交日志消息以“squash!…”(或“fixup!…”)开头,并且存在一个标题以相同方式开头的提交…时,自动修改rebase -i的待办事项列表,以便标记为压缩的提交紧随要修改的提交之后,并将移动提交的操作从 pick 更改为 squash(或 fixup)。

此选项仅在使用--interactive选项时有效。

如果输入格式正确,看起来它会做你想要的一半工作。

另一半工作是防止其启动交互式编辑器。幸运的是,编辑器是可配置的,因此我们可以将其设置为某些无害的内容。

尝试此命令:

env EDITOR=true git rebase -i --autosquash <from-here>

将编辑器设置为true(一个简单成功退出的工具)足以说服git继续使用默认的变基设置,而自动压缩应该已经将其设置为某些有用的内容。


或者,如果--autosquash不能达到您的要求,您可以将EDITOR设置为任何您喜欢的脚本:

env EDITOR=mysquasher.sh git rebase -i <from-here>

这个脚本可以完成你需要的任何操作,但在你的情况下,它只需要找到包含"SQUASHME!"的每一行,并将其后面一行的"pick"更改为"fixup"。这可以通过awk最容易地实现:

#!/bin/bash -e

awk -- 'BEGIN {dosquash=0}
        dosquash==1 {gsub(/^pick/, "fixup"); dosquash=0}
        /SQUASHME/ {dosquash=1}
        {print}' "$1" > /tmp/tmp.$$

mv /tmp/tmp.$$ "$1"

你设置了VISUAL或任何其他可疑的环境变量吗? - ams
2
尝试设置GIT_EDITOR。Git至少响应EDITORVISUALGIT_EDITOR(对我而言),但我假设后者首先被检查。 - ams
你确保在同一行上同时输入了 envgit 命令,对吗? - ams

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接