我有一个Git存储库,看起来像这样:
1
| A -> B -> C -> D -> HEAD |
我希望分支的头指向A,也就是说,我希望B、C、D和头消失,我希望头与A同义。
听起来我要么尝试重新平衡(不适用,因为我在两者之间推送了更改),要么恢复。但如何恢复多个提交?我一次回复一个吗?订单重要吗?
- 如果你只是想重置遥控器,你可以用任何东西来击倒它!但是让我们在前面使用第四个提交:git push -f HEAD~4:master(假设远程分支是主分支)。是的,你可以这样推动任何承诺。
- 如果有人拉了你,你必须做出承诺,用git revert恢复更改。
- 使用Git Show Head~4确保您正朝遥控器的右边推。
- 在Git中如何撤消上一次提交的可能副本?
- "订单重要吗?"是的,如果提交影响同一文件中的同一行。然后,您应该开始恢复最近的提交,并重新开始工作。
扩展我在评论中所写的内容
一般规则是,您不应该重写(更改)已发布的历史记录,因为可能有人基于它进行了工作。如果重写(更改)历史记录,则在合并它们的更改和更新它们时会出现问题。
因此,解决方案是创建一个新的提交,它恢复您想要摆脱的更改。您可以使用git revert命令来执行此操作。
您有以下情况:
1
| A <-- B <-- C <-- D <-- master <-- HEAD |
号
(此处箭头表示指针的方向:提交时的"父"引用,分支头时的顶部提交(分支引用),分支头时的名称)。
您需要创建以下内容:
1
| A <-- B <-- C <-- D <-- [(BCD)^-1] <-- master <-- HEAD |
其中"[(b c d)^-1]"表示恢复提交b、c、d中更改的提交。数学告诉我们(bcd)^-1=d^-1 c^-1 b^-1,因此可以使用以下命令获得所需情况:
1 2 3 4
| $ git revert --no-commit D
$ git revert --no-commit C
$ git revert --no-commit B
$ git commit -m"the commit message" |
。
另一种解决方案是签出commit a的内容,并提交此状态:
1 2
| $ git checkout -f A -- .
$ git commit -a |
那么,您将遇到以下情况:
1
| A <-- B <-- C <-- D <-- A' <-- master <-- HEAD |
。
commit a'与commit a具有相同的内容,但是不同的commit(commit message、parents、commit date)。
Jeff Ferland的解决方案,由Charles Bailey修改,基于相同的想法,但使用了git reset:
1 2 3
| $ git reset --hard A
$ git reset --soft @{1} # (or ORIG_HEAD), which is D
$ git commit |
。
- 如果您在b、c或d中添加了文件,那么git checkout -f A -- .将不会删除这些文件,您必须手动删除这些文件。我现在采用了这个策略,谢谢杰克
- 这些解决方案是不等价的。第一个不会删除新创建的文件。
- fwiw,如果你需要删除这些新文件,git clean是你的朋友。
- 嗨,你能解释一下这里所有的旗子和开关吗?Git结帐-F A--。"--"和"."是什么意思?
- @mastah:--可能需要将分支名称与路径规范分开。.的意思是"当前目录",并且由于路径与项目的顶部目录相关,所以它意味着签出整个项目以及项目的所有文件。
- @Jakubnar?bski"可能"是什么意思?我也觉得这个命令很神秘…
- @Jerry:git checkout foo可能是指签出分支foo(切换到分支)或签出文件foo(从索引)。--用于消除歧义,例如git checkout -- foo总是关于文件的。
- 除了很好的答案。这个速记对我来说很有用。
- @谢谢你的评论。在写这个答案时,git revert不接受多次提交;这是一个全新的添加。
- 这个答案很好地解释了所有的选择,但我们不应该把git reset --soft @{1}解决方案放在首位吗?这显然是解决这个问题的最佳方案。
- 为了澄清git checkout -f A -- .,我认为它的意思是"查看commit a对当前目录的更改,而不进入分离的head模式",基本上,您是在master中,但是commit a的更改会及时带到当前位置。至少我是这样解释的,如果我错了,请纠正我。
- @平面线:git checkout -f A -- .表示签出提交到当前目录的状态,覆盖文件…但它不会删除文件使其进入A状态。
- 错别字:(b c d)^-1=d^**-**1 c^-1 b^-1
- 这是在Git上进行回滚最有效和最负责任的方法。
- 当我尝试使用git revert no-commit B..时(在一个IDE的终端),然后使用该IDE进行提交,我进入一个分离的头部状态。这是一般预期的行为,还是可能是因为我使用了用于revert命令的终端和用于commit命令的IDE?在任何情况下,我都按照此处所述,用rebasing固定分离的头部。
- 只在本地执行REBASE!正如我们与git-commit(修正和git-reset)讨论过的那样,您不应该重新设置推送到公共存储库的提交。REBASE将用新的承诺替换旧的承诺,并且看起来您的项目历史的那部分突然消失了。链接
- 埃多克斯1〔0〕对我来说不太好。当我试图推动它时,我不能不合并远程版本,这不是我想要的。我正在尝试撤消最后几次提交,所以我不希望远程版本合并在一起。
- 只想增加一件对我们有帮助的事情。不要在还原之间提交。关键部分是——不提交。如果您在两个回复之间进行提交,那么最终可能会在不同提交之间进行奇怪的比较。
- 第三个解决方案适用于我的复杂案例,涉及多个提交、合并、添加和删除文件。
- 请用引号替换$ git commit -m 'the commit message'中的撇号。
- 我和Git结帐-F A——。因为我有8个承诺,喜欢一步到位的想法。这基本上是有效的,但是我会注意到新的文件没有被删除,只是更改了未完成的文件,在运行这个命令和推送之后,我将远程头与我的cherry-picked分支进行了比较,只看到了新的文件,在删除这些文件之后,然后提交/推送,分支是相同的。
- $ git revert --no-commit Dfatal: bad revision 'D'号
- 你是我的英雄你拯救了我的一天你帮助了我很多这是一个很好的答案你的解决方案很好我想说谢谢你这么专业<3<3<3<3<3<3<3<3<3<3
- 我以前从未想过将提交作为矩阵。太神了。
- "另一个解决方案是签出commit a的内容",但这会使您处于分离的头部状态…..
- @amalgovinus:签出内容是指使用带有修订的g it签出和路径(例如dot),这是不同的签出操作模式。
- 当我们是git revert顺序提交(上面的commit d、c、b)时,如果我们执行git reset --soft HEAD~3是一样的,对吗?同时,git revert可用于恢复非顺序提交,对吗?像git revert --no-commit D B A一样?
- @brucesun:是的,git revert可以用来恢复非顺序提交(通常是这样使用的),尽管它可能会导致合并冲突,您需要解决这些冲突
- Git Revert在合并提交时失败,但是签出前面散列的内容并提交这些内容非常出色,谢谢!
- 更多破碎的垃圾…一旦执行第一次恢复,就会产生error: your index file is unmerged。这个工具是个坏笑话。
为此,您只需使用revert命令,指定要恢复的提交范围。
考虑到您的示例,您必须这样做(假设您在分支"master"上):
1
| git revert master~3..master |
号
这将在您的本地创建一个新的提交,反向提交为b、c和d(意味着它将撤消这些提交带来的更改):
1
| A <- B <- C <- D <- BCD' <- HEAD |
号
- git revert --no-commit HEAD~2..是一种更为惯用的方法。如果您在master分支上,则无需再次指定master。--no-commit选项允许git尝试一次恢复所有提交,而不是将历史记录中的多条revert commit ...消息乱丢(假设这是您想要的)。
- @维克多我修正了你的承诺范围。范围的开始是独占的,这意味着它不包括在内。因此,如果要恢复最后3个提交,则需要从第3个提交的父级开始范围,即master~3。
- @Kubi是否无法使用单个提交(您的方法,但不必手动输入已恢复的提交)将shas包含在提交消息中?
- @Chriss我的第一个想法是不要使用--no-commit(这样您就可以为每个恢复分别提交一次),然后将它们压缩成一个交互式的重新组合。合并的提交消息将包含所有的sha,您可以使用自己喜欢的提交消息编辑器来安排它们。
干净的方式,我觉得有用
1
| git revert --no-commit HEAD~3.. |
。
此命令只使用一次提交来还原最后3次提交。
也不会重写历史。
- 这是最简单最好的答案
- 如果没有提交,这将如何工作?除了上述命令,还需要做什么?此命令之前/之后需要哪些git命令?
- @johnlittle它将改变分阶段进行。从那里来的git commit实际上会履行承诺。
- 如果某些提交是合并提交,则这将不起作用。
- 结尾的两个点做什么?
- @cardamom那些指定了一个范围。HEAD~3..与HEAD~3..HEAD相同
与Jakub的答案类似,这允许您轻松地选择要恢复的连续提交。
1 2 3
| # revert all commits from B to HEAD, inclusively
$ git revert --no-commit B..HEAD
$ git commit -m 'message' |
号
- 你的解决方案对我来说很有效,但稍作修改。如果有这种情况,z->a->b->c->d->head,如果我想返回到a状态,那么奇怪的是,我必须执行git revert——no commit z.head。
- 同意@bogdan,回复范围是:sha ou to ou revert ou to..head
- 范围错误。应为B^..HEAD,否则不包括b。
- 同意@tessus,所以正确的做法是:git revert --no-commit B^..HEAD或git revert --no-commit A..HEAD
1 2 3
| git reset --hard a
git reset --mixed d
git commit |
这将作为他们所有人的回归。给出一个好的提交消息。
- 如果他希望HEAD看起来像A,那么他可能希望索引匹配,因此git reset --soft D可能更合适。
- --软重置不会移动索引,所以当他提交时,提交看起来像是直接从a而不是从d来的,这将使分支分裂。--混合离开更改,但移动索引指针,因此d将成为父提交。
- 这可以通过Git重置来完成吗——保留?
- 是的,我认为Git重置——保留正是我上面所说的。它出现在2010年4月发布的1.7.1版中,所以当时还没有答案。
- 当时的git checkout A和上面的git commit对我来说不起作用,但这个答案确实起作用。
- 为什么需要git reset --mixed D?具体来说,为什么是reset?是因为,如果不重置为d,头部会指向a,导致b、c和d"悬空"并被垃圾收集——这不是他想要的吗?但那为什么是--mixed?您已经回答"--soft重设不移动索引…",所以通过移动索引,这意味着索引将包含d的更改,而工作目录将包含a的更改——这样,git status或git diff将显示内容;该用户将从d返回到a?
首先确保您的工作副本没有被修改。然后:
1
| git diff HEAD commit_sha_you_want_to_revert_to | git apply |
号
然后承诺。不要忘记记录恢复的原因。
- 为我工作!有一个问题,功能分支废弃了在开发分支中所做的更改(一些错误修复),因此功能分支必须覆盖在开发中所做的所有更改(包括删除一些文件)。
- 无法处理二进制文件:error: cannot apply binary patch to 'some/image.png' without full index line error: some/image.png: patch does not apply。
- 这是一个比公认答案更灵活的解决方案。谢谢!
- 回复:二进制文件使用——二进制选项:git diff --binary HEAD commit_sha_you_want_to_revert_to | git apply。
- 即使您希望恢复包含合并提交的提交范围,这也将有效。使用git revert A..Z时,会得到error: commit X is a merge but no -m option was given.。
我很沮丧,这个问题不能简单地回答。其他每一个问题都与如何正确地还原和保存历史有关。这个问题说:"我希望分支的头指向A,也就是说,我希望B、C、D和头消失,我希望头与A同义。"
1 2 3
| git checkout <branch_name>
git reset --hard <commit Hash for A>
git push -f |
号
我在阅读Jakub的帖子时学到了很多东西,但是公司里的一些人(可以在没有拉请求的情况下进入我们的"测试"分支)像5个坏的承诺一样推,试图修复和修复他之前犯下的一个错误。不仅如此,还有一到两个请求被接受,这现在很糟糕。所以忘记它,我找到了最后一个好的提交(abc1234),并运行了基本脚本:
1 2 3
| git checkout testing
git reset --hard abc1234
git push -f |
号
我告诉在这个回购协议中工作的其他5个人,他们最好记录下最近几个小时的变化,并从最新的测试中清除/重新分支。故事的结尾。
- 我没有公布承诺,所以这是我需要的答案。谢谢,@suamere。
- 更好的方法是git push --force-with-lease,只有当没有其他人在承诺蒸发之后或在承诺蒸发的范围内对分支做出承诺时,它才会重写历史。如果其他人已经使用了这个分支,那么它的历史就永远不应该被重写,提交应该被明显地还原。
- @疯狂的"历史不应该被改写",只有西斯才是绝对的。这条线索的问题,以及我的答案,正是因为对于一个特定的场景,所有的历史都应该被抹去。
- @苏阿梅当然,这就是问题所在。但当你的回答提到你必须告诉其他人的事情时,你就有可能遇到麻烦。从个人经验来看,如果其他人在您试图擦除的内容之后承诺了,push-f可能会破坏您的代码库。--带租约的强制也能达到同样的效果,只是如果你要搞砸回购协议的话,它可以帮你省钱。为什么要冒险?如果--force with lease失败,您可以看到哪个提交会妨碍您的工作,正确评估、调整并重试。
- @疯狂啊!你是说,不是第三行git push -f改为git push --force-with-lease似乎很明显,但你的评论不是这样读的。更重要的是,这似乎是你的一条线,不需要其他任何东西,精确的解决方案。我同意改变我答案的最后一行将提供额外的保护(和额外的工作)。我个人是不会这样做的,除非有人我不控制代码的工作。因此,您的观点对任何开源或多部门的内容都是有好处的。虽然我不是Git Savant,甚至不知道这个命令,哈哈。
- @谢谢你!我同意这个问题清楚地表明它想改写历史。我和你处于同一种情况,我猜这是因为有人做了几十个丑陋的回复和奇怪的承诺和回复(在我度假的时候),并且状态需要回复。在任何情况下,连同良好的健康警告,这应该是公认的答案。
这是对Jakub答案中提供的解决方案之一的扩展
我面临这样一种情况:我需要回滚的提交有些复杂,其中几个提交是合并提交,我需要避免重写历史。我不能使用一系列git revert命令,因为我最终在添加的恢复更改之间遇到了冲突。我最后使用了以下步骤。
首先,检查目标提交的内容,同时将头放在分支的尖端:
1
| $ git checkout -f <target-commit> -- . |
(确保被解释为提交而不是文件;the。指当前目录。)
然后,确定要回滚的提交中添加了哪些文件,因此需要删除:
1
| $ git diff --name-status --cached <target-commit> |
号
添加的文件应在行首显示"A",并且不应存在其他差异。现在,如果需要删除任何文件,请准备删除这些文件:
1
| $ git rm <filespec>[ <filespec> ...] |
最后,提交回复:
1
| $ git commit -m 'revert to <target-commit>' |
。
如果需要,请确保我们回到所需状态:
1
| $git diff <target-commit> <current-commit> |
应该没有区别。
- 你确定你只用树枝尖就可以把1〔10〕个头伸出来吗?
- 对于我来说,这是一个更好的解决方案,因为在我的系统中,我有合并提交。
恢复共享存储库上的一组提交的简单方法是将git revert与git rev-list结合使用。后者将向您提供一个提交列表,前者将自动执行恢复。
有两种方法可以做到这一点。如果希望在一次提交中还原多个提交,请使用:
1
| for i in `git rev-list <first-commit-sha>^..<last-commit-sha>`; do git revert -n $i; done |
这将恢复您需要的一组提交,但将所有更改保留在您的工作树上,您应该像往常一样提交它们。
另一种选择是每次恢复的更改都有一个提交:
1
| for i in `git rev-list <first-commit-sha>^..<last-commit-sha>`; do git revert --no-edit -s $i; done |
。
例如,如果您有一个类似提交树的
1 2
| o---o---o---o---o---o--->
fff eee ddd ccc bbb aaa |
要将更改从EEE还原为BBB,请运行
1
| for i in `git rev-list eee^..bbb`; do git revert --no-edit -s $i; done |
。
这些都不适合我,所以我有三个承诺要恢复(最后三个承诺),所以我做到了:
1 2 3 4
| git revert HEAD
git revert HEAD~2
git revert HEAD~4
git rebase -i HEAD~3 # pick, squash, squash |
。
工作得很有魅力:)
- 这是一个可行的选择,只有当你的改变还没有被推动的时候。
在我看来,一个非常简单和干净的方法可能是:
回到A
将大师的头指向当前状态
1
| git symbolic-ref HEAD refs/heads/master |
。节约
如果要临时恢复某个功能的提交,则可以使用以下一系列命令。
这是它的工作原理
git log--pretty=oneline grep'feature name'cut-d''-f1 xargs-n1 git revert--不编辑