如何将我最后的 N 个提交压缩为一个提交?
无需 git rebase
或 git merge --squash
即可轻松完成此操作。在此示例中,我们将压缩最后 3 次提交。
如果您想从头开始编写新的提交消息,这就足够了:
git reset --soft HEAD~3 &&
git commit
如果您想开始编辑带有现有提交消息的串联的新提交消息(即类似于 pick/squash/squash/.../squash git 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”,如 the manual 中所述。
在此示例中,<after-this-commit>
是 SHA1 散列或与当前分支的 HEAD 的相对位置,从该分支中分析 rebase 命令的提交。例如,如果用户希望查看当前 HEAD 过去的 5 次提交,则命令为 git rebase -i HEAD~5
。
<after-this-commit>
是什么意思?
<after-this-commit>
是提交 X+1,即您要压缩的最早提交的父级。
rebase -i
方法与 reset --soft
的区别在于,rebase -i
允许我保留提交作者,而 reset --soft
允许我重新提交。有时我需要压缩拉取请求的提交,同时维护作者信息。有时我需要在自己的提交上重置软。无论如何都对这两个很好的答案表示赞成。
Use git rebase -i <after-this-commit> and replace "pick" on the second and subsequent commits with "squash" or "fixup", as described in the manual.
呃呃呃呃呃呃呃?
为此,您可以使用 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
documentation for git merge
更详细地描述了 --squash
选项。
更新:与 Chris Johnsen 在 his answer 中建议的更简单的 git reset --soft HEAD~12 && git commit
相比,此方法的唯一真正优势是,您可以在提交消息中预先填充您要压缩的每条提交消息。
git merge --squash
也更易于在脚本中使用。本质上,原因是您根本不需要 git rebase -i
的“交互性”。
git merge --squash
在面对移动/删除/重命名时不太可能产生合并冲突,尤其是当您从本地分支合并时。 (免责声明:仅基于一次经验,如果在一般情况下这不是真的,请纠正我!)
HEAD@{1}
只是为了安全起见,例如当您的工作流程因停电等而中断一个小时时。
我建议尽可能避免使用 git reset
——尤其是对于 Git 新手。除非您真的需要基于提交的 number 次自动化流程,否则还有一种不那么奇特的方式......
将要被压缩的提交放在工作分支上(如果它们还没有的话)——为此使用 gitk 检查目标分支(例如“master”) git merge --squash (工作分支名称) git commit
提交消息将基于壁球预先填充。
gitk
的 GUI 界面来标记要压缩的代码行,并标记要压缩到的基础。在正常情况下,这两个标签都已经存在,因此可以跳过步骤(1)。
git branch your-feature && git reset --hard HEAD~N
是最方便的方式。但是,它确实再次涉及 git reset ,这个答案试图避免。
感谢 this handy blog post,我发现您可以使用此命令来压缩最后 3 次提交:
git rebase -i HEAD~3
这很方便,因为即使您在没有跟踪信息/远程仓库的本地分支上也可以使用。
该命令将打开交互式 rebase 编辑器,然后您可以按照正常方式重新排序、压缩、改写等。
使用交互式变基编辑器:
交互式变基编辑器显示最后三个提交。此约束由 HEAD~3
在运行命令 git rebase -i HEAD~3
时确定。
最近的提交 HEAD
首先显示在第 1 行。以 #
开头的行是评论/文档。
显示的文档非常清晰。在任何给定的行上,您都可以将命令从 pick
更改为您选择的命令。
我更喜欢使用命令 fixup
因为这会将提交的更改“压缩”到上一行的提交中并丢弃提交的消息。
由于第 1 行的提交是 HEAD
,因此在大多数情况下,您会将其保留为 pick
。您不能使用 squash
或 fixup
,因为没有其他提交可以将提交压缩到。
您还可以更改提交的顺序。这使您可以压缩或修复按时间顺序不相邻的提交。
https://i.stack.imgur.com/JYUt2.png
一个实用的日常示例
我最近提交了一个新功能。从那时起,我已经提交了两个错误修复。但是现在我在我提交的新功能中发现了一个错误(或者可能只是一个拼写错误)。多么烦人!我不希望新的提交污染我的提交历史!
我要做的第一件事是修复错误并使用注释 squash this into my new feature!
进行新的提交。
然后我运行 git log
或 gitk
并获取新功能的提交 SHA(在本例中为 1ff9460
)。
接下来,我使用 git rebase -i 1ff9460~
调出交互式变基编辑器。提交 SHA 后的 ~
告诉编辑器将该提交包含在编辑器中。
接下来,我将包含修复 (fe7f1e0
) 的提交移动到功能提交下方,并将 pick
更改为 fixup
。
关闭编辑器时,修复将被压缩到功能提交中,我的提交历史看起来很干净!
当所有提交都是本地的时,这很有效,但是如果您尝试更改已推送到远程的任何提交,您真的会给签出同一分支的其他开发人员带来问题!
https://i.stack.imgur.com/vzlVd.png
pick
。如果您在第 1 行的提交中选择 squash
或 fixup
,git 将显示一条消息“错误:在没有先前提交的情况下无法 'fixup'”。然后它会给你修复它的选项:“你可以用'git rebase --edit-todo'修复它,然后运行'git rebase --continue'。”或者您可以中止并重新开始:“或者您可以使用 'git rebase --abort' 中止 rebase。”。
gr3
的别名:alias gr3='git rebase -i HEAD~3'
pick
第一行的原因。当您在一行中选择 squash
或 fixup
时,它会将更改放入上一行的提交中。
2020 年没有变基的简单解决方案:
git reset --soft HEAD~2
git commit -m "new commit message"
git push -f
2 意味着最后两次提交将被压缩。您可以用任何数字替换它
push
中使用 -f (force)
选项是一种危险的做法,尤其是当您推送到共享存储库(即公共历史记录)时,这会使贡献者的生活变得困难
git reset --soft $(git merge-base feature master)
,然后使用 git commit
。
--force
进行推送。这样做可以使您与团队的其他成员和不同的远程位置保持一致,因为您知道发生了什么。你也知道历史的哪一部分已经稳定了。没必要隐瞒。
force
推送,因为这种方法的影响非常好——强制推送与 rebase 和工作同步齐头并进,所以它更好,在我的拙见,更多的人知道这个动作的效果,而不是成为人们害怕使用的公开命令。
从 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 squash -m "New summary."
并将 N
自动确定为未推送提交的数量。
git commit --amend
进一步更改消息,但此别名可让您对提交消息中的内容有一个良好的开端。
在您想要合并提交的分支中,运行:
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
有关重写提交历史记录的此方法和其他方法的更多信息,请参阅this helpful post
--continue
和 vim :x
的作用。
git add
文件中的正确配置之后,您使用 git rebase --continue
移动到下一个提交并开始合并,rebase 将在通过您的分支上的提交时以块的形式发生。 :x
是使用 vim 时保存文件更改的命令,请参阅 this
为此,您可以使用以下 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....
详情请点击Link
如果您使用 TortoiseGit,您可以使用函数 Combine to one commit
:
打开 TortoiseGit 上下文菜单 选择显示日志 在日志视图中标记相关提交 从上下文菜单中选择合并到一个提交
https://i.stack.imgur.com/IPL9i.png
此功能自动执行所有必要的单个 git 步骤。不幸的是,仅适用于 Windows。
根据 this article,我发现这种方法更适合我的用例。
我的 'dev' 分支领先于 'origin/dev' 96 次提交(因此这些提交尚未推送到远程)。
在推动更改之前,我想将这些提交压缩为一个。我更喜欢将分支重置为 'origin/dev' 的状态(这将使 96 次提交的所有更改都未暂存),然后立即提交更改:
git reset origin/dev
git add --all
git commit -m 'my commit message'
这是执行后的另一个直观示例:git rebase -i HEAD~3
https://i.stack.imgur.com/rybJi.gif
来源:https://www.git-tower.com/learn/git/faq/git-squash/
Anomies answer 很好,但我对此感到不安全,所以我决定添加几个屏幕截图。
步骤 0:git 日志
使用 git log
查看您的位置。最重要的是,找到您不想要压缩的第一个提交的提交哈希。所以只有:
https://i.stack.imgur.com/LOn7X.png
第一步:git rebase
执行 git rebase -i [your hash]
,在我的例子中:
$ git rebase -i 2d23ea524936e612fae1ac63c95b705db44d937d
第2步:挑选/挤压你想要的东西
就我而言,我想在第一次提交时压缩所有内容。顺序是从头到尾,因此与 git log
中的顺序完全相反。就我而言,我想要:
https://i.stack.imgur.com/Bjgxk.png
第 3 步:调整消息
如果您只选择了一个提交并压缩了其余的提交,您可以调整一个提交消息:
https://i.stack.imgur.com/75aIZ.png
而已。保存此 (:wq
) 后,您就完成了。用 git log
查看它。
git log
我认为最简单的方法是基于 master 创建一个新分支并执行merge --squash
功能分支。
git checkout master
git checkout -b feature_branch_squashed
git merge --squash feature_branch
然后你就可以提交所有的更改了。
git merge
的存在
程序 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
重命名为 abcd1234
之前的 squash
。重命名后应该如下所示。
....
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
并生成新的提交 ID 1d4ab2e1
结论
第一个过程的优点是压缩多个提交并重新排序。但是如果我们需要将修复合并到非常旧的提交中,这个过程将会很困难。
因此,第二个过程有助于轻松地将提交合并到非常旧的提交。
第三个过程在将新更改压缩到上次提交的情况下很有用。
如果您在从 Golden Repository (golden_repo_name
) 克隆的远程分支(称为 feature-branch
)上,那么这里是将您的提交压缩为一个的技术:
签出黄金仓库 git checkout golden_repo_name 从它(黄金仓库)创建一个新分支,如下所示 git checkout -b dev-branch 与您已经拥有的本地分支合并 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
要将最后 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
如果 git reset 出现问题,您可以通过 git reset --soft <ORIGINAL_COMMIT>
再次返回原始状态
有没有人提到在 IntelliJ IDEA UI 上做起来很容易:
转到 git 窗口
手动选择要合并为一个的所有提交。
右键单击 > Squash Commits > Edit the squashed commit message
单击左侧的分支名称>右键单击>推送>强制推送
https://i.stack.imgur.com/gVjRD.png
简单的解决方案:
git reset --soft HEAD~5
git commit -m "commit message"
git push origin branch --force-with-lease
如果您想将每个提交压缩为单个提交(例如,当第一次公开发布项目时),请尝试:
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 suggested。
gawk
简化此操作。 git -c core.editor="gawk -i inplace '{if(NR>1 && \$1==\"pick\"){\$1=\"squash\"} print \$0}'" rebase -i --autosquash HEAD~5
。
GIT_SEQUENCE_EDITOR
的命令,因为我需要 autostash
简单的单线始终有效,假设您当前位于要压缩的分支上,master 是它源自的分支,并且最新提交包含您希望使用的提交消息和作者:
git reset --soft $(git merge-base HEAD master) && git commit --reuse-message=HEAD@{1}
⚠️ 警告:“我的最后 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 存储库的这个非常简短的历史记录中,您打开了一个拉取请求,以将 Bill Clinton 提交合并到原始 (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 进行直接从 from 到 to 扭曲的 git 提交
如果您不关心中间提交的提交消息,您可以使用
git reset --mixed <commit-hash-into-which-you-want-to-squash>
git commit -a --amend
如果您正在使用 GitLab,您只需单击合并请求中的 Squash 选项,如下所示。提交消息将是合并请求的标题。
https://i.stack.imgur.com/iqlxy.jpg
GitLab Enterprise Edition 12.8.6-ee
时,它只是随机获取了压缩提交的提交消息......
如何使用 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
git rebase -i HEAD^^
其中 ^ 的数量是 X
(在这种情况下,压缩最后两个提交)
git rebase --squash-recent
,甚至是git commit --amend-many
。branch@{upstream}
(或仅@{upstream}
用于当前分支;在这两种情况下,最后一部分都可以缩写为@{u}
;请参阅gitrevisions)。这可能与您的“上次推送的提交”不同(例如,如果其他人推送了构建在您最近推送之上的内容,然后您获取了该内容),但似乎它可能接近您想要的。push -f
,否则它很可爱,谢谢。git push --force
以便它接受提交