【问题标题】:Undo removal of file from a previous commit撤消从先前提交中删除文件
【发布时间】:2016-12-16 16:47:55
【问题描述】:

我正在使用 gitflow 分支模型进行开发。 我从develop 分支到feature/X,在feature/X 的第一次提交中,我还删除了一些文件(使用git rm <file>)。 现在,在该分支中进行了几次其他提交之后,我意识到我需要之前删除的文件。

这里有一个简短的示例来说明我要澄清的意思:

git flow init -d
echo "file contents" > file.txt
git commit -m "Added file.txt"
git checkout -b feature/X
git rm file.txt
echo "foo" > foo.txt
git add --all
git commit -m "Deleted file.txt and added another file"
<some more commits in feature/X>



git log -u

...

commit 04948fc4fc36d83901a0862b057657f3ccb9cf0d
Author: <...>
Date:   Wed Aug 10 12:26:58 2016 +0200

    Deleted file.txt and added another file

diff --git a/file.txt b/file.txt
deleted file mode 100644
index d03e242..0000000
--- a/file.txt
+++ /dev/null
@@ -1 +0,0 @@
-file contents
diff --git a/foo.txt b/foo.txt
new file mode 100644
index 0000000..257cc56
--- /dev/null
+++ b/foo.txt
@@ -0,0 +1 @@
+foo

...

我不只是想在新的提交中重新添加文件,以避免在将 feature/X 合并到 develop 时出现问题,而 develop 分支中的 file.txt 发生了一些变化。

有什么方法可以从之前的提交中删除file.txt

我尝试了git reset &lt;sha-of-previous-commit&gt; file.txt,但这并没有将文件恢复到我的工作副本中。

编辑 1:
我知道重写历史的不利方面已经被推送到远程。但是我知道除了我之外没有人在 feature/X 上进行过任何提交,因此尽管它已经被推送,但我重写历史记录应该是安全的。

【问题讨论】:

标签: git version-control git-rewrite-history


【解决方案1】:

回答

我不只是想在新提交中重新添加文件,以避免在开发分支中的 file.txt 发生一些更改时合并功能/X 以开发时出现问题。

是的,您确实只想在新提交中重新添加文件。 git 对此很明智(嗯,实际上这里没有涉及特殊的智能,它只是来自 git 处理文件的方式)。

示例

git init test                       # init a repos, create test.txt
cd test
echo start > test.txt
git add -A ; git commit -m 'x'

git checkout -b bra                 # create branch, add a line

echo line1 >> test.txt
git add -A ; git commit -m 'x'

git rm test.txt                     # delete file + commit
git commit -m "x"

echo start > test.txt               # restore file and add a line
echo line2 >> test.txt
git add -A ; git commit -m 'x'

git checkout master                 # back to master, add the same line and another one
echo line2 >> test.txt
echo line3 >> test.txt
git add -A ; git commit -m 'x'

git merge bra                       # merge

cat test.txt
   start
   line2
   <<<<<<< HEAD
   line3
   =======
   >>>>>>> bra

冲突如预期(原文如此!);关于它的重要部分是line2 不是冲突的一部分。 git merge不关心分支上文件发生的所有恶作剧,只关心最终结果。

【讨论】:

  • 所以 tl;dr;是如果我删除一个文件并稍后在同一分支中恢复它 git 在合并期间“忽略”它?在实际合并之前,是否由于不同的原因发生了某种内部挤压?
  • 是的,你可以称之为“内部挤压”。合并比较了 3 个文件:源分支上的当前文件、目标分支上的当前文件和公共父分支上的文件。中间发生的事情根本不重要。
  • 感谢您详细说明。
【解决方案2】:

您现在拥有的是某个分支上的一系列提交:

...--o--*--B1--B2--B3   <-- branch

* 是一个好的提交,但 B1 是一个“坏”的提交(已经删除了您决定的文件,现在,您毕竟不想删除)。

你需要将你的错误提交重新做为好的提交——并且,复制任何 good 之后的提交错误的提交。也就是说,你基本上会根据需要重复以下三个步骤:

  1. 将您不喜欢的提交复制到一个新的提交中(但实际上暂时不要提交)。从标记为* 的提交扩展的新分支开始,使用git cherry-pick -n 复制像B1 这样的错误提交,但不实际提交。

  2. 然后,恢复丢失的文件:git checkout -- &lt;path&gt;。 (您也可以使用git reset 来执行此操作,但是当我们在下面使用git rebase -i 时,它变得太难了,所以我们将坚持使用git checkout。)

  3. 最后,创建一个新的提交,它是旧提交的副本:git commit。由于这是一个很好的选择,Git 将准备好编辑旧提交的消息。

现在第一个错误提交B1已被复制到一个新的、更正的、良好的提交G1

...--o--*--B1--B2--B3   <-- branch
         \
          G1            <-- new branch [new copies being made]

