【问题标题】:How to undo multiple “git commit --amend" or to get difference for each amend commit?如何撤消多个“git commit --amend”或为每个修改提交获取差异?
【发布时间】:2019-02-28 08:17:10
【问题描述】:

我错误地做了超过 100 次修改提交。我怎样才能将它们转换为通常的提交?或者至少要为每个修改提交获得不同的 git log? 如果我现在运行gitkgit log -p,我一次只能看到所有修改提交的区别。我可以看到所有修改提交的哈希值,但只能使用 reflog。我也可以看到与 git diff amend_hash1 amend_hash2 的差异,但在 gitkgit log -p 中看不到。尽管它们在 .git/logs/refs/heads/master.git/logs/HEAD 中正确链接,但它们跳过了这些修改

我只运行了 git commit --amend 100 次,每次更改 100 次。然后,当我在没有修改的情况下运行 git commit 时,我对所有 100 个更改都进行了一次大提交。

我找到了如何只撤消一个修改提交...

【问题讨论】:

  • 修改后的提交是“通常的”提交。像往常一样进行的提交和修改后的提交之间没有真正的区别。你能用一个简短的例子来解释你做了什么,你想做什么或得到什么?
  • 这些提交在 gitk 中显示为一个提交
  • 修改 100 次提交是不寻常的。你把它们压扁了吗?那是完全不同的事情。你能显示你运行的确切命令吗?
  • 请添加您运行的确切命令以“修改”提交。
  • @l0pan 这只会修改一个提交。你还跑了什么让它修改100?顺便说一句,使用git filter-branch 会更好、更安全。

标签: git commit undo git-amend


【解决方案1】:

它们都在进行修改的 repo 的 reflog 中,它们是完全普通的提交,因此您可以例如git show他们看看他们做了什么改变,git cherry-pick他们抓住这些改变等等。请参阅git reflog 文档,并查看例如.git/logs/refs/heads/master 看看它在争论什么。

【讨论】:

  • 使用cherry-pick会很痛苦;他们每个人都应该有正确的树,所以使用git commit-tree 应该很轻松。
  • 如果它们是普通提交,那么为什么 gitkgit log -p 忽略它们?尽管它们在 .git/logs/refs/heads/master 中正确链接,但它们跳过了这些修改
  • Reflog 是所指事物的历史记录。如果您希望 gitk 和 git log 显示您在过去 .. 我忘记了,一个月左右?无论某些当前 ref 是否仍然可以访问它,请将 --reflog 添加到任一命令。试试git log --reflog --oneline --no-walk 玩。
【解决方案2】:

这些提交在 gitk 中显示为一个提交 – l0pan

我怀疑实际发生的情况是您将 100 个提交压缩在一起,可能是 git merge --squash。你很幸运,这比实际修改 100 次提交要容易得多(这将是非常不寻常的)。我们需要找到原来的分支负责人。

如果你幸运的话,ORIG_HEAD 仍然设置为原来的分支头。请与git log ORIG_HEAD联系。

否则,在进行壁球合并之前,请使用 git reflog 查找分支的尖端。你正在寻找这样的东西......

a83ad6b (master) HEAD@{1}: commit: Squashed commit of the following:
78c06ed HEAD@{2}: checkout: moving from feature to master

a83ad6b 是新的压缩提交。 78c06ed 是原始的、未压扁的分支尖端。

找到原始提交后,将分支移回原处。

git branch -f <commit id>

如果您确实修改了 100 个提交,那么您会如何找到它们。

您将使用git reflog 来查找原始未修改的提交。你正在寻找这样的东西......

6493a4c HEAD@{2}: commit (amend): The log message
a2a99ea HEAD@{3}: commit: The log message

a2a99ea 是原始的、未修改的提交。 6493a4c 是修改后的新提交。

一般来说,您应该能够使用 grep 查找 commit (amend) 并使用之前的 reflog 和相同的提交消息。但是,这并不能保证。例如,在提交和修改之间可能存在签出和拉取。

0742a3a HEAD@{111}: commit (amend): chore: Add a simple monitoring process.
b71620d HEAD@{112}: pull: checkout e29402b1fda83664cf17782b1a34e2bb4a77d44f: returning to refs/heads/master
b71620d HEAD@{113}: pull: checkout e29402b1fda83664cf17782b1a34e2bb4a77d44f: chore: Add a simple monitoring process.
e29402b HEAD@{114}: pull: checkout e29402b1fda83664cf17782b1a34e2bb4a77d44f
fab188c HEAD@{115}: commit: chore: Add a simple monitoring process.

或者修改可能会更改提交消息。

6493a4c HEAD@{2}: commit (amend): A different log message
a2a99ea HEAD@{3}: commit: The log message

【讨论】:

  • 是的,我可以通过 reflog 看到他们的哈希值。但我想在 gitk 或 'git log -p' 中看到它们
  • @l0pan 不知道你做了什么来一次修改 100 个提交,我无法告诉你如何恢复它们。
  • 我只为 100 次更改中的每一次运行了 100 次“git commit --amend”。我没有运行合并。然后我在没有修改的情况下运行了提交,并为所有 100 个更改获得了一个大提交
  • @l0pan 最后一个不应该发生。请编辑您的问题以显示确切您运行了哪些命令。最好从您的终端复制和粘贴。
  • 你确定你最后一个没有--amendgit commit真的做了任何事情吗?如果没有阶段性更改,那么git commit 基本上是空操作。
