我有以下提交历史:
头头~头~2头~3头
git commit --amend
修改当前的 HEAD
提交。但是如何修改 HEAD~3
?
使用 git rebase
。例如,要修改提交 bbc643cd
,请运行:
$ git rebase --interactive 'bbc643cd^'
请注意命令末尾的插入符号 ^
,因为您实际上需要变基回 the commit before the one you wish to modify。
在默认编辑器中,将提及 bbc643cd
的行中的 pick
修改为 edit
。
保存文件并退出。 git 将解释并自动执行文件中的命令。您会发现自己处于刚刚创建提交 bbc643cd
的先前情况。
此时,bbc643cd
是您的最后一次提交,您可以easily amend it。进行更改,然后使用以下命令提交:
$ git commit --all --amend --no-edit
之后,使用以下命令返回到之前的 HEAD 提交:
$ git rebase --continue
警告:请注意,这将更改该提交以及所有子项的 SHA-1——换句话说,这将重写从那时起的历史记录。 You can break repos doing this 如果您使用命令 git push --force
推送。
使用很棒的交互式变基:
git rebase -i @~9 # Show the last 9 commits in a text editor
找到您想要的提交,将 pick
更改为 e
(edit
),然后保存并关闭文件。 Git 将回退到该提交,允许您:
使用 git commit --amend 进行更改,或
使用 git reset @~ 放弃最后一次提交,但不放弃对文件的更改(即带您到您编辑文件但尚未提交时所处的位置)。
后者对于做更复杂的事情很有用,比如拆分成多个提交。
然后,运行 git rebase --continue
,Git 将在您修改的提交之上重放后续更改。您可能会被要求修复一些合并冲突。
注意:@
是 HEAD
的简写,~
是指定提交之前的提交。
在 Git 文档中阅读有关 rewriting history 的更多信息。
不要害怕变基
ProTip™:不要害怕尝试重写历史记录的“危险”命令*——Git 默认在 90 天内不会删除您的提交;您可以在 reflog 中找到它们:
$ git reset @~3 # go back 3 commits
$ git reflog
c4f708b HEAD@{0}: reset: moving to @~3
2c52489 HEAD@{1}: commit: more changes
4a5246d HEAD@{2}: commit: make important changes
e8571e4 HEAD@{3}: commit: make some changes
... earlier commits ...
$ git reset 2c52489
... and you're back where you started
但请注意 --hard
和 --force
之类的选项 - 它们可能会丢弃数据。
* 此外,不要在您正在协作的任何分支上重写历史记录。
在许多系统上,git rebase -i
默认会打开 Vim。 Vim 不像大多数现代文本编辑器那样工作,所以看看 how to rebase using Vim。如果您希望使用其他编辑器,请使用 git config --global core.editor your-favorite-text-editor
进行更改。
git reset @~
正是我在使用 git rebase ...
选择提交后想要做的。你是我的英雄)
git rebase -i --root
。
git reset @~
进行一些更新(主要是 git checkout file-i-didn't-mean-to-change
)之后,是否有一种简单的方法可以将该提交与原始消息一起放回?我从 git status
的简介输出中复制了原始消息,然后运行 git rebase --continue
,但我想知道是否有更简单的方法?也许如果没有更简单的方法,您也可以在回答中简要提及这一点?
git reset @~ -- file-i-didn't-mean-to-change
后跟 git commit --amend --no-edit
?
当我需要更深入地修复历史中的先前提交时,我经常使用与 --autosquash
交互的 rebase。它从本质上加快了 ZelluX 的回答所说明的过程,并且当您需要编辑多个提交时特别方便。
从文档中:
--autosquash 当commit log message以“squash!...”(或“fixup!...”)开头,并且有一个标题以相同...开头的commit时,自动修改rebase -i的todo列表,使该commit在要修改的提交之后立即标记为挤压
假设您的历史记录如下所示:
$ git log --graph --oneline
* b42d293 Commit3
* e8adec4 Commit2
* faaf19f Commit1
并且您有要修改到 Commit2 的更改,然后使用
$ git commit -m "fixup! Commit2"
或者,您可以使用 commit-sha 代替提交消息,因此 "fixup! e8adec4
甚至只是提交消息的前缀。
然后在之前的提交上启动交互式变基
$ git rebase e8adec4^ -i --autosquash
您的编辑器将打开已正确排序的提交
pick e8adec4 Commit2
fixup 54e1a99 fixup! Commit2
pick b42d293 Commit3
您需要做的就是保存并退出
git commit --fixup=@~
代替 git commit -m "fixup! Commit2"
。当您的提交消息较长并且输入整个内容会很痛苦时,这尤其有用。
fixup = "!fn() { git commit --fixup ${1} && GIT_EDITOR=true git rebase --autosquash -i ${1}^; }; fn
-> git fixup <commitId>
修改给定提交的所有分阶段更改
"
。
fixup = "!fn() { git commit -m \"fixup! ${1}\" && GIT_EDITOR=true git rebase --autosquash -i ${1}^; }; fn"
HEAD
、HEAD^
、HEAD~7
等:fixup = "!fn() { _FIXUP_COMMIT=`git rev-parse $HEAD
` && git commit -m \"fixup! ${_FIXUP_COMMIT}\" && GIT_EDITOR=true git rebase --autosquash -i ${_FIXUP_COMMIT}^; }; fn"
跑:
$ git rebase --interactive commit_hash^
每个 ^
表示您想要编辑多少次提交,如果它只有一个(您指定的提交哈希),那么您只需添加一个 ^
。
使用 Vim 将单词 pick
更改为 reword
以表示要更改、保存和退出 (:wq
) 的提交。然后 git 将提示您标记为 reword 的每个提交,以便您可以更改提交消息。
您必须保存并退出(:wq
)每条提交消息才能转到下一个提交消息
如果您想退出而不应用更改,请按 :q!
编辑:要在 vim
中导航,您使用 j
向上,k
向下,h
向左,l
向右(所有这些都在 { 6} 模式,按 ESC
进入 NORMAL
模式)。要编辑文本,请按 i
进入 INSERT
模式,在此插入文本。按 ESC
返回 NORMAL
模式 :)
更新:这是来自 github 列表的一个很好的链接 How to undo (almost) anything with git
git push --force
?
git push --force
所做的是用您的本地提交覆盖远程提交。这不是本主题的情况:)
修改旧的或多个提交消息的消息
git rebase -i HEAD~3
上面显示了当前分支上最后 3 次提交的列表,如果您想要更多,请将 3 更改为其他内容。该列表将类似于以下内容:
pick e499d89 Delete CNAME
pick 0c39034 Better README
pick f7fde4a Change the commit message but push the same commit.
在您要更改的每条提交消息之前将 pick 替换为 reword 。假设您更改了列表中的第二个提交,您的文件将如下所示:
pick e499d89 Delete CNAME
reword 0c39034 Better README
pick f7fde4a Change the commit message but push the same commit.
保存并关闭提交列表文件,这将弹出一个新的编辑器供您更改提交消息,更改提交消息并保存。
最后,强制推送修改后的提交。
git push --force
完全非交互式命令(1)
我只是想我会分享一个我为此使用的别名。它基于非交互式交互式变基。要将其添加到您的 git,请运行以下命令(解释如下):
git config --global alias.amend-to '!f() { SHA=`git rev-parse "$1"`; git commit --fixup "$SHA" && GIT_SEQUENCE_EDITOR=true git rebase --interactive --autosquash "$SHA^"; }; f'
或者,一个也可以处理未暂存文件的版本(通过存储然后取消存储它们):
git config --global alias.amend-to '!f() { SHA=`git rev-parse "$1"`; git stash -k && git commit --fixup "$SHA" && GIT_SEQUENCE_EDITOR=true git rebase --interactive --autosquash "$SHA^" && git stash pop; }; f'
这个命令的最大优点是它没有 vim。
(1)假设在变基期间没有冲突,当然
用法
git amend-to <REV> # e.g.
git amend-to HEAD~1
git amend-to aaaa1111
恕我直言,名称 amend-to
似乎很合适。将流程与 --amend
进行比较:
git add . && git commit --amend --no-edit
# vs
git add . && git amend-to <REV>
解释
git config --global alias.
f() { <身体> }; f - “匿名” bash 函数。
SHA=`git rev-parse "$1"`; - 将参数转换为 git 修订版,并将结果分配给变量 SHA
git commit --fixup "$SHA" - SHA 的修复提交。请参阅 git-commit 文档
GIT_SEQUENCE_EDITOR=true git rebase --interactive --autosquash "$SHA^" git rebase --interactive "$SHA^" 部分已被其他答案覆盖。 --autosquash 是与 git commit --fixup 一起使用的,有关更多信息,请参阅 git-rebase 文档 GIT_SEQUENCE_EDITOR=true 是使整个事物非交互的原因。我从这篇博文中学到了这个技巧。
git rebase --interactive "$SHA^" 部分已被其他答案覆盖。
--autosquash 与 git commit --fixup 一起使用,有关更多信息,请参阅 git-rebase 文档
GIT_SEQUENCE_EDITOR=true 使整个事情变得非交互式。我从这篇博文中学到了这个技巧。
amend-to
处理未暂存的文件:git config --global alias.amend-to '!f() { SHA=
git rev-parse "$1"; git stash -k && git commit --fixup "$SHA" && GIT_SEQUENCE_EDITOR=true git rebase --interactive --autosquash "$SHA^" && git stash pop; }; f'
如果由于某种原因您不喜欢交互式编辑器,您可以使用 git rebase --onto
。
假设您要修改 Commit1
。首先,从 before Commit1
分支:
git checkout -b amending [commit before Commit1]
其次,用 cherry-pick
抓取 Commit1
:
git cherry-pick Commit1
现在,修改您的更改,创建 Commit1'
:
git add ...
git commit --amend -m "new message for Commit1"
最后,在隐藏任何其他更改之后,将其余的提交移植到 master
到您的新提交之上:
git rebase --onto amending Commit1 master
阅读:“rebase,到分支 amending
,Commit1
(不包括)和 master
(包括)之间的所有提交”。即 Commit2 和 Commit3,将旧的 Commit1 完全剔除。你可以挑选它们,但这种方式更容易。
记得清理你的树枝!
git branch -d amending
git checkout -b amending Commit1~1
获取先前的提交
git checkout -b amending Commit1
?
git stash
+ rebase
自动化
因为当我需要为 Gerrit 审查多次修改旧提交时,我一直在做:
git-amend-old() (
# Stash, apply to past commit, and rebase the current branch on to of the result.
current_branch="$(git rev-parse --abbrev-ref HEAD)"
apply_to="$1"
git stash
git checkout "$apply_to"
git stash apply
git add -u
git commit --amend --no-edit
new_sha="$(git log --format="%H" -n 1)"
git checkout "$current_branch"
git rebase --onto "$new_sha" "$apply_to"
)
用法:
修改源文件,如果已经在 repo 中就不需要 git add
git-amend-old $old_sha
我喜欢 --autosquash
,因为它不会压缩其他不相关的修复。
git amend
的默认选项,以使用当前存储将更改应用于特定提交,非常聪明!
最好的选择是使用“交互式变基命令”。
git rebase 命令非常强大。它允许您编辑提交消息、合并提交、重新排序......等等。每次您重新提交提交时,都会为每个提交创建一个新的 SHA,无论内容是否会更改!使用此命令时应小心,因为它可能会产生重大影响,尤其是在您与其他开发人员合作时。他们可能会在您重新设置一些基础时开始处理您的提交。在您强制推送提交后,它们将不同步,您稍后可能会在混乱的情况下发现。所以要小心!建议在变基之前创建一个备份分支,这样每当您发现事情失控时,您都可以返回到以前的状态。
现在如何使用这个命令?
git rebase -i <base>
-i
代表“交互式”。请注意,您可以在非交互模式下执行变基。前任:
#interactivly rebase the n commits from the current position, n is a given number(2,3 ...etc)
git rebase -i HEAD~n
HEAD
表示您当前的位置(也可以是分支名称或提交 SHA)。 ~n
表示“n beforeé”,因此 HEAD~n
将是您当前所在的提交之前的“n”个提交列表。
git rebase
有不同的命令,例如:
p 或选择保持原样提交。
r 或 reword:保留提交的内容但更改提交消息。
s 或 squash:将此提交的更改合并到上一个提交中(列表中它上面的提交)。
... 等等 注意:最好让 Git 与您的代码编辑器一起工作,以使事情变得更简单。例如,如果您使用可视代码,您可以像这样添加 git config --global core.editor "code --wait"。或者您可以在 Google 中搜索如何将您喜欢的代码编辑器与 GIT 相关联。
git rebase 示例
我想更改我所做的最后 2 次提交,所以我处理如下:
显示当前提交:#This 在一行上显示所有提交 $git log --oneline 4f3d0c8 (HEAD -> 文档) docs: Add project description and included files" 4d95e08 docs: Add created date and project title" eaf7978 (origin /master , origin/HEAD, master) 初始提交 46a5819 创建 README.md 现在我使用 git rebase 更改最后的 2 个提交消息: $git rebase -i HEAD~2 它打开代码编辑器并显示:pick 4d95e08 文档:添加创建日期和项目标题 pick 4f3d0c8 文档:添加项目描述和包含的文件 # Rebase eaf7978..4f3d0c8 到 eaf7978(2 个命令)# # 命令:# p, pick
自动交互式 rebase 编辑,然后提交恢复准备好进行重做
我发现自己经常修复过去的提交,以至于我为它编写了一个脚本。
这是工作流程:
git commit-edit
为使上述操作生效,请将以下脚本放入 $PATH
中某处名为 git-commit-edit
的可执行文件中:
#!/bin/bash
set -euo pipefail
script_name=${0##*/}
warn () { printf '%s: %s\n' "$script_name" "$*" >&2; }
die () { warn "$@"; exit 1; }
[[ $# -ge 2 ]] && die "Expected single commit to edit. Defaults to HEAD~"
# Default to editing the parent of the most recent commit
# The most recent commit can be edited with `git commit --amend`
commit=$(git rev-parse --short "${1:-HEAD~}")
message=$(git log -1 --format='%h %s' "$commit")
if [[ $OSTYPE =~ ^darwin ]]; then
sed_inplace=(sed -Ei "")
else
sed_inplace=(sed -Ei)
fi
export GIT_SEQUENCE_EDITOR="${sed_inplace[*]} "' "s/^pick ('"$commit"' .*)/edit \\1/"'
git rebase --quiet --interactive --autostash --autosquash "$commit"~
git reset --quiet @~ "$(git rev-parse --show-toplevel)" # Reset the cache of the toplevel directory to the previous commit
git commit --quiet --amend --no-edit --allow-empty # Commit an empty commit so that that cache diffs are un-reversed
echo
echo "Editing commit: $message" >&2
echo
采用了这种方法(它可能与使用交互式 rebase 完全相同),但对我来说这很简单。
注意:我提出这种方法是为了说明您可以做什么,而不是日常替代方案。因为它有很多步骤(可能还有一些警告。)
假设您要更改提交 0
并且您当前在 feature-branch
some-commit---0---1---2---(feature-branch)HEAD
签出此提交并创建一个 quick-branch
。您还可以克隆您的功能分支作为恢复点(在开始之前)。
?(git checkout -b feature-branch-backup)
git checkout 0
git checkout -b quick-branch
您现在将拥有如下内容:
0(quick-branch)HEAD---1---2---(feature-branch)
阶段变化,藏匿一切。
git add ./example.txt
git stash
提交更改并结帐回 feature-branch
git commit --amend
git checkout feature-branch
您现在将拥有如下内容:
some-commit---0---1---2---(feature-branch)HEAD
\
---0'(quick-branch)
将 feature-branch
重新设置为 quick-branch
(解决沿途的任何冲突)。应用存储并删除 quick-branch
。
git rebase quick-branch
git stash pop
git branch -D quick-branch
你最终得到:
some-commit---0'---1'---2'---HEAD(feature-branch)
Git 不会在变基时复制 0 提交(尽管我不能真正说出在多大程度上)。
注意:所有提交哈希都从我们最初打算更改的提交开始更改。
要获取非交互式命令,请将包含此内容的脚本放在 PATH 中:
#!/bin/sh
#
# git-fixup
# Use staged changes to modify a specified commit
set -e
cmt=$(git rev-parse $1)
git commit --fixup="$cmt"
GIT_EDITOR=true git rebase -i --autosquash "$cmt~1"
通过暂存更改(使用 git add
)使用它,然后运行 git fixup <commit-to-modify>
。当然,如果发生冲突,它仍然是交互式的。
我解决了这个,
1)通过创建带有我想要的更改的新提交..
r8gs4r commit 0
2)我知道我需要与哪个提交合并。这是提交3。
所以,git rebase -i HEAD~4
# 4 代表最近的 4 次提交(这里的第 3 次提交在第 4 位)
3) 在交互式变基中,最近的提交将位于底部。它看起来很像,
pick q6ade6 commit 3
pick vr43de commit 2
pick ac123d commit 1
pick r8gs4r commit 0
4)在这里,如果您想与特定的合并,我们需要重新安排提交。它应该是这样的,
parent
|_child
pick q6ade6 commit 3
f r8gs4r commit 0
pick vr43de commit 2
pick ac123d commit 1
重新排列后,您需要将 p
pick
替换为 f
(fixup 将在没有提交消息的情况下合并)或 s
(squash 与提交消息合并可以更改在运行时)
然后保存你的树。
现在与现有提交合并完成。
注意:除非您自己维护,否则它不是可取的方法。如果您的团队规模很大,那么重写 git 树的方法不是可接受的方法,最终会出现您知道其他人不会发生的冲突。如果你想用更少的提交来保持你的树干净,可以试试这个,如果它的小团队,否则它不是可取的.....
更改最后一次提交:
git commit --amend
// or
git commit --amend -m "an updated commit message"
不要修改公共提交 修改后的提交实际上是全新的提交,之前的提交将不再在您当前的分支上。
例如,如果您想更改最后三个提交消息,或该组中的任何提交消息,您将作为参数提供给 git rebase -i 您要编辑的最后一个提交的父级,即 HEAD~2 ^ 或 HEAD~3。记住 ~3 可能更容易,因为您正在尝试编辑最后三个提交,但请记住,您实际上是在指定四个提交之前,即您要编辑的最后一个提交的父级:
$ git rebase -i HEAD~3
对我来说,这是为了从回购中删除一些凭据。我尝试变基并在尝试变基时遇到了大量看似无关的冲突--继续。不要费心尝试重新设置自己的基础,在 mac 上使用名为 BFG (brew install bfg) 的工具。
如果您尚未推送提交,那么您可以使用 git reset HEAD^[1,2,3,4...]
返回上一个提交
例如
git commit <file1> -m "Updated files 1 and 2"
git commit <file3> -m "Updated file 3"
糟糕,忘记将 file2 添加到第一个提交中...
git reset HEAD^1 // because I only need to go back 1 commit
git add <file2>
这会将 file2 添加到第一个提交中。
好吧,这个解决方案可能听起来很傻,但在某些情况下可以拯救你。
我的一个朋友刚刚遇到不小心提交了一些非常大的文件(四个自动生成的文件,每个文件的大小在 3GB 到 5GB 之间),然后在此之上提交了一些额外的代码,然后才意识到问题是 { 1} 不再工作了!
这些文件已在 .gitignore
中列出,但在重命名容器文件夹后,它们被公开并提交!现在在此之上还有一些代码提交,但 push
一直在运行(尝试上传 GB 的数据!),最后由于 Github's file size limits 而失败。
交互式变基或任何类似的问题是他们会处理这些巨大的文件,并且需要永远做任何事情。然而,在 CLI 中花费了近一个小时后,我们不确定文件(和增量)是否实际上已从历史记录中删除,或者根本不包含在当前提交中。推动也不起作用,我的朋友真的被卡住了。
所以,我想出的解决方案是:
将当前 git 文件夹重命名为 ~/Project-old。再次从 github 克隆 git 文件夹(到 ~/Project)。结帐到同一分支。手动 cp -r 将 ~/Project-old 文件夹中的文件复制到 ~/Project.确保不需要签入的大量文件已被 mved,并正确包含在 .gitignore 中。还要确保不要用旧的 ~/Project 覆盖最近克隆的 ~/Project 中的 .git 文件夹。那就是有问题的历史记录所在的地方!现在查看更改。它应该是所有最近提交的联合,不包括有问题的文件。最后提交更改,很高兴被推送。
该解决方案的最大问题是,它处理手动复制一些文件,并且还将所有最近的提交合并为一个(显然使用新的提交哈希。) B
最大的好处是,每一步都非常清晰,它适用于大文件(以及敏感文件),并且不会在历史上留下任何痕迹!
不定期副业成功案例分享
git rebase -i
中使用reword
操作而不是edit
(它会自动打开编辑器并继续执行其余的变基步骤;这避免了使用git commit --ammend
和 { 5} 当您只需要更改提交消息而不是内容时)。git rebase
之前运行git stash
并在之后运行git stash pop
。git commit --all --amend --no-edit
。在git rebase -i ...
之后我所要做的就是正常地git commit --amend
然后git rebase --continue
。