现在您可以复制 second 提交,B2。如果它有您不想要的删除,请使用相同的三部分序列(cherry-pick -ncheckoutcommit)。如果没问题,请忽略 -n 以复制并提交:

...--o--*--B1--B2--B3   <-- branch
         \
          G1--G2        <-- new branch [new copies being made]

现在您可以复制第三次提交,B3。和以前一样,如果它有一些你需要改变的地方,你可以一路修复它。 (这假设B1--...--Bn 链中有三个提交;如果有更多或更少,请根据需要进行调整。)

当一切都完成后,您需要说服 Git 从 old 分支上剥离 branch 标签并将其粘贴到 new 分支上。 一种方法可以做到这一点,但是......

git rebase -i

有一个更简单的方法来做到这一点。 git rebase 命令的工作原理是进行一系列樱桃挑选。1 当樱桃挑选全部完成后,git rebase 命令剥离旧的分支标签,并将标签粘贴到它刚刚建立的新分支。这正是您需要做的,所以您所要做的就是说服git rebase 让您告诉它哪些提交是 提交,哪些是 ,并在不好的地方停下来。

这就是git rebase -i 的用武之地。它会为您的编辑器提供一组说明。最初,所有指令都是pick:对每个提交进行挑选。您可以将任何一条指令更改为 edit,这会告诉 Git 进行特定的挑选,然后停下来让您修复/更改内容。

有点烦人的是,rebase 做了樱桃挑选没有 -n:它制作副本并提交它。所以你必须稍微调整第 2 步:而不是 git checkout -- &lt;path&gt; 来从 HEAD 取回文件,你需要 git checkout HEAD^ -- &lt;path&gt;git checkout HEAD~1 -- &lt;path&gt;。这意味着“回到先前的提交,并获取该文件的版本。”完成后,您可以运行 git commit --amend 来更新提交。

然后,使用git rebase --continue 继续变基过程。变基代码将通过更多的picks 到下一个edit,或者如果只剩下picks,则将它们全部关闭并完成变基并移动分支标签。

主要是识别提交*:最后一个“好”提交。以某种方式拼写提交* 可以让您执行git rebase -i。在我们的示例中,它从当前分支提示向后退了三步,所以:

git rebase -i HEAD~3

会成功的。如果后退步数或多或少,则需要调整波浪号 ~ 字符后的数字。

请注意,无论您做什么,您都会得到原始提交的副本。此外,git rebase 通常会在制作副本时删除合并提交。这两种情况都意味着,如果您已将这些提交提供给其他任何人(通过git push 或类似方式),或者如果您在打算“替换”的第一个错误提交之后进行了合并(真的,复制然后忽略原件)。


1事实上,git rebase 有时会运行 git cherry-pick。其他时候,它使用其他几乎相同的东西。

【讨论】:

    【解决方案3】:

    Git 不是 SVN,我认为如果你只是在新的提交中重新添加文件应该没有任何问题。
    与 SVN 不同,Git 不跟踪文件,它跟踪恰好存储在文件中的内容。
    如果您在 SVN 中删除并重新添加文件,则它们的历史不会连接,但它们是 SVN 的不同文件。

    如果您在 Git 中删除并重新添加文件,它只是被删除和重新添加的内容,因此合并应该可以正常工作。 rebase 可能会出现问题,因为提交被一一应用于新的基本提交,因此您将遇到编辑/删除冲突。但是在合并时,只有所做的更改应该作为一个整体应用,因此不会出现问题。

    【讨论】:

      【解决方案4】:

      有几种方法可以恢复该文件,具体取决于重写 feature/X 分支的历史记录是否安全。

      选项 1:在新提交中恢复文件

      如果您已经推送了该提交,最简单的做法是从 删除它的提交的父级中检索 foo.txt 文件并再次提交。 p>

      例如,假设删除文件的提交的 SHA-1 是123abc

      git checkout feature/X
      git checkout 123abc^ path/to/file.txt
      git add path/to/file.txt
      git commit -m "Restore the file.txt file"
      

      选项2:恢复原始提交中的文件

      另一方面,如果您没有推送这些提交,则可以安全地重写本地 feature/X 分支的历史记录,以撤消对该文件的删除。为此,您必须执行 interactive rebase 并编辑删除文件的提交:

      git checkout feature/X
      git rebase -i 123abc^
      

      待办事项列表中,将该提交左侧的单词从pick 更改为edit;然后保存文件并退出。

      一旦 Git 到达您要编辑的提交,您可以通过以下方式恢复已删除的文件:

      git checkout HEAD^ path/to/file.txt
      git add path/to/file.txt
      git commit --amend -C HEAD  # where -C HEAD reuses the commit message of HEAD
      

      然后完成变基:

      git rebase --continue
      

      【讨论】:

        猜你喜欢
        • 2010-12-19
        • 2016-01-11
        • 2017-06-16
        • 1970-01-01
        • 2019-11-28
        • 1970-01-01
        • 2021-05-30
        • 2018-11-13
        • 1970-01-01
        相关资源
        最近更新 更多