【解决方案3】:

我认为这可以用图片来解释,虽然我的 ASCII 艺术能力在几个--amends 之后就耗尽了。诀窍是要意识到 git commit --amend 所做的,与没有 --amendgit commit 相比,是更改存储在新提交中的 父哈希

普通的git commit 将索引内容冻结到树中,并使用新树进行新提交,您作为作者和提交者,您的日志消息和 当前提交 作为父级新提交的:

...--F--G   <-- [you were here]
         \
          H   <-- branch (HEAD) [you are here now]

然后我们在理顺绘图后,重新提交I

...--F--G--H--I   <-- branch (HEAD)

和新的提交 J:

...--F--G--H--I--J   <-- branch (HEAD)

等等。

然而,使用git commit --amend,我们像往常一样进行新的提交H除了HF而不是@987654334 @:

       G   [you were here]
      /
...--F--H   <-- branch (HEAD)

然后,创建I,我们像往常一样除了IF而不是H

       G   [you were here]
      /
...--F--H   [you were here too]
      \
       I   <-- branch (HEAD)

等等。

如果您想在此时运行git log(提交I 指向提交F),您将看到提交I,然后提交F,然后是E,依此类推.提交 GH 将对 gitkgit log 不可见(但将显示在 git reflog 输出中,因为它不遵循父链)。

在进行了 100 次这样的操作之后,我已经用完了提交信,并且不可能吸引所有提交的疯狂粉丝都指向 F,但你可以想象它们;或者我可以只绘制 9 个这样的提交,GO

     GH
     | I
     |/ J
...--F==K
     |\ L
     | M
     ON

这些不同的提交中的每一个都有您想要的 treemessage。除了 G 本身之外,这些提交中的每一个都有什么问题是它有错误的 parent: 你希望 GF 作为它的父级(它确实),但是您希望 HG 作为其父级(它没有)。

这意味着你必须复制错误的提交。让我们首先将H 复制到H',将G 作为其父级,但在其他方面使用与H 相同的message 以及其他元数据:

      H'
     /
     GH
     | I
     |/ J
...--F==K
     |\ L
     | M
     ON

现在我们需要将I 复制到新的提交I'I' 的父级不是G 而是H'

      H'-I'
     /
     GH
     | I
     |/ J
...--F==K
     |\ L
     | M
     ON

我们重复JJ',使用I' 作为J' 的父级,依此类推,直到我们将每个“错误”提交复制到“正确”提交。然后我们可以设置一个分支名称来指向最后一个这样的复制提交:

      H'-I'-J'-K'-L'-M'-N'-O'   <-- repaired
     /
     GH
     | I
     |/ J
...--F==K
     |\ L
     | M
     ON

repairedgitk --all 上运行git log 现在将显示提交N' 导致返回M' 导致返回L' 等等。请记住,git log(和 gitk)向后跟随父链接,根本不查看 reflog。

如果您愿意让某些元数据(作者和提交者姓名、电子邮件和时间戳)被破坏,则可以使用 git commit-tree 使用 shell 脚本循环轻松完成每个提交。如果你想保留那个元数据,那就更难了:你需要在每次调用git commit-tree之前设置一系列Git环境变量,设置:

  • GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, GIT_AUTHOR_DATE: 给作者
  • GIT_COMMITTER_NAMEGIT_COMMITTER_EMAILGIT_COMMITTER_DATE:类似,但针对提交者

日志消息可以直接从不正确的提交中复制,使用sed 或类似的方法来切断所有内容,包括第一个空行(注意这会丢弃任何 i18n 编码数据,sed 可能表现不佳提交消息中有未终止的最终文本行,但这些可能是可以容忍的;如果不是,请从 git filter-branch 中提取相关代码。

使用git reflog 以正确的顺序获取所有要复制的提交的哈希ID(最旧的先复制,最后复制的=最新的最后)。将它们放在一个文件中,每行一个条目。那么下面的 untested shell 脚本可能就足够了:

parent=$hash  # hash ID of commit G, onto which new chain will be built
while read tocopy; do
    tree=$(git rev-parse $tocopy^{tree})
    parent=$(git cat-file -p $tocopy | sed '1,/^$/d' |
        git commit-tree -p $parent -t $tree)
done < $file_of_commits
git branch repaired $parent

这会创建一个新的分支名称repaired 来保存新构建的链。

【讨论】:

  • hmm,那么为什么修正提交在 .git/logs/refs/heads/master.git/logs/HEAD 中正确链接?他们没有一个父母在那里
  • @l0pan:Reflog 条目不使用父链接。每个 reflog 条目命名一个特定的提交,git reflog aka git log -g 查看(或在 Git 术语中,“遍历”)reflog 条目而不是遍历父链。
猜你喜欢
  • 2017-01-08
  • 2017-05-11
  • 2010-11-30
  • 2021-08-14
  • 2016-10-09
  • 2011-07-15
  • 2015-12-04
  • 2017-09-03
相关资源
最近更新 更多