几天前,我从 master 分支,并随后对newbranch
.
后来,我注意到我第一次提交的文件不应该被更改。我还没有将任何东西推送到主分支,我所有的更改都在newbranch
. 当我最初从 master 分支时,如何将这个文件回滚newbranch
到它所在的位置?
几天前,我从 master 分支,并随后对newbranch
.
后来,我注意到我第一次提交的文件不应该被更改。我还没有将任何东西推送到主分支,我所有的更改都在newbranch
. 当我最初从 master 分支时,如何将这个文件回滚newbranch
到它所在的位置?
git checkout master -- filepath
根据您想要的结果,有几种不同的方法可以做到这一点。
euphoria83的回答是这样的……
开始时,您的提交历史记录如下所示(每个字母代表一个提交):
A - B - C <-- master
\
D
\
E <-- HEAD=newbranch
提交C
是您开始时在 master 中的内容。您进行了一些更改并提交(创建D
),然后进行了更多更改并提交(创建E
)。
现在你运行git checkout HEAD~1
. 这将更新您的工作目录以匹配提交 D,并为您提供一个“分离的 HEAD”(法国大革命的阴影!)直接指向 commit D
,而不是包含分支名称:
A - B - C <-- master
\
D <-- HEAD
\
E <-- newbranch
(这种形式的 checkoutgit checkout <branch-name-or-rev>
告诉 git 更改HEAD
名称。<rev>
在这种情况下,HEAD~1
的意思是“查找什么提交 HEAD 名称并移回一个”,所以这表示从 commit 移回一个E
。因为这是一个特定的<rev>
和不是分支名称,它也具有“分离 HEAD”效果。)
现在你运行git checkout HEAD~1 filename
. 为了更明确地了解 git,您可以说git checkout HEAD~1 -- filename
. 说“剩余的--
参数是文件名,而不是分支或修订名称”——如果你省略--
,git 会尝试猜测它是分支名还是文件名。
这种形式的checkout
, 带有-- filename
参数,做了一些相当不同的事情:它说像往常一样查找修订版HEAD~1
,但是这次,不要更改 HEAD
,只需提取给定的文件。由于HEAD
现在命名为 revision D
,git 再备份一个,以 revision C
,并从该修订中提取文件的filename
版本并将其放在工作目录中。
(请注意,分支名称master
也命名为 revision C
,因此您可以git checkout master -- filename
在此时编写。)
接下来,git commit -a --amend
告诉 git 添加任何更改的文件——在这种情况下,你实际上并不需要它,但通常这会添加你已经修复的其他文件——然后执行“修改提交”。“修改提交”意味着“创建一个新提交,但使其父提交与我们的父提交相同”。这会创建一个新的提交——我们称其D'
为父提交C
。与往常一样,新的提交变为HEAD
. 由于HEAD
已分离,因此尚未移动分支名称。生成的提交树如下所示:
A - B - C <-- master
| \
\ D
\ \
\ E <-- newbranch
\
D' <-- HEAD
中的文件与D'
中的相同D
,除了文件filename
,现在与修订中的相同C
。
接下来,git cherry-pick newbranch
说要获得由newbranch
-that's revision 命名的修订版E
- 并对 HEAD 修订版进行相同的更改,作为一个新的提交(我们称之为E'
):
A - B - C <-- master
| \
\ D
\ \
\ E <-- newbranch
\
D' - E' <-- HEAD
现在你只需要给HEAD
. 然后可以安全地删除newbranch
,或将其重命名,然后重命名newnew
为newbranch
:
git checkout -b newnew # now HEAD=newnew which points to E'
git branch -m newbranch oldnew # rename out of the way
git branch -m newnew newbranch # and rename newnew to newbranch
(你可以缩短一点,但让我们继续......)
有一种更简单的方法可以做同样的事情。跑吧git rebase -i master
。这表示将当前分支 ( newbranch
)master
用作其“上游”。也就是说,在master
(这意味着,D
和E
)之后找到所有提交并将它们重新定位到master
(这是它们之前的基础)。没有-i
这将是愚蠢的——变基D
并E
完全按照以前的方式,这只是一种什么都不做的昂贵方式——但是使用-i
git 打开一系列命令的编辑器。这些命令将pick
提交D
和E
. pick
将for commit更改D
为edit
,并写出文件。
然后,rebase 将为您挑选D
并停止并让您修改提交。在 shell 中,输入:
git checkout master -- filename
git commit -a --amend
和以前一样,保存修改后的提交,它变成D'
. 然后运行:
git rebase --continue
Git 现在将挑选 commit E
,给出 commit E'
。现在一切都完成了,所以 rebase 完成,并且分支newbranch
有你想要的提交。
ДМИТРИЙ МАЛИКОВ的回答做了一些不同的事情。它包含相同的git checkout master -- filename
(拼写为filepath
)但没有类似 rebase 的序列。
所以,和以前一样,你从这个开始:
A - B - C <-- master
\
D
\
E <-- HEAD=newbranch
filename
然后您将正在修订的版本提取C
到工作目录中。如果你现在git commit
(或者git commit -a
,在这种特殊情况下也是一样的——git,这是因为上面首先checkout
将提取的 rev-C 文件写入暂存区),你将获得一个新的提交F
,将文件更改filename
回原来的样子C
.
换句话说,提交D
并且E
仍然会对文件进行更改;新的提交F
将撤消更改。
在 new_branch 上:
git checkout HEAD~1
git checkout HEAD~1 file_not_needed
git commit -a --amend
git cherry-pick new_branch
git branch -f new_branch HEAD