如何修改指定的提交?

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

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

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

答案

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

$ git rebase --interactive 'bbc643cd^'

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

在默认编辑器中,修改pick以在提到 “bbc643cd” 的行中进行edit

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

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

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

之后,输入:

$ git rebase --continue

返回上一个 HEAD 提交。

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

使用超赞的交互式基础:

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

找到所需的提交,将pick更改为eedit ),然后保存并关闭文件。 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更改。

互动变基--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 而不是提交消息,因此使用"fixup! e8adec4甚至只是提交消息的前缀。

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

$ git rebase e8adec4^ -i --autosquash

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

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

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

跑:

$ git rebase --interactive commit_hash^

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

使用 Vim 你换的话pickreword为要改变的提交,保存并退出( :wq )。然后 git 会提示您每个标记为 reword 的提交,以便您可以更改提交消息。

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

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

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

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

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

假设您要修改Commit1 。首先,从Commit1 之前 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

读取:“将Commit1 (非包含)和master (包含)之间的所有提交Commit1到分支进行amending ”。也就是说,Commit2 和 Commit3,完全淘汰了旧的 Commit1。您可以挑选它们,但是这种方式更容易。

记住要清理您的分支!

git branch -d amending

根据文档

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

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

完全非交互式命令(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>' - 创建一个名为<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^"部分已被其他答案覆盖。
    • --autosquashgit commit --fixup结合使用,有关更多信息,请参见git-rebase文档
    • GIT_SEQUENCE_EDITOR=true是使整个事物不交互式的原因。我从这篇博客文章中学到了这种技巧。

来到了这种方法(它可能与使用交互式 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 提交(尽管我不能真正说到什么程度)。

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

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

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

这是工作流程:

  1. git commit-edit <commit-hash>

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

  2. 首先修复并分阶段提交。

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

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

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

    git rebase --continue

为了使以上内容有效,请将以下脚本放入$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

要获取非交互式命令,请将具有以下内容的脚本放入 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> 。当然,如果发生冲突,它将仍然是交互式的。