几天前,我从 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完全按照以前的方式,这只是一种什么都不做的昂贵方式——但是使用-igit 打开一系列命令的编辑器。这些命令将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