如何修改指定的提交?


2231

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

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

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


31
在此处查看替代答案:stackoverflow.com/a/18150592/520567您接受的答案确实是对问题的确切答案,但是如果在决定使用编辑之前已准备好新提交,则此答案将更加简单。它也可以与多个您希望与较旧的提交合并/压缩的提交一起使用。
akostadinov

6
您也可以Git工具-重写历史记录中看到拆分提交获取更多信息。
13年

Answers:


2951

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

$ git rebase --interactive 'bbc643cd^'

请注意^命令末尾的插入符号,因为实际上您需要要修改的提交之前基准变回到提交

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

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

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

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

之后,输入:

$ git rebase --continue

返回上一个HEAD提交。

警告:请注意,这将更改该提交以及所有子项的SHA-1- 换句话说,这将从该点开始重写历史记录。如果使用命令推送,则可以中断存储库git push --force


125
此流程中的另一个有趣的选项是,一旦您移至要修改的提交,而不是修改文件并在顶部的提交(正在编辑的)上进行修改,您可能希望将该提交分为两个不同的提交(甚至更多)。在这种情况下,移回提交进行编辑,然后运行“ git reset HEAD ^”。这会将提交的修改后的文件放到舞台上。现在,根据需要选择并提交任何文件。“ git-rebase”手册页对此流进行了很好的解释。请参见“拆分提交”部分。 bit.ly/d50w1M
Diego Pino 2010年

200
在Git 1.6.6及更高版本中,您可以使用reword操作git rebase -i代替edit(它会自动打开编辑器并继续其余的变基步骤;这避免了使用,git commit --ammend并且git rebase --continue仅当您需要更改提交消息而不是内容时, )。
克里斯·约翰森

108
值得注意的是,如果您有待处理的更改,则可能需要在运行git stash之前git rebasegit stash pop之后运行。
user123444555621 2013年

3
是否有一个快捷命令来编辑交互式资源库中的特定提交,而无需打开编辑器,查找提交,将其标记为编辑然后放回命令行?
sstur 2014年

15
请注意,使用较新的git时,按照提示进行操作而不是盲目使用git commit --all --amend --no-edit此处更明智。之后git rebase -i ...,我所要做的就是恢复git commit --amend正常git rebase --continue
艾瑞克·陈

452

使用超赞的交互式基础:

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

找到所需的提交,将其更改pickeedit),然后保存并关闭文件。Git将回退到该提交,使您可以:

  • 采用 git commit --amend进行更改,或
  • 用于git reset @~放弃上一次提交,而不是放弃对文件的更改(即,将您带到编辑文件但尚未提交时的状态)。

后者对于执行更复杂的操作(例如拆分为多个提交)很有用。

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

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

在Git文档中了解有关重写历史记录的更多信息。


不要害怕变基

ProTip™:不要害怕尝试使用“危险”命令来重写历史记录* — Git默认不会删除您的提交90天;您可以在参考日志中找到它们:

$ 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


29
您的答案中间是一个奇怪的地方,我只能将其描述为VIM的微型广告。它与问题无关,只会使您的答案混乱不堪。
意向

21
@Intentss:啊,我明白了为什么看起来很奇怪。其背后的原因是,Vim是许多系统上默认的文本编辑器,因此许多人最初对交互式重定基准的体验是在屏幕上打字使光标四处飞来。然后,他们将编辑器切换到其他编辑器,并且他们进行交互变基的第二次体验是很正常的,但是他们想知道为什么它使用文本文件而不是GUI。要实现带基础的流程,您需要类似Vim或Emacs的rebase-mode。
Zaz

9
好的。看到如此多的人认为无关紧要的部分,我将其简化为3行,并解释了如何根据需要更改编辑器。
Zaz

17
太棒了!我不知道您可以@用作的简写HEAD。感谢您发布此信息。
高James

3
git reset @~选择提交后正是我想做的git rebase ...。您是我的英雄)
18年

79

互动变基--autosquash的东西,当我需要更深的修正内容以前犯的历史我经常使用。从本质上讲,它可以加快ZelluX答案说明的过程,并且在需要编辑多个提交时特别方便。

从文档中:

--autosquash

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

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

$ git log --graph --oneline
* b42d293 Commit3
* e8adec4 Commit2
* faaf19f Commit1

并且您有要修改为Commit2的更改,然后使用提交更改

$ git commit -m "fixup! Commit2"

或者,您可以使用commit-sha而不是commit消息,"fixup! e8adec4甚至可以使用commit消息的前缀。

然后在提交之前启动交互式基础

$ git rebase e8adec4^ -i --autosquash

您的编辑器将打开,并已正确订购提交

pick e8adec4 Commit2
fixup 54e1a99 fixup! Commit2
pick b42d293 Commit3

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


21
您也可以使用git commit --fixup=@~代替git commit -m "fixup! Commit2"。当您的提交消息较长时,这非常有用,并且要输入整个内容会很麻烦。
Zaz

42

跑:

$ git rebase --interactive commit_hash^

每个^表示您要编辑的提交数,如果只有一个(您指定的提交哈希),则只需添加一个即可^

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

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

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

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

更新:这是来自github列表的很棒的链接如何使用git撤销(几乎)任何操作


4
为我完美地工作。值得一提git push --force吗?
u01jmg3

什么git push --force是用您的本地提交覆盖远程提交。并非如此:)
betoharres

