2766

我通常会提交一份提交列表以供审核。如果我有以下提交:

  1. HEAD
  2. Commit3
  3. Commit2
  4. Commit1

...我知道我可以使用git commit --amend. Commit1但是,鉴于它不是提交,我该如何修改HEAD

4

17 回答 17

3722

您可以使用git rebase。例如,如果要修改 commit bbc643cd,请运行

$ git rebase --interactive 'bbc643cd^'

请注意^命令末尾的插入符号,因为您实际上需要重新定位到要修改的提交之前的提交。

在默认编辑器中,在提及“bbc643cd”的行中修改pickedit

保存文件并退出:git 将解释并自动执行文件中的命令。您会发现自己处于刚刚创建 commit 的先前情况bbc643cd

此时,bbc643cd是您的最后一次提交,您可以轻松修改它:进行更改,然后使用以下命令提交:

$ git commit --all --amend --no-edit

之后,键入:

$ git rebase --continue

返回到之前的 HEAD 提交。

警告:请注意,这将更改该提交以及所有子项的 SHA-1 - 换句话说,这将重写从那时起的历史记录。如果你使用命令推送,你可以打破 reposgit push --force

于 2009-07-27T05:28:02.240 回答
609

使用很棒的交互式变基:

git rebase -i @~9   # Show the last 9 commits in a text editor

找到您想要的提交,更改picke( edit),然后保存并关闭文件。Git 将回退到该提交,允许您:

  • 用于git commit --amend进行更改,或
  • 用于git reset @~放弃最后一次提交,但不放弃对文件的更改(即带您回到编辑文件但尚未提交时的位置)。

后者对于做更复杂的事情很有用,比如拆分成多个提交。

然后,运行git rebase --continue,Git 将在您修改的提交之上重播后续更改。您可能会被要求修复一些合并冲突。

注意:@是 的简写HEAD~是指定提交之前的提交。

在 Git 文档中阅读有关重写历史的更多信息。


不要害怕变基

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 不像大多数现代文本编辑器那样工作,所以看看如何使用 Vim 变基。如果您想使用其他编辑器,请使用git config --global core.editor your-favorite-text-editor.

于 2015-04-29T17:50:53.873 回答
111

当我需要更深入地修复以前的提交时,我经常使用交互式rebase --autosquash它从本质上加快了 ZelluX 的回答所说明的过程,并且当您需要编辑多个提交时特别方便。

从文档中:

--autosquash

当提交日志消息以“squash!...​”(或“fixup!...​”)开头,并且有一个标题以相同的...开头的提交时,自动修改 rebase -i 的 todo 列表,以便提交在要修改的提交之后立即标记为挤压

假设您的历史记录如下所示:

$ 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

您需要做的就是保存并退出

于 2015-09-29T17:59:45.897 回答
51

跑:

$ git rebase --interactive commit_hash^

each^表示您要编辑多少次提交,如果它只有一个(您指定的提交哈希),那么您只需添加一个^.

使用 Vim,您可以更改要更改的提交的单词pickreword保存和退出(:wq)。然后 git 将提示您标记为 reword 的每个提交,以便您可以更改提交消息。

您必须保存每条提交消息并退出(:wq)以转到下一条提交消息

如果您想退出而不应用更改,请按:q!

编辑vim:在你使用j向上、k向下、h向左和l向右导航(所有这些都在NORMAL模式下,按下ESC进入NORMAL模式)。要编辑文本,i请按 进入INSERT插入文本的模式。按ESC返回NORMAL模式 :)

更新:这是来自 github 列表的一个很好的链接如何使用 git 撤消(几乎)任何事情

于 2015-07-02T19:11:31.507 回答
39

基于文档

修改旧的或多个提交消息的消息

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
于 2018-05-17T08:38:57.537 回答
26

