恢复并推送GIT中的先前版本

时间:2017-05-15 15:20:37

标签: git

这里有很多关于此的问题,但没有一个具体回答它。

我使用了git reset HARD <commit hash>,我希望在不删除任何以前的历史记录的情况下返回特定的提交。我只想创建旧版本的新提交。

我在这里阅读了答案,他们中的大多数都说要使用git checkout <commit hash>,但是,其他答案说不使用它,因为它不是好习惯,因为它会删除历史记录。

我还使用了{{1}},这只是一个只读选项。

我想做的就是回到之前的提交而不改变任何以前的历史。

[评论中的每个Q&amp; A,有些编辑]

  
      
  • 问:如果你有提交&#34; A,B,C,D,E&#34;和HEAD现在在E,你想要HEAD在C,你想要&#34; A,B,C&#34;或者你想要&#34; A,D,E,B,C&#34;?

  •   
  • 答:它应该仍然存在并且保持不变,所以我的新作品将是&#34; A,B,C,D,E,C&#34;其中C现在是HEAD。

  •   

4 个答案:

答案 0 :(得分:1)

TL; DR回答

确保您的存储库是干净的(git status没有提交任何东西,等等),并且您位于工作树的顶部(隐藏的.git目录所在的位置)。然后:

git checkout master
git rm -rf -- .                 # remove it all: the scary step :-)
git checkout <hash-of-C> -- .   # but this puts everything back
git commit

(对于最后一个命令,您可以选择再次添加-C <hash-of-C>,以重新使用C的日志消息)。使用git log或类似内容为提交C找到合适的哈希ID。

有一种稍微短的方式,在很多情况下你根本不需要git rm -rf -- .步骤,但我会在解释之后留下它。

还有其他几种方法可以做到这一点,但以上是最简单的方法。

解释

Git完全是关于添加新提交,而保持所有已提交的内容,永远

因此,假设您有一个只有五次提交的存储库,您可以通过一次提交A开始:

A   <--master

然后添加第二个提交B,回顾A

A <-B   <--master

然后是第三次提交C

A <-B <-C   <--master

等等最终以:

结束
A--B--C--D--E   <-- master

在这里的每种情况下,我们都为我们的提交使用了单个大写字母ID(A,B,... Z),这意味着我们只需要在26之后用完.Git使用那些难以理解的哈希ID,所以它会never run out of unique IDs for its objects,但缺点是它们不可理解,我们只需要剪切和粘贴它们或其他什么。而且, Git 分配它们;我们无法控制哈希ID;我们只是做出提交,然后突然出现了新的哈希值。

另请注意,A未指向任何之前的提交,因为无法提交 之前没有提交。然而,所有其他提交都指向他们的父母。名称master只是指向分支master上的最新提交。事实上,这就是Git知道它是最新的提交 - 以及Git如何找到早期的提交!

同样,Git完全是关于添加新提交。如果您已经看过一些带有the Borg的星际迷航剧集,我喜欢称Git为Borg of Source Control:当你提交时,它会为你的集体增加你的技术独特性。您运行git commit,Git将所有内容保存在新提交中,Git使当前分支(在这种情况下为master)指向新提交。新提交会自动指回之前 分支的提示。

然后,你想要的是一个新的提交,它看起来就像C&#34;除了一件事:它指向E,而不是C。然后所有其余的事情自动发生:

A--B--C--D--E--C'  <-- master

其中名称C'表示&#34;外观和气味很像C,但不完全相同&#34; (因为它指向E,而不是B - 它可能也有不同的日期戳。)

这对于定义目标一切都很好,但是如何使这个新的提交看起来和闻起来很像C&#34;?好吧,每个提交都有一个附加的源代码树。当你进行提交时,Git将Git称之为 index 的内容转换为附加的源代码树。这就是为什么在工作树中编辑它们之后必须git add的事情:git add表示&#34;将我放在工作树中的更新版本复制到索引中。&# 34;这会覆盖旧的未编辑的索引版本,因此现在更新的文件已准备好提交。

这是关于Git的一个关键事实:索引是您构建 next 提交的地方。

