如何修改特定的提交?

时间:2009-07-27 05:19:08

标签: git git-rewrite-history

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

  1. HEAD
  2. Commit3
  3. Commit2
  4. Commit1
  5. ...我知道我可以使用git commit --amend修改头部提交。但是,我如何修改Commit1,因为它不是HEAD提交?

15 个答案:

答案 0 :(得分:2524)

您可以使用git rebase,例如,如果要修改回提交bbc643cd,请运行

$ git rebase --interactive 'bbc643cd^'

在默认编辑器中,将pick修改为要修改其提交的行中的edit。进行更改,然后使用之前的相同消息提交它们:

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

修改提交,然后修改

$ git rebase --continue

返回上一个头部提交。

警告:请注意,这会更改该提交的SHA-1以及所有子项 - 换句话说,这将从此时开始重写历史记录。如果您使用命令git push --force

推送,请You can break repos doing this

答案 1 :(得分:67)

交互式rebase--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甚至只是提交消息的前缀。

然后在

之前在提交上启动交互式rebase
$ git rebase e8adec4^ -i --autosquash

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

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

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

答案 2 :(得分:35)

执行命令

$ 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列表How to undo (almost) anything with git

的精彩链接

答案 3 :(得分:17)

如果由于某种原因您不喜欢交互式编辑器,则可以使用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上,Commit1(非包含)和master(包括)"之间的所有提交。也就是说,Commit2和Commit3完全削减了旧的Commit1。你可以挑选它们,但这种方式更容易。

记得要清理你的树枝!

git branch -d amending

答案 4 :(得分:10)

完全非交互式命令(1)

我只是想与我分享一个别名,我正在使用它。它基于非交互式交互式rebase。要将它添加到您的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)假设在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 - 一个&#34;匿名&#34; bash功能。
  • SHA=`git rev-parse "$1"`; - 将参数转换为git revision,并将结果分配给变量SHA
  • git commit --fixup "$SHA" - SHA的fixup-commit。请参阅git-commit docs
  • GIT_SEQUENCE_EDITOR=true git rebase --interactive --autosquash "$SHA^"
    • git rebase --interactive "$SHA^"部分已被其他答案所涵盖。
    • --autosquashgit commit --fixup一起使用,有关详细信息,请参阅git-rebase docs
    • GIT_SEQUENCE_EDITOR=true是整个事物非互动的原因。这个黑客我学会了from this blog post

答案 5 :(得分: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-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提交。

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

答案 6 :(得分:7)

自动进行交互式基准库编辑,然后执行提交还原以进行重复操作

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

这是工作流程:

  1. git commit-edit <commit-hash>
    

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

  2. 按您希望的那样修复并暂存提交。

    (您可能希望使用git stash save来保存所有未提交的文件)

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

    git commit --amend
    
  4. 完成rebase:

    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 :(得分: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>。当然,如果你遇到冲突,它仍然是互动的。

答案 8 :(得分:6)

基于Documentation

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

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.

在您要更改的每个提交邮件之前,将选择替换为 reword 。假设您更改了列表中的第二个提交,您的文件将如下所示:

pick e499d89 Delete CNAME
reword 0c39034 Better README
pick f7fde4a Change the commit message but push the same commit.

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

Finaly Force-推动修改后的提交。

git push --force

答案 9 :(得分:4)

我解决了这个问题,

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

r8gs4r commit 0

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

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

3)在交互式rebase中,最近的提交将位于底部。它看起来很像,

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   树将最终陷入你不会知道的冲突中。如果你想   保持你的树清洁与较少的提交可以尝试这个,如果它   小团队,否则不可取.....

答案 10 :(得分:3)

git stash + rebase自动化

当我需要多次修改旧提交以进行Gerrit评论时,我一直在做:

git-amend-to() (
  # 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"
)

用法:

  • 修改文件
  • git-amend-to $old_sha

我喜欢--autosquash,因为它不会挤压其他无关的修正。

答案 11 :(得分:2)

对我而言,它是从回购中删除一些凭据。 在试图改变时,我尝试了变调并遇到了大量看似无关的冲突 - 继续。 不要试图自己动手,在Mac上使用名为BFG(brew install bfg)的工具。

答案 12 :(得分:1)

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

  

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. 在变基结束之前,您将收到以下消息: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
    

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

答案 13 :(得分: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添加到第一次提交中。

答案 14 :(得分:0)

好吧,这个解决方案听起来可能很愚蠢,但是可以在某些情况下为您省钱。

我的一个朋友遇到了意外提交了很多大文件(四个自动生成的文件,每个文件的大小从3GB到5GB),然后在实现之前又做了一些其他代码提交git push不再工作的问题!

文件已在.gitignore中列出,但是在重命名容器文件夹后,它们被公开并提交!现在,除此之外,还提交了一些代码,但是push一直在运行(试图上传GB的数据!),最后由于Github's file size limits而失败。

交互式变基或类似方法的问题在于,它们将处理这些巨大的文件,并且永远需要做任何事情。但是,在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

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