完全非交互式命令(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'

该命令的最大优点是它是no-vim


(1)鉴于 rebase 期间没有冲突,当然

用法

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.<NAME> '!<COMMAND>'- 创建一个<NAME>将执行非 git 命令的全局 git 别名<COMMAND>
  • f() { <BODY> }; 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是什么使整个事情非交互。我从这篇博文中学到了这个技巧。
于 2018-02-27T01:47:51.570 回答
21

如果由于某种原因您不喜欢交互式编辑器,您可以使用git rebase --onto.

说你要修改Commit1. 首先,从之前 分支Commit1

git checkout -b amending [commit before Commit1]

其次,Commit1抓住cherry-pick

git cherry-pick Commit1

现在,修改您的更改,创建Commit1'

git add ...
git commit --amend -m "new message for Commit1"

最后,在隐藏任何其他更改之后,将其余提交移植到master新提交之上:

git rebase --onto amending Commit1 master

阅读:“rebase,到分支上, (非包含)和(包含)amending之间的所有提交”。即 Commit2 和 Commit3,将旧的 Commit1 完全剔除。你可以挑选它们,但这种方式更容易。Commit1master

记得清理你的树枝!

git branch -d amending
于 2016-10-22T12:19:44.887 回答
12

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"
)

GitHub 上游.

用法:

  • git add修改源文件,如果已经在 repo中则不需要
  • git-amend-old $old_sha

我喜欢这个,--autosquash因为它不会压制其他不相关的修复。

于 2018-12-03T16:02:43.910 回答
11

最好的选择是使用"Interactive rebase command"

git rebase命令非常强大。它允许您编辑 提交消息、合并提交、重新排序......等等。

每次您重新提交提交时,都会为每个提交创建一个新的 SHA,无论内容是否会更改!使用此命令时应小心,因为它可能会产生重大影响,尤其是在您与其他开发人员合作时。他们可能会在您重新设置一些基础时开始处理您的提交。在您强制推送提交后,它们将不同步,您稍后可能会在混乱的情况下发现。所以要小心!

建议在变基之前创建一个backup分支,这样每当您发现事情失控时,您都可以返回到以前的状态。

现在如何使用这个命令?

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有不同的命令,如:

  • ppick保持原样。
  • rreword:保留提交的内容但更改提交消息。
  • ssquash:将此提交的更改合并到上一个提交中(列表中它上面的提交)。
  • ... ETC。

    注意:最好让 Git 与您的代码编辑器一起工作,以使事情变得更简单。例如,如果您使用可视代码,您可以像这样添加git config --global core.editor "code --wait"。或者您可以在 Google 中搜索如何将您喜欢的代码编辑器与 GIT 相关联。

示例git rebase

我想更改我所做的最后 2 次提交,所以我处理如下:

  1. 显示当前提交:
    #This to show all the commits on one line
    $git log --oneline
    4f3d0c8 (HEAD -> documentation) docs: Add project description and included files"
    4d95e08 docs: Add created date and project title"
    eaf7978 (origin/master , origin/HEAD, master) Inital commit
    46a5819 Create README.md
    
  2. 现在我git rebase用来更改最后的 2 个提交消息: $git rebase -i HEAD~2 它打开代码编辑器并显示:

    pick 4d95e08 docs: Add created date and project title
    pick 4f3d0c8 docs: Add project description and included files
    
    # Rebase eaf7978..4f3d0c8 onto eaf7978 (2 commands)
    #
    # Commands:
    # p, pick <commit> = use commit
    # r, reword <commit> = use commit, but edit the commit message
    ...
    

    因为我想更改这 2 个提交的提交消息。所以我会输入rorreword代替pick. 然后保存文件并关闭选项卡。请注意,这rebase是在多步骤过程中执行的,因此下一步是更新消息。另请注意,提交按时间倒序显示,因此最后一次提交显示在该提交中,第一次提交显示在第一行,依此类推。

  3. 更新消息:更新第一条消息:

    docs: Add created date and project title to the documentation "README.md"
    
    # Please enter the commit message for your changes. Lines starting
    # with '#' will be ignored, and an empty message aborts the commit.
    ...
    

    保存并关闭 编辑第二条消息

    docs: Add project description and included files to the documentation "README.md"
    
    # Please enter the commit message for your changes. Lines starting
    # with '#' will be ignored, and an empty message aborts the commit.
    ...
    

    保存并关闭。

  4. 在 rebase 结束时你会收到这样的消息:Successfully rebased and updated refs/heads/documentation这意味着你成功了。您可以显示更改:

    5dff827 (HEAD -> documentation) docs: Add project description and included files to the documentation "README.md"
    4585c68 docs: Add created date and project title to the documentation "README.md"
    eaf7978 (origin/master, origin/HEAD, master) Inital commit
    46a5819 Create README.md
    

    我希望这可以帮助新用户:)。