在正常使用情况下,您只需git checkout一个分支,它将您的索引和工作树设置为与该分支的 tip commit 相匹配,例如E for { {1}}。然后编辑一些文件,master将它们复制回这个隐藏的索引,然后git add将新的提交复制出来。

但是,在的情况下,您希望以提交git commit 的方式将所有文件恢复为C命令可以执行此操作:而不是:

git checkout

我们需要更长的形式(理想情况下应该是不同的Git命令,但它不是):

git checkout <hash-or-branch>

这告诉Git:去查看我给你的哈希(如果你给它一个分支名称,它将分支名称变成一个哈希ID)然后复制它的每一个文件到我的工作树,写这个文件&#34;通过&#34;索引

如果您的提交git checkout <hash-or-branch> -- <files> 有六个文件(C和另外五个文件),则每个文件都会复制到索引中,然后再复制到工作树中。现在,您的工作树README和其他文件将更新为与提交README匹配。他们也已经上台提交:您不必C他们到索引,因为git add通过索引复制了

我们首先需要git checkout的原因是提交git rm -rf -- .可能包含七个文件:可能在ED,您E - 编写了一个在提交git add中不存在的文件new.ext。如果是这种情况,则C根本不会删除git checkout <hash-of-C> -- .。因此,我们首先完成清空索引和工作树,以便new.ext从commit git checkout <hash-of-C> -- .重新填充索引和工作树,而不会遗漏{{1 }}

现在你已经准备好像往常一样提交,像往常一样进行新的提交。将C标志添加到E告诉Git从现有提交中检索初始提交消息(它有点令人毛骨悚然或者可能是我们正在复制提交的令人遗憾的巧合。一直在调用-C,并使用git commit标记,但这只是巧合。)

请注意,如果你现在C这个新的提交-C,Git将做的是提取提交git show,然后提取C',然后将两者区分开来。您将得到的是Git关于&#34;如何将commit E的内容转换为commit C'&#34;的内容的说明。这是关于Git的另一个关键项目: a E生成一组指令,Git告诉您如何将提交 C 转换为提交 git diff old new em>做到了。当您比较相邻提交时,例如old - vs - newA - vs - B,您会看到您所做的事情的近似值。当您比较远距离的广告时,例如DE,您可以获得Git关于如何直接从A转到E的说明。但每个提交本身都有一个每个文件的完整快照,就像你在A运行时的索引一样。

略短的方式

如果你已经阅读过这篇文章,你可能想知道一个避免执行E的捷径。我们可以使用三个来代替上面的四个命令:

git commit

第一个和最后一个是相同的;它是中间神秘的。 git rm -rf -- .命令是一个内部Git命令--Git调用 plumbing 命令之一,用于脚本 - 操作索引,给定特定提交或树形哈希ID(实际上,任何东西) Git可以转换为树形哈希)。 git checkout master git read-tree --reset -u <hash-of-C> git commit 选项表示&#34;抛出当前索引并将其替换为读取&#34;的结果。 git read-tree标志意味着&#34;根据索引中发生的事情更新工作树。&#34;

这意味着如果索引中现在有七个文件,但我们读取的提交只有六个,Git将删除额外文件(来自索引,--reset ,工作树)。因此,它只需一步即可完成拆卸和重新加注。

答案 1 :(得分:0)

如果您只想重新访问特定提交,请使用git checkout <SHA>。从那里,您将进入分离的HEAD 模式,该模式根本不会改变您的提交历史记录。

如果您想保存更改,则需要根据具有git checkout -b的特定SHA创建分支。

答案 2 :(得分:0)

您是否考虑过检查有问题的提交?

git checkout sv6e5uj566

其中sv6e5uj566被您感兴趣的提交的哈希替换。然后您可以使用

创建一个分支
git checkout -b your-new-branch-name

并开始从那里进行更改。这根本不会影响你的历史,但允许你回到那个特定的提交并从那里开始分支。

答案 3 :(得分:0)

要回到历史记录中,只需执行git checkout,即可提供要返回的修订版ID。然后你可以在那里提交,如果你愿意,你可以在那里创建一个分支。或者您可以在要返回的修订版本上创建分支,然后检查它然后提交。