Git从master重置分支但保留分支提交

时间:2019-02-10 20:17:22

标签: git github

我不确定是否会问这个问题,因为关于github的场景太多。

所以我的情况如下:

  1. 我将master分支克隆到了本地工作目录
  2. 我将新文件添加到本地工作目录中
  3. 我提交更改
  4. 然后我执行git checkout -b new_branch
  5. 然后我将本地工作目录推送到new_branch
  6. master分支存在最新版本的问题。需要回滚到较早的版本。

问题: 我该如何进行git reset --hard操作,以便本地目录中的所有文件都与主BUT中的先前发行版相同,从而保留了我的工作?例如创建的新文件。

我尝试将git reset重置为上一发行版SHA,但是它将文件与最新发行版一起保留在本地工作目录中。

我尝试了git reset --hard,它删除了所有内容以及我自己的工作。

希望这种解释很清楚,对您有所帮助。

谢谢。

更新

对我的情况有更清晰的理解: 我将带有v2标签的回购主复制到我的PC。 我创建一个新的测试分支。 我添加新文件并提交,然后推送到测试分支。 现在,我的高级开发人员告诉我,带有v2标签的母版有问题,请我不要使用。 所以我的测试分支需要回退到v1标签。

如何做到这一点而不删除我提交的文件,而是删除由主v2标签创建的文件?

1 个答案:

答案 0 :(得分:3)

TL; DR

您想要git rebase,必须用力推动。

很长,使用更新的问题:

  

我使用[git clone <url>]将带有v2标签的仓库主库克隆到我的电脑上。我创建了一个新的测试分支。

让我们假设您使用以下方法:

git checkout -b test v2

其中v2是有问题的标签。在Git中,分支名称(例如master)和标签名称(例如v2)之间是有区别的。差异实际上很小,分为两个密切相关的部分。

  • 分支名称标识一个特定的提交。但是提交随时间而变化。该名称标识了我们想说的“最新”提交或“最新”提交,即“在分支上”。实际上,在您运行git checkout <branch-name>并开始进行新的提交之后,它会自动为您自动更改。

  • 标记名称标识一个特定的提交。它永远不会标识任何其他提交-它应该永远保持那个提交的名称。 (可以移动标签-任何人都可以这样做-但为了保持理智,请不要这样做。)而且,在git checkout <tag-name>之后,您处于一种有点奇怪的状态,Git称之为分离式HEAD

所有这些的原因是,Git实际上根本不是关于分支的,而是有关 commits 的全部。提交是Git中的基本单位。每个提交都有一个丑陋的哈希ID,这对于该特定提交是唯一的。这些哈希ID实际上是提交的“真实名称”。因为哈希ID实际上是提交的 contents 的加密校验和,所以任何提交都不能更改,不能更改。如果您能以某种方式更改内容,那将更改哈希ID,这意味着您将进行新的提交。

这种密码校验和也是为什么提交ID似乎很荒谬,对人类几乎没有用。它们对人类无用的事实是为什么我们需要名称的原因,以及为什么我们拥有分支和标签名称的原因。

每个提交存储一堆东西,例如进行提交的人的姓名和电子邮件地址(以及时间戳),用于显示git log的日志消息以及完整的内容每个源文件的快照。每个提交存储的内容之一是其 parent 提交的原始哈希ID。这些形成了一个向后看的链:

... <-F <-G <-H

(字母代表实际的哈希ID)。

给出提交H的哈希ID,Git可以找到实际的提交本身。该提交包含提交G的哈希ID,因此Git可以找到G。找到G之后,Git将获得F的哈希ID,以便它可以找到F,依此类推。因此,Git从最新版本开始一直往后退,一直往后退。

由于提交中的任何内容都无法更改,因此我们可以将其绘制为线性序列:

...--F--G--H

只要我们记得向后走 很容易,而向后走 却不容易。

进行新的提交只是一个问题:

  • 选择一个分支名称,该名称还将选择其 last 提交:

    ...--F--G--H   <-- branch-name (HEAD)
    

    选择分支将特殊名称HEAD附加到名称上,因此HEAD现在同时是branch-name和提交H的同义词。

  • 做一些工作,并使用git add将文件复制回Git的 index (也称为 staging区域,有时也称为缓存,具体取决于谁在执行此调用)。

    请注意,如果您在现有文件上使用git add,则会将它们复制到索引中已位于索引上方的文件之上,以替换某些旧版本。如果在 new 文件上使用git add,它们已被复制到索引中,但它们只是新文件,它们不会覆盖以前的任何索引副本。

  • 运行git commit。这会将索引副本冻结为已提交的版本。它还会从您的日志消息中收集信息,并将您作为此新提交的作者和提交者。此新提交的当前提交H

    ...--F--G--H   <-- branch-name (HEAD)
                \
                 I
    

    并且,既然新提交已存在(因此已经获得了自己的新唯一哈希ID),Git只需将其哈希ID写入分支名称中,因此branch-name现在指向提交{{1 }}:

    I

因此,如果您做了...--F--G--H \ I <-- branch-name (HEAD) ,则存储库中将有类似的内容。请注意,名称git checkout -b test v2标识了其他(不同的,可能是更早的)提交,因此让我们将它们全部绘制:

v1