于 2019-11-15T17:59:53.417 回答
9

自动交互式 rebase 编辑,然后提交恢复准备好进行重做

我发现自己经常修复过去的提交,以至于我为它编写了一个脚本。

这是工作流程:

  1. git commit-edit <commit-hash>
    

    这将使您进入要编辑的提交。

  2. 修复并按您希望的方式暂存提交。

    (您可能想用来git stash save保留您未提交的任何文件)

  3. 用 重做提交--amend,例如:

    git commit --amend
    
  4. 完成变基:

    git rebase --continue
    

为使上述工作正常进行,请将以下脚本放入git-commit-edit您的某处名为的可执行文件中$PATH

#!/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
于 2018-09-14T03:32:42.740 回答
7

采用了这种方法(它可能与使用交互式 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-branchquick-branch解决沿途的任何冲突)。应用 stash 并删除quick-branch.

git rebase quick-branch
git stash pop
git branch -D quick-branch

你最终得到:

some-commit---0'---1'---2'---HEAD(feature-branch)

Git 不会在变基时复制 0 提交(尽管我不能真正说出在多大程度上)。

注意:所有提交哈希都从我们最初打算更改的提交开始更改。

于 2016-06-01T11:57:58.397 回答
6

要获取非交互式命令,请将包含此内容的脚本放在 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>​​. 当然,如果发生冲突,它仍然是交互式的。

于 2018-01-16T15:27:09.477 回答
5

我解决了这个,

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 pickf修复将在没有提交消息的情况下合并)或s(与提交消息合并的壁球可以在运行时更改)

然后保存你的树。

现在与现有提交合并完成。

注意:除非您自己维护,否则它不是可取的方法。如果您的团队规模很大,那么重写 git 树的方法不是可接受的方法,最终会出现您知道其他人不会发生的冲突。如果你想用更少的提交来保持你的树干净,可以试试这个,如果它的小团队,否则它不是可取的.....

于 2018-01-05T18:35:50.833 回答
2

对我来说,这是为了从回购中删除一些凭据。我尝试变基并在尝试变基时遇到了大量看似无关的冲突--继续。不要费心尝试重新设置自己的基础,在 mac 上使用名为 BFG (brew install bfg) 的工具。

于 2017-11-07T07:24:20.803 回答
0

如果您还没有推送提交,那么您可以使用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 添加到第一个提交中。

于 2019-05-08T13:30:01.707 回答
0

好吧,这个解决方案可能听起来很傻,但在某些情况下可以拯救你。

我的一个朋友刚刚意外提交了一些非常大的文件(四个自动生成的文件,每个文件的大小在 3GB 到 5GB 之间),然后在此基础上提交了一些额外的代码,然后才意识到问题git push不再有效!

这些文件已被列出,.gitignore但在重命名容器文件夹后,它们被暴露并提交了!现在在此之上还有一些代码的提交,但push一直在运行(试图上传 GB 的数据!),最后由于Github 的文件大小限制而失败。

交互式变基或任何类似的问题是他们会处理这些巨大的文件,并且需要永远做任何事情。然而,在 CLI 中花费了近一个小时后,我们不确定文件(和增量)是否实际上已从历史记录中删除,或者根本不包含在当前提交中。推动也不起作用,我的朋友真的被卡住了。

所以,我想出的解决方案是:

  1. 将当前 git 文件夹重命名为~/Project-old.
  2. 再次从 github 克隆 git 文件夹(到~/Project)。
  3. 结帐到同一分支。
  4. 手动cp -r将文件~/Project-old夹中的文件添加到~/Project.
  5. mv确保编辑并正确包含不需要签入的大量文件.gitignore
  6. 还要确保您不会覆盖旧.git文件夹中最近克隆的文件夹。~/Project那就是有问题的历史记录所在的地方!
  7. 现在查看更改。它应该是所有最近提交的联合,不包括有问题的文件。
  8. 最后提交更改,很高兴被push编辑。

该解决方案的最大问题是,它处理手动复制一些文件,并且还将所有最近的提交合并为一个(显然使用新的提交哈希。) B

最大的好处是,每一步都非常清晰,它适用于大文件(以及敏感文件),并且不会留下任何历史痕迹!

于 2019-06-12T09:47:05.917 回答
0

更改最后一次提交:

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

了解更多

于 2021-12-05T21:00:44.020 回答