如何使用 Git 将我最后的 X 提交压缩成一个提交?
40 回答
你可以很容易地做到这一点没有git rebase
或git merge --squash
。在此示例中,我们将压缩最后 3 次提交。
如果您想从头开始编写新的提交消息,这就足够了:
git reset --soft HEAD~3 &&
git commit
如果您想开始编辑带有现有提交消息的串联的新提交消息(即类似于 pick/squash/squash/.../squashgit rebase -i
指令列表将开始您的内容),那么您需要提取这些消息并通过他们git commit
:
git reset --soft HEAD~3 &&
git commit --edit -m"$(git log --format=%B --reverse HEAD..HEAD@{1})"
这两种方法都以相同的方式将最后三个提交压缩成一个新的提交。软重置只是将 HEAD 重新指向您不想压缩的最后一个提交。软重置不会触及索引和工作树,从而使索引处于新提交所需的状态(即,它已经包含了您即将“丢弃”的提交的所有更改)。
如手册中所述,在第二次及后续提交中使用git rebase -i <after-this-commit>
“pick”并将其替换为“squash”或“fixup” 。
在此示例中,<after-this-commit>
是 SHA1 哈希值或来自当前分支的 HEAD 的相对位置,从该分支中分析 rebase 命令的提交。例如,如果用户希望查看当前 HEAD 过去的 5 次提交,则命令为git rebase -i HEAD~5
.
你可以使用git merge --squash
这个,它比git rebase -i
. 假设您在 master 上,并且想要将最后 12 个提交压缩为一个。
警告:首先确保你提交你的工作——检查git status
是否干净(因为git reset --hard
会丢弃分阶段和非分阶段的更改)
然后:
# Reset the current branch to the commit just before the last 12:
git reset --hard HEAD~12
# HEAD@{1} is where the branch was just before the previous command.
# This command sets the state of the index to be as it would just
# after a merge from that commit:
git merge --squash HEAD@{1}
# Commit those squashed changes. The commit message will be helpfully
# prepopulated with the commit messages of all the squashed commits:
git commit
的文档git merge
更详细地描述了该--squash
选项。
更新:git reset --soft HEAD~12 && git commit
与Chris Johnsen 在他的回答中建议的更简单的方法相比,这种方法的唯一真正优势是,您会收到预先填充了您要压缩的每条提交消息的提交消息。
我建议尽可能避免git reset
——尤其是对于 Git 新手。除非您真的需要基于多次提交来自动化流程,否则还有一种不那么奇特的方式......
- 将要被压缩的提交放在工作分支上(如果它们还没有的话)——为此使用 gitk
- 检查目标分支(例如'master')
git merge --squash (working branch name)
git commit
提交消息将基于壁球预先填充。
感谢这篇方便的博客文章,我发现您可以使用此命令来压缩最后 3 次提交:
git rebase -i HEAD~3
这很方便,因为即使您在没有跟踪信息/远程仓库的本地分支上也可以使用。
该命令将打开交互式 rebase 编辑器,然后您可以按照正常方式重新排序、压缩、改写等。
使用交互式变基编辑器:
交互式变基编辑器显示最后三个提交。此约束由HEAD~3
运行命令时确定git rebase -i HEAD~3
。
最近的提交,HEAD
,首先显示在第 1 行。以 a 开头的行#
是注释/文档。
显示的文档非常清晰。在任何给定的行上,您都可以将命令从更改pick
为您选择的命令。
我更喜欢使用该命令fixup
,因为这会将提交的更改“压缩”到上一行的提交中,并丢弃提交的消息。
由于第 1 行的提交是HEAD
,在大多数情况下,您会将其保留为pick
. 您不能使用squash
或fixup
因为没有其他提交可以将提交压缩到。
您还可以更改提交的顺序。这使您可以压缩或修复按时间顺序不相邻的提交。
一个实用的日常示例
我最近提交了一个新功能。从那时起,我已经提交了两个错误修复。但是现在我在我提交的新功能中发现了一个错误(或者可能只是一个拼写错误)。多么烦人!我不希望新的提交污染我的提交历史!
我要做的第一件事是修复错误并使用评论进行新的提交squash this into my new feature!
。
然后我运行git log
orgitk
并获取新功能的提交 SHA(在本例中1ff9460
)。
接下来,我使用git rebase -i 1ff9460~
. 提交后的~
SHA 告诉编辑器将该提交包含在编辑器中。
接下来,我将包含 fix ( fe7f1e0
) 的提交移动到功能提交下方,并更改pick
为fixup
.
关闭编辑器时,修复将被压缩到功能提交中,我的提交历史看起来很干净!
当所有提交都是本地的时,这很有效,但是如果您尝试更改已推送到远程的任何提交,您真的会给签出同一分支的其他开发人员带来问题!
2020 年没有变基的简单解决方案:
git reset --soft HEAD~2
git commit -m "new commit message"
git push -f
2 意味着最后两次提交将被压缩。您可以用任何数字替换它
根据克里斯约翰森的回答,
从 bash 添加一个全局“squash”别名:(或 Windows 上的 Git Bash)
git config --global alias.squash '!f(){ git reset --soft HEAD~${1} && git commit --edit -m"$(git log --format=%B --reverse HEAD..HEAD@{1})"; };f'
...或使用 Windows 的命令提示符:
git config --global alias.squash "!f(){ git reset --soft HEAD~${1} && git commit --edit -m\"$(git log --format=%B --reverse HEAD..HEAD@{1})\"; };f"
您~/.gitconfig
现在应该包含此别名:
[alias]
squash = "!f(){ git reset --soft HEAD~${1} && git commit --edit -m\"$(git log --format=%B --reverse HEAD..HEAD@{1})\"; };f"
用法:
git squash N
...它会自动将最后的提交压缩在一起N
,包括在内。
注意:生成的提交消息是所有压缩提交的组合,按顺序排列。如果您对此不满意,您可以随时git commit --amend
手动修改它。(或者,编辑别名以符合您的口味。)
为此,您可以使用以下 git 命令。
git rebase -i HEAD~n
n(=4 here) 是最后一次提交的次数。然后你有以下选项,
pick 01d1124 Message....
pick 6340aaa Message....
pick ebfd367 Message....
pick 30e0ccb Message....
更新如下pick
一个提交,squash
其他更新为最新,
p 01d1124 Message....
s 6340aaa Message....
s ebfd367 Message....
s 30e0ccb Message....
详情请点击链接
在您想要合并提交的分支中,运行:
git rebase -i HEAD~(n number of commits back to review)
例子:
git rebase -i HEAD~2
这将打开文本编辑器,如果您希望将这些提交合并在一起,则必须将每个提交前面的“pick”切换为“squash”。从文档:
p, 选择 = 使用提交
s, squash = 使用提交,但融合到之前的提交中
例如,如果您希望将所有提交合并为一个,则“pick”是您所做的第一个提交,所有未来的提交(位于第一个下方)都应设置为“squash”。如果使用 vim,请在插入模式下使用:x保存并退出编辑器。
然后继续变基:
git add .
git rebase --continue
有关重写提交历史的其他方法的更多信息,请参阅这篇有用的帖子
根据这篇文章,我发现这种方法更适合我的用例。
我的 'dev' 分支领先于 'origin/dev' 96 次提交(因此这些提交尚未推送到远程)。
在推动更改之前,我想将这些提交压缩为一个。我更喜欢将分支重置为 'origin/dev' 的状态(这将使 96 次提交的所有更改都未暂存),然后立即提交更改:
git reset origin/dev
git add --all
git commit -m 'my commit message'
Anomies 的回答很好,但我对此感到不安全,所以我决定添加几个屏幕截图。
步骤 0:git 日志
看看你在哪里git log
。最重要的是,找到您不想压缩的第一个提交的提交哈希。所以只有:
第一步:git rebase
执行git rebase -i [your hash]
,在我的情况下:
$ git rebase -i 2d23ea524936e612fae1ac63c95b705db44d937d
第2步:挑选/挤压你想要的东西
就我而言,我想在第一次提交时压缩所有内容。顺序是从头到尾,所以与 中的完全相反git log
。就我而言,我想要:
第 3 步:调整消息
如果您只选择了一个提交并压缩了其余的提交,您可以调整一个提交消息:
就是这样。保存此 ( :wq
) 后,您就完成了。一起来看看吧git log
。
我认为最简单的方法是基于 master 创建一个新分支并执行merge --squash
feature 分支。
git checkout master
git checkout -b feature_branch_squashed
git merge --squash feature_branch
然后你就可以提交所有的更改了。
程序 1
1)识别提交短哈希
# git log --pretty=oneline --abbrev-commit
abcd1234 Update to Fix for issue B
cdababcd Fix issue B
deab3412 Fix issue A
....
这里甚至git log --oneline
还可以用来获取短哈希。
2)如果你想压缩(合并)最后两个提交
# git rebase -i deab3412
3)这将打开一个nano
编辑器进行合并。它看起来像下面
....
pick cdababcd Fix issue B
pick abcd1234 Update to Fix for issue B
....
4) 将之前出现的单词重命名pick
为。重命名后应该如下所示。squash
abcd1234
....
pick cdababcd Fix issue B
squash abcd1234 Update to Fix for issue B
....
5) 现在保存并关闭nano
编辑器。按ctrl + o
并按Enter
保存。然后按ctrl + x
退出编辑器。
6)然后nano
编辑器再次打开以更新评论,如有必要进行更新。
7)现在它已经成功压扁了,你可以通过查看日志来验证它。
# git log --pretty=oneline --abbrev-commit
1122abcd Fix issue B
deab3412 Fix issue A
....
8)现在推送回购。注意+
在分支名称前添加符号。这意味着强制推动。
# git push origin +master
注意:这是基于在ubuntu
shell 上使用 git。如果您使用不同的操作系统(Windows
或Mac
),那么除了编辑器之外,上述命令是相同的。你可能会得到不同的编辑器。
程序 2
- 首先添加提交所需的文件
git add <files>
- 然后使用
--fixup
选项提交OLDCOMMIT
,我们需要合并(挤压)这个提交。
git commit --fixup=OLDCOMMIT
现在这会在 HEAD 之上创建一个新的提交,使用fixup1 <OLDCOMMIT_MSG>
.
- 然后执行以下命令将新提交合并(压缩)到
OLDCOMMIT
.
git rebase --interactive --autosquash OLDCOMMIT^
这里^
的意思是上一次提交到OLDCOMMIT
. 此rebase
命令在编辑器(vim 或 nano)上打开交互式窗口,我们无需执行任何操作,只需保存并退出即可。因为传递给 this 的选项会自动将最新提交移动到旧提交旁边并将操作更改为fixup
(相当于 squash)。然后 rebase 继续并完成。
程序 3
- 如果需要在最后一次提交中添加新的更改,
--amend
可以使用git-commit
.
# git log --pretty=oneline --abbrev-commit
cdababcd Fix issue B
deab3412 Fix issue A
....
# git add <files> # New changes
# git commit --amend
# git log --pretty=oneline --abbrev-commit
1d4ab2e1 Fix issue B
deab3412 Fix issue A
....
这里--amend
将新的更改合并到最后一次提交cdababcd
并生成新的提交 ID1d4ab2e1
结论
- 第一个过程的优点是压缩多个提交并重新排序。但是如果我们需要将修复合并到非常旧的提交中,这个过程将会很困难。
- 因此,第二个过程有助于轻松地将提交合并到非常旧的提交。
- 第三个过程在将新更改压缩到上次提交的情况下很有用。
如果您在feature-branch
从 Golden Repository() 克隆的远程分支(称为 )上golden_repo_name
,那么这里是将您的提交压缩为一个的技术:
查看黄金回购
git checkout golden_repo_name
从中创建一个新分支(黄金回购),如下所示
git checkout -b dev-branch
Squash 与您已经拥有的本地分支合并
git merge --squash feature-branch
提交您的更改(这将是 dev-branch 中的唯一提交)
git commit -m "My feature complete"
将分支推送到本地存储库
git push origin dev-branch
例如,如果您想将最后 3 个提交压缩为分支(远程存储库)中的单个提交,例如:https ://bitbucket.org
我所做的是
git reset --soft HEAD~3
git commit
git push origin <branch_name> --force
要将最后 10 次提交压缩为 1 次提交:
git reset --soft HEAD~10 && git commit -m "squashed commit"
如果您还想使用压缩提交更新远程分支:
git push -f
真正方便的是:
找到要压缩的提交哈希,例如d43e15
.
现在使用
git reset d43e15
git commit -am 'new commit name'
许多答案都是基于git rebase
命令的,但根据我的经验,它对于 git 初学者来说有点复杂和先进。
假设您要压缩最后 3 次提交。然后是以下步骤:
- 记下当前提交 ID:使用
git log -1 --oneline
并记下当前状态的提交 ID(以防万一你在 git reset 上做错了) - 返回 3 次提交:使用
git reset --soft HEAD~3
您将返回 3 次提交(并且有点忘记您之前已经进行了这三个提交) - 做一个新的提交:现在只需做
git commit -m <NEW_SINGLE_MESSAGE>
这将自动合并您的消息下的三个提交
如果 git reset 出现问题,您可以通过以下方式再次返回原始状态git reset --soft <ORIGINAL_COMMIT>
如果您想将每个提交压缩为单个提交(例如,当第一次公开发布项目时),请尝试:
git checkout --orphan <new-branch>
git commit
这是超级笨拙的,但以一种很酷的方式,所以我会把它扔进戒指:
GIT_EDITOR='f() { if [ "$(basename $1)" = "git-rebase-todo" ]; then sed -i "2,\$s/pick/squash/" $1; else vim $1; fi }; f' git rebase -i foo~5 foo
翻译:为 git 提供一个新的“编辑器”,如果要编辑的文件名是git-rebase-todo
(交互式 rebase 提示)将除第一个“pick”之外的所有内容都更改为“squash”,否则会生成 vim - 这样当你收到提示时要编辑压缩的提交消息,你会得到 vim。(显然我正在压缩分支 foo 上的最后五个提交,但你可以随意更改它。)
不过,我可能会按照Mark Longair 的建议去做。
⚠️ 警告:“我的最后 X 次提交”可能不明确。
(MASTER)
Fleetwood Mac Fritz
║ ║
Add Danny Lindsey Stevie
Kirwan Buckingham Nicks
║ ╚═══╦══════╝
Add Christine ║
Perfect Buckingham
║ Nicks
LA1974══════════╝
║
║
Bill <══════ YOU ARE EDITING HERE
Clinton (CHECKED OUT, CURRENT WORKING DIRECTORY)
在https://github.com/fleetwood-mac/band-history存储库的这个非常简短的历史记录中,您已经打开了一个拉取请求,以将比尔克林顿提交合并到原始 ( MASTER
) Fleetwood Mac 提交中。
你打开了一个拉取请求,在 GitHub 上你会看到:
四个提交:
- 添加丹尼·柯万
- 添加克里斯汀完美
- LA1974
- 比尔·克林顿
认为没有人会关心阅读完整的存储库历史记录。(实际上有一个存储库,单击上面的链接!)您决定压缩这些提交。所以你跑吧git reset --soft HEAD~4 && git commit
。然后你git push --force
把它放到 GitHub 上来清理你的 PR。
会发生什么?您刚刚做出了从 Fritz 到 Bill Clinton 的一次提交。因为你忘记了昨天你正在研究这个项目的白金汉尼克斯版本。并且git log
与您在 GitHub 上看到的不匹配。
故事的道德启示
- 找到您想要访问的确切文件,然后找到
git checkout
它们 - 找到您想要保留在历史记录中的确切先前提交,
git reset --soft
并且 - 制作一个
git commit
直接从from到to变形的
简单的单线始终有效,假设您当前位于要压缩的分支上,master 是它源自的分支,并且最新提交包含您希望使用的提交消息和作者:
git reset --soft $(git merge-base HEAD master) && git commit --reuse-message=HEAD@{1}
简单的解决方案:
git reset --soft HEAD~5
git commit -m "commit message"
git push origin branch --force-with-lease
如果您不关心中间提交的提交消息,您可以使用
git reset --mixed <commit-hash-into-which-you-want-to-squash>
git commit -a --amend
git rebase -i HEAD^^
其中 ^ 的数量是 X
(在这种情况下,压缩最后两个提交)
与这样的工作流程相关的问题的答案呢?
- 许多本地提交,混合了多个合并 FROM master,
- 最后推到远程,
- PR 和合并 TO master by reviewer。(是的,开发人员
merge --squash
在 PR 之后会更容易,但团队认为这会减慢进程。)
我还没有在这个页面上看到这样的工作流程。(那可能是我的眼睛。)如果我理解rebase
正确,多次合并将需要多次冲突解决。我什至不想考虑这个!
所以,这似乎对我们有用。
git pull master
git checkout -b new-branch
git checkout -b new-branch-temp
- 在本地编辑和提交很多,定期合并master
git checkout new-branch
git merge --squash new-branch-temp
// 将所有更改放入阶段git commit 'one message to rule them all'
git push
- Reviewer 做 PR 并合并到 master。
如何使用 Git 将我最后的 X 提交压缩成一个提交?
git rebase -i HEAD~X
将显示以下内容:
pick 1bffc15c My earlier commit
pick 474bf0c2 My recent commit
# ...
对于您要压缩的提交,将pick替换为fixup,因此它变为:
pick 1bffc15c My earlier commit
fixup 474bf0c2 My recent commit
# ...
如果它在 vim 中打开(终端中的默认界面),然后按Esc
键盘,键入:wq
并Enter
保存文件。
验证:检查git log
我发现一个更通用的解决方案不是指定“N”次提交,而是指定您想要压缩的分支/提交 ID。这比将提交计数到特定提交更不容易出错——只需直接指定标签,或者如果你真的想计算,你可以指定 HEAD~N。
在我的工作流程中,我启动了一个分支,我在该分支上的第一次提交总结了目标(即,这通常是我将作为功能的“最终”消息推送到公共存储库的内容。)所以当我完成后,所有我想做的是git squash master
回到第一条消息,然后我准备推送。
我使用别名:
squash = !EDITOR="\"_() { sed -n 's/^pick //p' \"\\$1\"; sed -i .tmp '2,\\$s/^pick/f/' \"\\$1\"; }; _\"" git rebase -i
这将在它这样做之前转储被压缩的历史记录——如果你想恢复,这让你有机会通过从控制台获取旧的提交 ID 来恢复。(Solaris 用户注意它使用 GNU sed-i
选项,Mac 和 Linux 用户应该可以接受。)
除了其他出色的答案之外,我想补充一下git rebase -i
,提交顺序总是让我感到困惑 - 旧的到新的,反之亦然?所以这是我的工作流程:
git rebase -i HEAD~[N]
,其中 N 是我想加入的提交数,从最近的一个 开始。所以git rebase -i HEAD~5
这意味着“将最后 5 个提交压缩成一个新的提交”;- 编辑器弹出,显示我要合并的提交列表。现在它们以相反的顺序显示:较旧的提交在顶部。将除第一个/较旧的提交之外的所有提交标记为“壁球”或“s” :它将用作起点。保存并关闭编辑器;
- 编辑器再次弹出新提交的默认消息:将其更改为您的需要,保存并关闭。壁球完成!
有问题的是,“最后”的含义可能是模棱两可的。
例如git log --graph
输出以下(简化):
* commit H0
|
* merge
|\
| * commit B0
| |
| * commit B1
| |
* | commit H1
| |
* | commit H2
|/
|
然后按时间的最后一次提交是 H0,合并,B0。要压缩它们,您必须在提交 H1 上重新设置合并分支的基础。
问题是 H0 包含 H1 和 H2 (通常在合并之前和分支之后提交更多),而 B0 不包含。因此,您至少必须管理来自 H0、合并、H1、H2、B0 的更改。
可以使用 rebase 但在其他人提到的答案中以不同的方式使用:
rebase -i HEAD~2
这将向您显示选择选项(如其他答案中所述):
pick B1
pick B0
pick H0
将 squash 而不是 pick 放到 H0:
pick B1
pick B0
s H0
保存并退出后,rebase 将在 H1 之后依次应用提交。这意味着它将要求您再次解决冲突(其中 HEAD 将首先是 H1,然后在应用它们时累积提交)。
在 rebase 完成后,您可以为压扁的 H0 和 B0 选择消息:
* commit squashed H0 and B0
|
* commit B1
|
* commit H1
|
* commit H2
|
PS 如果您只是对 BO 进行了一些重置:(例如,在此处https://stackoverflow.com/a/18690845/2405850reset --mixed
对此进行了更详细的说明):
git reset --mixed hash_of_commit_B0
git add .
git commit -m 'some commit message'
然后你压缩到 H0、H1、H2 的 B0 更改(在分支之后和合并之前完全丢失更改提交。
首先,我通过以下方式找出我的功能分支和当前主分支之间的提交次数
git checkout master
git rev-list master.. --count
然后,我基于 my-feature 分支创建另一个分支,保持my-feature
分支不变。
最后,我跑
git checkout my-feature
git checkout -b my-rebased-feature
git checkout master
git checkout my-rebased-feature
git rebase master
git rebase head^x -i
// fixup/pick/rewrite
git push origin my-rebased-feature -f // force, if my-rebased-feature was ever pushed, otherwise no need for -f flag
// make a PR with clean history, delete both my-feature and my-rebased-feature after merge
希望有帮助,谢谢。
为避免在重新定位到同一分支中的提交时必须解决任何合并冲突,您可以使用以下命令
git rebase -i <last commit id before your changes start> -s recursive -X ours
要将所有提交压缩为一个,当系统提示您编辑要合并的提交(-i 标志)时,也按照其他答案中的建议pick
更新除第一个操作之外的所有操作。squash
在这里,我们使用合并策略(-s 标志)recursive
和策略选项(-X)ours
来确保历史中后来的提交赢得任何合并冲突。
注意:不要将其与git rebase -s ours
其他功能混淆。
尝试了这里提到的所有方法。但最后我的问题通过点击这个链接解决了。 https://gist.github.com/longtimeago/f7055aa4c3bba8a62197
$ git fetch upstream
$ git checkout omgpull
$ git rebase -i upstream/master
< choose squash for all of your commits, except the first one >
< Edit the commit message to make sense, and describe all your changes >
$ git push origin omgpull -f
方法 1,如果你有很多提交
git rebase -i master
然后按键盘“i”进行编辑
你会看到这样的:
pick etc1
pick etc2
pick etc2
替换单词 pick'f'
并按下esc y :wq
pick etc1 //this commit will the one commit
f etc2
f etc2
并按此命令
git push origin +head
方法 2如果你的提交很少,你可以这样做来删除一个提交,你需要做同样的事情来删除你的第二个提交,依此类推
git reset --soft HEAD^1
git commit --amend
git push -f
方法 3,如果您已经有一个提交并且您不想再提交另一个提交
git add files...
git commit --amend //then press `:wq`
git push origin +head
如果您使用的是GitUp,请选择要与其父级合并的提交,然后按S。您必须为每次提交执行一次,但这比提出正确的命令行咒语要简单得多。特别是如果它是你只偶尔做一次的事情。
只需将此 bash 函数添加到 .zshrc 文件的 bash 中即可。
# Squash last X commits with a Commit message.
# Usage: squash X 'COMMIT_MSG'
# where X= Number of last commits.
# where COMMIT_MSG= New commit msg.
function squash() {
if [ -z "${1}" -o -z "${2}" ]; then
echo "Usage: \`squash X COMMIT_MSG\`"
echo "X= Number of last commits."
echo "COMMIT_MSG= New commit msg."
return 1
fi
git reset --soft HEAD~"$1"
git add . && git ci -m "$2" # With 100 emoji
git push --force
}
然后运行
squash X 'New Commit Message'
你完成了。