撤消从上一次提交中删除文件
Undo removal of file from a previous commit
我正在使用 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
,但未能将文件恢复到我的工作副本。
编辑 1:
我知道重写已经被推送到远程的历史的缺点。但是我知道除了我之外没有人在 feature/X
上做过任何提交,因此重写历史记录对我来说应该是安全的,尽管它已经被推送了。
Git不是SVN,我觉得只要在新提交中重新添加文件应该没有问题。
与 SVN 不同,Git 不跟踪文件,它跟踪恰好存储在文件中的内容。
如果您在 SVN 中删除并重新添加文件,则它们的历史记录没有关联,但它们对于 SVN 来说是不同的文件。
如果您在 Git 中删除并重新添加文件,则只是内容被删除并重新添加,因此合并应该可以正常工作。变基可能会有问题,因为提交会逐一应用于新的基础提交,因此您会遇到编辑/删除冲突。但是在合并时,只有所做的更改应该作为一个整体应用,因此不会出现问题。
您现在拥有的是某个分支上的一系列提交:
...--o--*--B1--B2--B3 <-- branch
其中 *
是一个很好的提交,但是 B1
是一个 "bad" 提交(已经删除了您决定的文件,现在,您毕竟不想删除) .
您需要将错误的提交重做为良好的提交——并且还要复制任何 良好的 提交 在 一个错误的提交之后。换句话说,你基本上会根据需要重复以下三个步骤:
将您不喜欢的提交复制到新的提交中(但实际上暂时不要提交)。从一个将从标记为 *
的提交中增长的新分支开始,使用 git cherry-pick -n
复制错误的提交,如 B1
,但不实际提交它。
然后,恢复丢失的文件:git checkout -- <path>
。 (你也可以用 git reset
来做到这一点,但是当我们在下面使用 git rebase -i
时,它变得太难了,所以我们将坚持使用 git checkout
。)
最后,创建作为旧提交副本的新提交:git commit
。由于这是一个樱桃选择,Git 将准备好旧提交的消息以供编辑。
现在 第一个 错误提交 B1
已被复制到一个新的、更正的良好提交 G1
:
...--o--*--B1--B2--B3 <-- branch
\
G1 <-- new branch [new copies being made]
现在您可以复制 second 提交,B2
。如果它有您不想要的删除,请使用相同的三部分序列 (cherry-pick -n
、checkout
、commit
)。如果没问题,请忽略 -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
命令通过执行一系列的 cherry-picks 来工作。1 当 cherry-picks 全部完成后,git rebase
命令剥离旧的分支标签, 并将标签粘贴到刚刚构建的新分支上。这正是你需要做的,所以你所要做的就是说服 git rebase
让你告诉它哪些提交是 好 提交,哪些是 ]不好的,遇到不好的就停下来。
这就是 git rebase -i
的用武之地。它会根据一组说明调出您的编辑器。最初,所有指令都是 pick
:对每个提交进行一次挑选。您可以将任何一条指令更改为 edit
,它告诉 Git 进行特定的挑选,但随后停止并让您修复/更改内容。
有点恼人的是,rebase
在没有 -n
的情况下进行了 cherry-pick :它制作了副本并提交了它。所以你必须稍微调整第 2 步:而不是 git checkout -- <path>
从 HEAD
取回文件,你需要 git checkout HEAD^ -- <path>
或 git checkout HEAD~1 -- <path>
。这意味着 "reach back into the previous commit, and get that version of the file." 完成后,您可以 运行 git commit --amend
更新提交。
然后,使用 git rebase --continue
继续变基过程。 rebase 代码将通过更多 pick
s 继续到下一个 edit
,或者如果只剩下 pick
s,则将它们全部完成并完成 rebase 并移动分支标签。
主要是识别提交*
:最后的"good"提交。以某种方式拼写提交 *
可以让您执行 git rebase -i
。在我们的示例中,它从当前分支提示后退三步,因此:
git rebase -i HEAD~3
会成功的。如果后退的步数更多或更少,您需要调整波浪号 ~
字符后的数字。
请注意,无论您做什么,您都会获得原始提交的 副本 。此外,git rebase
通常会在制作副本时删除合并提交。这两者都意味着,如果您已将这些提交提供给其他任何人(通过 git push
或类似方式),或者如果您在第一个错误提交之后进行了合并,您打算 "replace" (真的, 复制然后忽略原件)。
1实际上,git rebase
字面上的运行s git cherry-pick
有时。其他时候,它使用其他几乎相同的东西。
有几种方法可以恢复该文件,具体取决于重写 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
然后完成变基:
git rebase --continue
回答
I don't just want to re-add the file in a new commit to avoid issues when merging feature/X to develop while there has been some changes in file.txt in develop branch.
是的,您只想在新提交中重新添加文件。 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
不关心分支上文件发生的所有恶作剧,只关心最终结果。
我正在使用 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
,但未能将文件恢复到我的工作副本。
编辑 1:
我知道重写已经被推送到远程的历史的缺点。但是我知道除了我之外没有人在 feature/X
上做过任何提交,因此重写历史记录对我来说应该是安全的,尽管它已经被推送了。
Git不是SVN,我觉得只要在新提交中重新添加文件应该没有问题。
与 SVN 不同,Git 不跟踪文件,它跟踪恰好存储在文件中的内容。
如果您在 SVN 中删除并重新添加文件,则它们的历史记录没有关联,但它们对于 SVN 来说是不同的文件。
如果您在 Git 中删除并重新添加文件,则只是内容被删除并重新添加,因此合并应该可以正常工作。变基可能会有问题,因为提交会逐一应用于新的基础提交,因此您会遇到编辑/删除冲突。但是在合并时,只有所做的更改应该作为一个整体应用,因此不会出现问题。
您现在拥有的是某个分支上的一系列提交:
...--o--*--B1--B2--B3 <-- branch
其中 *
是一个很好的提交,但是 B1
是一个 "bad" 提交(已经删除了您决定的文件,现在,您毕竟不想删除) .
您需要将错误的提交重做为良好的提交——并且还要复制任何 良好的 提交 在 一个错误的提交之后。换句话说,你基本上会根据需要重复以下三个步骤:
将您不喜欢的提交复制到新的提交中(但实际上暂时不要提交)。从一个将从标记为
*
的提交中增长的新分支开始,使用git cherry-pick -n
复制错误的提交,如B1
,但不实际提交它。然后,恢复丢失的文件:
git checkout -- <path>
。 (你也可以用git reset
来做到这一点,但是当我们在下面使用git rebase -i
时,它变得太难了,所以我们将坚持使用git checkout
。)最后,创建作为旧提交副本的新提交:
git commit
。由于这是一个樱桃选择,Git 将准备好旧提交的消息以供编辑。
现在 第一个 错误提交 B1
已被复制到一个新的、更正的良好提交 G1
:
...--o--*--B1--B2--B3 <-- branch
\
G1 <-- new branch [new copies being made]
现在您可以复制 second 提交,B2
。如果它有您不想要的删除,请使用相同的三部分序列 (cherry-pick -n
、checkout
、commit
)。如果没问题,请忽略 -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
命令通过执行一系列的 cherry-picks 来工作。1 当 cherry-picks 全部完成后,git rebase
命令剥离旧的分支标签, 并将标签粘贴到刚刚构建的新分支上。这正是你需要做的,所以你所要做的就是说服 git rebase
让你告诉它哪些提交是 好 提交,哪些是 ]不好的,遇到不好的就停下来。
这就是 git rebase -i
的用武之地。它会根据一组说明调出您的编辑器。最初,所有指令都是 pick
:对每个提交进行一次挑选。您可以将任何一条指令更改为 edit
,它告诉 Git 进行特定的挑选,但随后停止并让您修复/更改内容。
有点恼人的是,rebase
在没有 -n
的情况下进行了 cherry-pick :它制作了副本并提交了它。所以你必须稍微调整第 2 步:而不是 git checkout -- <path>
从 HEAD
取回文件,你需要 git checkout HEAD^ -- <path>
或 git checkout HEAD~1 -- <path>
。这意味着 "reach back into the previous commit, and get that version of the file." 完成后,您可以 运行 git commit --amend
更新提交。
然后,使用 git rebase --continue
继续变基过程。 rebase 代码将通过更多 pick
s 继续到下一个 edit
,或者如果只剩下 pick
s,则将它们全部完成并完成 rebase 并移动分支标签。
主要是识别提交*
:最后的"good"提交。以某种方式拼写提交 *
可以让您执行 git rebase -i
。在我们的示例中,它从当前分支提示后退三步,因此:
git rebase -i HEAD~3
会成功的。如果后退的步数更多或更少,您需要调整波浪号 ~
字符后的数字。
请注意,无论您做什么,您都会获得原始提交的 副本 。此外,git rebase
通常会在制作副本时删除合并提交。这两者都意味着,如果您已将这些提交提供给其他任何人(通过 git push
或类似方式),或者如果您在第一个错误提交之后进行了合并,您打算 "replace" (真的, 复制然后忽略原件)。
1实际上,git rebase
字面上的运行s git cherry-pick
有时。其他时候,它使用其他几乎相同的东西。
有几种方法可以恢复该文件,具体取决于重写 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
然后完成变基:
git rebase --continue
回答
I don't just want to re-add the file in a new commit to avoid issues when merging feature/X to develop while there has been some changes in file.txt in develop branch.
是的,您只想在新提交中重新添加文件。 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
不关心分支上文件发生的所有恶作剧,只关心最终结果。