请注意,...--F <-- tag: v1 \ G--H <-- tag: v2, test (HEAD) 进行了设置,以便新的 git checkout -b name commit-ID 指向所选的提交 name ,然后使其通过在该提交中填充索引和工作树来实现当前功能,并在一个命令中将特殊名称commit-ID附加到新的 HEAD 上。

  

我添加新文件并提交

确定,因此您在工作树中(工作所在的位置)创建了一个新文件,并运行name。这会将git add newfile复制到您的索引中(只有一个索引与此工作树一起),然后您运行newfile进行新的提交git commit

I
  

然后推送到测试分支。

因此,您现在运行了:

...--F   <-- tag: v1
      \
       G--H   <-- tag: v2
           \
            I   <-- test (HEAD)

这会将提交git push -u origin test 本身发送到您的Git记住的URL下,以I的名称发送到另一个Git。请注意,那里有一个完整的单独的Git存储库!它具有自己的分支和标签,尽管因为标签永远不会移动或永远不会移动,所以您的标签及其标签都应就哪个提交哈希ID origin和{{1}达成一致}指向。

发送了提交v1后,您的Git然后要求其Git设置自己的分支名称v2或在这种情况下 create 指向提交{{ 1}}。大概I的Git都很好,所以做到了。

  

现在,我的高级开发人员告诉我,带有v2标签的master出现了问题,请我不要使用。因此,我的测试分支需要回退以使用v1标签进行掌握。

任何现有提交的任何部分都不能更改。这意味着提交test停留在原处。如果您再进行几次提交,则它们全部卡住了:

I

您需要的是一系列新的提交,它们类似于 origin,但在某些方面有所不同。特别是,您希望将新文件添加到I中,如标签...--F <-- tag: v1 \ G--H <-- tag: v2 \ I--J--K <-- test (HEAD) 所指向。然后,Git应该提交该快照,重新使用来自提交I-J-K的提交消息,使用新的且不同的哈希ID进行闪亮的新提交,我们将其称为F,以表明它是副本之v1

I

已成功将I'复制到I,现在您希望Git以相同的方式将 I' <-- [somehow remembered as in-progress] / ...--F <-- tag: v1 \ G--H <-- tag: v2 \ I--J--K <-- test 复制到I,然后将I'复制到{ {1}}:

J

最后,您希望Git将J'的标签剥离提交K,然后粘贴到提交K'上,然后回到分支 I'-J'-K' <-- [somehow remembered as in-progress] / ...--F <-- tag: v1 \ G--H <-- tag: v2 \ I--J--K <-- test 像往常一样,只不过现在意味着提交test

K

一旦所有事情都发生了,您就需要向K'处的另一个Git发送新的提交test并告诉K'的Git移动他们的 I'-J'-K' <-- test (HEAD) / ...--F <-- tag: v1 \ G--H <-- tag: v2 \ I--J--K [abandoned] 也指向I'-J'-K'

origin为您做第一部分

首先,您应该运行:

origin

如果一切看起来不错,那么您只需要:

test

此命令告诉Git:复制一些提交。要复制的提交是指从我当前的分支“上”(在技术上可以从其访问)的提交,减去名称“ K'”也“上”的所有提交。放置它们的位置是在由名称git rebase标识的提交之后。

  • 名称git checkout test git status # and make sure everything is committed! 的名称提交git rebase --onto v1 v2 ,名称名称的提交v2,依此类推。因此,这些提交不会被复制。
  • 当前分支名称v1,名称提交v2,名称H和名称G,名称test,因此{{1} }是要复制的内容。
  • K告诉Git在哪里放置副本:在提交J后,由I命名。

在复制过程结束时,Git将标签H(当前分支)从提交I-J-K中移出,并使其指向副本--onto v1

F现在需要v1

由于您已经向test发送了提交K,因此他们具有:

K'

作为其提交集以及分支和标签名称。不过,当然,如果从那以后您发送了git push--force,则它们的I指向提交origin。现在,我们可以假定它们指向...--F <-- tag: v1 \ G--H <-- tag: v2 \ I <-- test ,并且它们没有JK,因为如果它们确实具有它们和它们的{{ 1}}指向他们的test

副本

请注意,散列ID(以及底层提交本身)在每个 Git存储库中都是相同。这就是为什么哈希ID是内容的加密校验和的原因!只要您和他们具有相同的内容,您就拥有相同的提交,因此它具有相同的哈希ID。如果您具有不同的哈希ID,那么您将具有不同的内容和不同的提交。我们需要知道的是:您有这个哈希ID吗?

如果您没有滥用标记名称,那么所有Git存储库中的标记名称也都相同:相同的名称标识相同的提交。但是分支名称故意不同,因为Git的目的是继续添加 new 提交。

因此,既然您已经运行K并放弃了 三个旧的提交I,现在您将再次运行J。这将向他们发送K(您的新提交,您有他们没有的提交,您的Git和他们的Git可以仅通过哈希ID来告知它们),然后要求他们移动他们的从现在存储库中指向的任何地方进行测试– testK。您是在要求他们将其移至指向git rebase的位置。

他们会说。他们之所以拒绝的原因是,他们的Git会看到将他们的{em> {em} I-J-Kgit push origin testI'-J'-K'(无论设置为现在)移动到{ {1}}将放弃提交I(以及K和K K'起源test测试I K'` ,您需要这样做:

K

告诉他们:移动您的K'指向I,即使它放弃了一些提交!

(他们仍然可以说“不”,但是通常,如果您要告诉他们强行是您的您的分支,那么他们应该允许。)