@BetuUuUu当然,如果您的提交被推送到远程并且您已经在本地修改了提交消息,您将要强制推送到远程,不是吗?
Sudip Bhandari'7

@SudipBhandari这就是我的感觉。我没有强行使用,现在我有了一个额外的分支,将所有提交镜像到我更改了消息的那个提交,这非常丑陋。
ruffin

2
@greenhouse如果您修改并强行推入,则其他团队成员很可能会遇到合并冲突。因此,您通常应该对此保持谨慎。但是,如果您修改了其他人还没有拿到的东西,那应该没问题(不会注意到的)。因此,我将--force作为最后的选择,并始终与其他成员协商回购的状态。
Tomasz Kaczmarzyk,

18

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

说您要修改Commit1。首先,从之前 分支Commit1

git checkout -b amending [commit before Commit1]

二,抢Commit1cherry-pick

git cherry-pick Commit1

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

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

最后,在暂无其他更改之后,将其余的提交移植到master新的提交之上:

git rebase --onto amending Commit1 master

读:“将amending所有提交Commit1(到此,包括(不包括在内)和master(包括)之间)重新建立到分支上”。也就是说,Commit2和Commit3,完全淘汰了旧的Commit1。您可以挑选它们,但是这种方式更容易。

记住要清理您的分支!

git branch -d amending

4
您可以git checkout -b amending Commit1~1用来获取先前的提交
Arin Taylor

前两个步骤是否等效git checkout -b amending Commit1
郝书

16

根据文档

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

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 e499d89 Delete CNAME
reword 0c39034 Better README
pick f7fde4a Change the commit message but push the same commit.

保存并关闭提交列表文件,这将弹出一个新的编辑器,供您更改提交消息,更改提交消息并保存。

Finaly Force-推动修订的提交。

git push --force

我收到以下错误:错误:编辑器“ vi”存在问题。请使用-m或-F选项提供消息。
埃里克·梅纳德

12

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

该命令的最大优势在于它是no-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.<NAME> '!<COMMAND>'-创建一个名为git的全局别名<NAME>,它将执行非git命令<COMMAND>
  • f() { <BODY> }; f -“匿名” bash函数。
  • SHA=`git rev-parse "$1"`; -将参数转换为git版本,并将结果分配给变量 SHA
  • git commit --fixup "$SHA"-fixup-commit 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是什么使整个事情不互动。我从这篇博客文章中学到了这种技巧。

1
也可以制作未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'
Dethariel

2
使用此方法的一个问题是它可能会应用无关的修复程序。
Ciro Santilli冠状病毒审查六四事件法轮功18/12/3

问题的重点不是更改提交消息吗?因为这个答案不能解决这个问题,或者至少不能直接解决。
wytten

@wytten问题不询问有关更改提交消息的问题,它与修改提交有关,不是HEAD。因此,您的问题的答案将是“不,这不是问题的重点”
Dethariel

我很困惑,那么我们如何更改提交消息?
ggb667 '19

8

自动进行交互式基准库编辑,然后进行提交还原以准备进行转换

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

这是工作流程:

  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

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(解决沿途的任何冲突)。应用藏匿处并取出quick-branch

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

结果是:

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

Git不会在重定基时复制0提交(尽管我真的不能说到什么程度)。

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


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>。当然,如果发生冲突,它将仍然是交互式的。


1
这很好。我添加了一些额外的功能来对脏树进行零碎修复,以完善提交集。`dirtydiff = $(git diff); 如果[“ $ {dirtydiff}”!=“”]; 然后回显“隐藏脏树”>&2; git stash; fi;
西蒙·费特曼

6

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如果已经在仓库中则不需要
  • git-amend-old $old_sha

我喜欢这个,--autosquash因为它不会压缩其他无关的修复程序。


非常好的解决方法,这应该是默认选项,git amend可以使用当前存储将更改应用于特定提交,非常聪明!
caiohamamura

5

我解决了

1)通过创建我想要的更改的新提交。

r8gs4r commit 0

2)我知道我需要与之合并的提交。这是提交3。

因此,git rebase -i HEAD~4#4代表最近的4次提交(此处,提交3位于第四位)

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 pickffixup将合并而没有提交消息)或ssquash与commit消息合并可以在运行时更改)

然后保存您的树。

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

注意:除非您自己维护,否则它不是可取的方法。如果您的团队规模很大,那么它是不可接受的重写git tree的方法,将最终导致您知道其他情况不会发生的冲突。如果您想用更少的提交来保持树的干净,则可以尝试此操作,如果它的团队很小,则不推荐.....


如果您不想在交互式变基期间进行实时修改,这是一个很好的解决方案。
Dunatotatos

4

最好的选择是使用“交互式rebase命令”

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手段“Nbeforeé,所以HEAD~n将是列表中的‘一个你目前的前N’的提交。

git rebase 具有不同的命令,例如:

  • ppick保持原样提交。
  • rreword:保留提交内容,但更改提交消息。
  • ssquash:将该提交的更改合并到上一个提交(列表中位于其上方的提交)中。
  • ...等

    注意:最好让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次提交更改提交消息。因此,我将输入rreword代替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
    

    希望对新用户有所帮助:)。


2

对我而言,这是为了从回购协议中删除一些凭证。我尝试重新设定基准,并在尝试重新设定-继续的过程中遇到了许多看似无关的冲突。不要打扰尝试重新设置自己的基础,请在Mac上使用名为BFG(棕色安装bfg)的工具。


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添加到第一次提交。


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编辑。

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.