撤消从先前提交中删除文件

时间:2016-08-10 10:38:36

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

我正在使用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时出现问题,而file.txt中的develop发生了一些变化file.txt分支。

有没有办法从早先的提交中删除git reset <sha-of-previous-commit> file.txt

我已尝试feature/X,但未将该文件带回我的工作副本。

编辑1:
我知道已经被推到遥远的重写历史的缺点。但是我知道除了我之外没有人对git diff --diff-filter=D --summary做过任何提交,因此尽管它已被推送,但我应该安全地重写历史记录。

4 个答案:

答案 0 :(得分:1)

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

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

其中*是一个很好的提交,但B1是一个“错误的”提交(已经删除了你决定的文件,现在,你根本不想删除)。

你需要将糟糕的提交作为良好的提交重新执行 - 并且还要复制之后 之后的所有提交糟糕的提交。换句话说,您将基本上根据需要重复以下三个步骤:

  1. 将您不喜欢的提交复制到新提交中(但实际上还没有提交它)。从将从标记为*的提交增长的新分支开始,使用git cherry-pick -n复制像B1这样的错误提交,但不实际提交它。

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

  3. 最后,将新提交作为旧提交的副本:git commit。由于这是一个樱桃选择,Git将准备好旧提交的消息进行编辑。

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

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

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

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

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

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

    git rebase -i

    有一种更简单的方法。 git rebase命令通过执行一系列樱桃选择来工作。 1 当樱桃选择全部完成时,git rebase命令剥离旧分支标签,并粘贴它刚刚建立的新分支上的标签。这正是您需要做的,所以您要做的就是说服git rebase告诉它哪些提交是好的提交,哪些是那些,并暂停在坏的。

    这是git rebase -i的用武之地。它会根据一组说明调出编辑器。最初,所有指令都是pick:执行每个提交的樱桃选择。您可以将任何一条指令更改为edit,告诉Git执行该特定的挑选,然后停止并让您修复/更改内容。

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

    然后,使用git rebase --continue继续rebase进程。 rebase代码将继续通过更多pick s到下一个edit,或者如果只剩下pick,则完成所有关闭并完成rebase并移动分支标签。

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

    git rebase -i HEAD~3
    

    会做到这一点。如果退回的次数更多或更少,则需要在波浪号~字符后调整数字。

    请注意,无论您做什么,您都会获得原始提交的副本。此外,git rebase通常在复制时删除合并提交。这两个意味着你必须谨慎,如果你已经向其他任何人提供这些提交(通过git push或类似),或者你想要“替换”的第一个错误提交后合并(真的,复制然后忽略原件。

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

答案 1 :(得分:1)

答案

  

我不只是想在新提交中重新添加文件,以避免在开发分支中的file.txt中发生某些更改时合并feature / 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并不关心发生在分支文件中的所有shenannigans,它只对最终结果感兴趣。

答案 2 :(得分:0)

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

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

答案 3 :(得分:0)

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

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

如果您已经推送了该提交,那么最简单的方法就是从删除它的提交的父级中检索foo.txt文件并提交它试。

例如,假设删除文件的提交的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

然后用:

完成rebase
git rebase --continue