【问题标题】:git: how get an old tagged version into master, without losing history?git:如何在不丢失历史的情况下将旧的标记版本变为 master?
【发布时间】:2020-09-20 19:14:11
【问题描述】:

这是一个非常小的项目,只有一个 master 分支。

我(轻量级)标记了生产中的源版本并将标记推送到源

然后我向主节点提交了一些更改(这会触发构建到我们的开发系统上,以便我们对其进行测试)并将这些更改推送到源。

现在我希望 master 包含标记版本,例如“还原/重置”,但我不想丢失我所做的更改,这些更改可能在某些时候有用。

这个答案:How do I revert master branch to a tag in git?

是做到以下几点:

git checkout master
git reset --hard tag_ABC
git push --force origin master

我不知道这是做什么的,但它看起来很危险/很激烈,我正在寻找一种更简单(不太可能出错)的解决方案。

大概我需要签出 master 分支,然后在标签中合并,或者签出标记的版本,然后在 master 中合并?

例如

$ git checkout master
$ git pull
$ git merge mytag
$ git push

或者这会因为我要撤销的更改更新而感到困惑?

我看到您可以将主分支“位置标记”设置为提交。所以我猜我只需要这样做

git reset XXX

其中 XXX 是标签中提交的提交号。如果此方法有效,我如何获取标签的提交号(git hist 或 git history 在 mac 上不起作用)?如果这很容易,为什么要强硬的东西?

如果我签出我的标签并执行 git status,它会显示“HEAD detached at mytag”

如果我不遵循烹饪书的食谱,它通常会以灾难告终,所以希望以前有人这样做过。

更新

我收到了几个回复,这很好,但不幸的是,没有一个有完整的食谱。由于缺乏更好的解决方案,我这样做了:

  1. 签出旧的标记版本。
  2. 将我改过的文件内容剪切粘贴到onenote中。
  3. 检查头部。
  4. 在编辑器中打开修改后的文件,然后粘贴来自 onenote 的内容。
  5. 已将更改提交给 master。

我相信还有更好的方法。

【问题讨论】:

  • 为什么不签出标签?或者看看 revert 命令。
  • 如果我签出标签,它会显示“HEAD detached at mytag”。如果这个错误没问题,我会在本地签出标签,但是 origin master 仍然是我在标签之后推送的代码。我不知道如何让标记版本成为源上 master 分支中的最新版本
  • “分离 HEAD”模式不是错误,但如果你正在做新工作,它也不是你通常想要的模式我>。当您出于任何目的(测试、重现客户的错误、启动新分支以修复所述客户的错误等)使用历史版本时,这是很正常的。

标签: git tags


【解决方案1】:

要保留指向您的master 分支的当前头部提交的指针:只需在那里创建一个分支:

git branch new/features master
git push origin new/features

之后,你可以reset --hardpush -f掌握你想要的一切。

【讨论】:

    【解决方案2】:

    这是一个非常小的项目,只有一个 master 分支。

    这也许是这里真正的错误。 ? Git 中的分支既便宜又好,你应该开始使用它们。请参阅LeGEC's answer 获取食谱(但请通读所有这些内容以获取警告和启发!)。

    要意识到的一点是,Git 中的分支实际上没有任何意义。也就是说,master 没有什么特别之处,只是人们从它开始。1 这就是它们如此便宜的原因。任何分支名称的唯一真正含义是您赋予它的任何含义。

    如果我不遵循烹饪书的食谱,它通常会以灾难告终......

    这意味着您应该了解 Git 在这里真正做了什么。只是有点复杂!


    1嗯,Git 默认使用它作为起始名称这一事实,你可以称之为“特殊”。但请注意,这即将改变——据报道,特别是 GitHub 正在切换到 main,并且 Git 正在发展一项功能,您可以在其中设置名称(例如,像 GitHub 一样设置为 main)在您的系统中或每个用户的配置(这或多或少是 GitHub 计划用来设置 他们的 新默认值的)。这还有一些其他的小怪癖,并且由于对六字母序列master 的内置比较,需要进行几轮审查才能找到并调整所有发生奇怪事件的地方。但除此之外master 没有什么特别之处。


    Git 就是提交

    作为 Git 的普通用户,您可能认为 Git 是关于分支和/或文件的。这就是你误入歧途的地方,也是事情以灾难告终的原因:Git 无关文件,也与分支无关。 Git 存储文件,使用分支名称,但它把文件存储在commits中,并使用分支名称来查找提交。最后,一切都与提交本身有关。

    关于提交有一些事情需要了解:

    • 每个提交都有一个唯一的编号。这些数字并不是简单的计数数字:它们不是从提交 #1 开始,而是一直计数到 #2、#3 等等。取而代之的是,每次提交都会得到一个看起来随机的——但实际上根本不是随机的——又大又丑的 hash ID,比如 385c171a018f2747b329bcfa6be8eda1709e5abd

      这些数字必须又大又丑,因为从现在开始,这个数字意味着那个提交。 您的 个提交都不能使用该编号。2 提交的编号(其哈希 ID)实际上是该提交内容的加密校验和。这意味着一旦提交,就不能更改,这是非常重要的。

      Git 可以查找提交——或任何内部 Git 对象,但我们只担心这里的提交——通过丑陋的大数字。 Git 存储库主要是一个大的key-value database,其中哈希 ID“数字”是键(嗯,加上第二个名称到哈希 ID 的数据库,我们稍后会介绍)。

    • 每个提交存储两件事:

      • 它存储 Git 知道的每个文件的完整快照(或在您或任何人提交提交时知道的)。提交中的文件以压缩和去重的形式存储。由于这些文件都是只读的——正如我们刚刚提到的,提交的每个部分都是只读的——可以通过重复数据删除与其他提交共享它们。因此,在一千次提交中存储同一个文件一千次实际上并没有什么坏处:它们都只是重复使用该文件的一个版本。只有当文件更改时,新的提交才必须存储新版本。

      • 它存储一些元数据,或者关于提交本身的信息。例如,这包括提交者的姓名和电子邮件地址。在每个提交中也有一个日期和时间戳——实际上是其中的两个,你的日志信息在这里。不过,对于 Git 本身而言,最重要的是,每个提交都在此元数据中存储 previous 提交的大而丑陋的哈希 ID。

    当您遇到灾难时,正是这最后一点使一切正常运行或中断。 ? 为了了解这里发生了什么,让我们绘制一个简单且非常小的 Git 存储库,其中我们只有三个提交。因为实际的哈希 ID 太大太丑,我们将这些提交称为 ABC,并像这样绘制它们:

    A <-B <-C
    

    Commit C(不管它的哈希 ID 是什么)是 last 提交,它存储了一堆文件和一些元数据,现在所有这些都被冻结了。在提交 C 的元数据中,Git 存储了 B 的哈希 ID。所以从C,Git 可以向后一跳,找到B。同时B 有文件和元数据,在B 的元数据中,Git 存储了A 的实际哈希ID。所以从B,Git 可以退一步提交A

    Git 将这些向后指向的链接称为parents。最后一次提交C 的父级是BB 的父级是A。如您所见,Git 实际上向后运行。我们从 last 提交开始——到目前为止,它是 C——然后一次返回一个提交。提交A,作为第一个提交,有一个特别之处:它的元数据没有列出之前的提交。它没有父母(它是一个孤儿,有点)。这就是 Git 知道它可以停止倒退的方式。但是这里有一个问题:我们如何找到提交C 的实际哈希ID?


    2从技术上讲,您的 Git可以重复使用该号码,只要您从未将您的 Git 介绍给拥有 Git 本身存储库的 Git。有关更多信息,请参阅How does the newly found SHA-1 collision affect Git?


    一个分支名称存储一个提交哈希 ID

    这是像master 这样的分支名称出现的地方。每个名称都存储一个哈希ID。根据定义,分支名称中的哈希 ID 是该链中的 last 提交。所以我们可以将上面的内容重新绘制为:

    A--B--C   <-- master
    

    name master 很容易被人类记住,它拥有提交 C 的实际哈希 ID。我们将检查这个提交,这将是我们的当前提交master 是我们的当前分支。所以现在我们在master,如果我们添加一个 new 提交——通过我们在这里没有描述的通常方式——Git 会做的是:

    • 打包一个新的快照;
    • 添加一些元数据:姓名、电子邮件地址等;
    • 在元数据中包含当前提交C的哈希ID;和
    • 将所有内容写成一个新的提交,这将获得一个新的唯一的大丑陋哈希 ID,但我们将称之为 D

    让我们画出来:

    A--B--C   <-- master
           \
            D
    

    如您所见,D 的父级是 C。现在git commit 执行它的特殊技巧:git commit最后 步骤是将D 的新哈希ID 写入名称 master。结果是:

    A--B--C
           \
            D   <-- master
    

    我们可以画出来:

    A--B--C--D   <-- master
    

    使用多个分支名称

    在我们创建D之前,让我们回到我们的三提交存储库:

    A--B--C   <-- master
    

    现在,在我们创建D 之前,让我们创建一个新的分支名称dev 用于开发。 Git 分支名称​​必须选择某个提交,那么我们应该选择三个提交中的哪一个?好吧,最新的很有意义,所以让我们使用C,我们通过名称master 使用的提交:

    A--B--C   <-- master, dev
    

    现在所有三个提交都在两个分支上。但是现在我们的绘图出现了问题:我们使用的是哪个名字?我们有两个名字!我们需要一种方法来判断我们实际使用的是哪个。现在它不是很重要,因为这两个名称都包含提交 C 的哈希 ID,但我们即将进行新的提交 D

    让我们选择要使用的名称devgit checkout dev,然后画成这样:

    A--B--C   <-- master, dev (HEAD)
    

    在这里,我们使用了特殊名称HEAD,全部大写,并将其附加到其中一个分支名称上。这告诉我们——以及 Git——我们正在使用哪个名称。

    现在让我们在使用 dev 名称时提交 D。 Git 会像以前一样写出一个新的提交,但是这一次,它更新的 namedev,而不是master。所以我们最终得到:

    A--B--C   <-- master
           \
            D   <-- dev (HEAD)
    

    新的提交D 现在是dev 分支中的最后一个 提交。提交A-B-C 现在在两个 分支上,提交Cmaster 分支中的最后一个提交。

    这就是它的全部内容!好吧,好吧,几乎全部。马上就会出现更多的皱纹。但这就是分支名称的全部含义:分支名称只是保存链中最后一次提交的哈希 ID。Git 将从这里开始,并在需要时向后工作到。

    短边栏:索引和你的工作树

    为了使这个答案更简短,我不会在这里详细介绍,但请考虑一下每个提交都被冻结的事实。每个提交中的文件都采用特殊的仅 Git 去重格式,只有 Git 可以读取。这些文件实际上有什么用?为了使用,文件必须可以被其他程序读取,并且通常至少有一些文件也需要可写

    所有版本控制系统都有这个问题,并且都使用类似的方法:有版本控制的“文件”,一直冻结,然后有一个单独的文件实际上可以使用。可用文件进入工作区。 Git将此工作区称为您的工作树工作树

    这意味着,当您使用 Git 存储库时,您看到和使用的文件实际上根本不在存储库中。它们已复制存储库(由git checkoutgit switch),以便您可以使用它们,但现在它们已经出来了,它们实际上在存储库之外。这些不是 Git 的文件:它们是你的。

    不过,Git 与大多数版本控制系统的不同之处在于,Git 保留了每个文件的第三份副本——嗯,有点像副本。这个额外的副本位于 Git 提交中的冻结文件和工作树中的可用文件“之间”。它采用 Git 的冻结和去重 格式,但实际上并未冻结,因为它不在提交中。这个额外的副本位于 Git 所称的 index暂存区 中,或者有时(现在很少见)在 cache 中。因为它是预先去重的,所以它不是真正的副本(Git 索引中的另一个是那些丑陋的大哈希 ID,用于内部 blob 对象,而不是直接的实际文件数据) .但将其视为副本效果很好。

    当您在工作树中更改的某个文件上运行 git add 时,您真正要做的是告诉 Git:使该文件的索引副本与工作树副本匹配。 Git 将删除并替换去重的冻结格式文件,制作该文件的新副本(但已经去重,如果它与任何以前的版本匹配),准备提交。

    因为索引保存每个文件的去重、准备提交副本,所以考虑 Git 索引的一个好方法是它保存您的建议的下一次提交。运行 git add 是您告诉 Git 的方式:现在使用我在工作树中所做的更新更改我提议的下一次提交。

    标签

    现在我们有一个很好的方法来绘制提交的内容,让我们绘制 标签 的作用。标签名称很像分支名称:它包含一个哈希 ID。在这种情况下,这是一个提交哈希 ID。

    分支名称和标签名称有几个关键区别:

    • 分支名称被强制保存提交哈希ID。标签名称可以包含其他类型的哈希 ID,这就是 带注释的标签 的意义所在。你会得到一个内部 Git 对象,它包含额外的信息——注解——然后包含一个哈希 ID:通常是一个提交哈希 ID。所以带注释的标签可以让你提交,但让你先添加信息。您提到您正在使用 轻量级标签,而那些只是直接保存提交哈希 ID,所以这就是我将在这里绘制的内容。

    • 分支名称​​move,正如我们在上面进行新提交时看到的D。无论您有什么分支名称作为附加头,这就是移动的名称。标签名称不动。3

    • 分支和标签名称在不同的namespaces 中。我们不会在这里详细介绍,但标签名称意味着比分支名称更“全局”:每个 Git 存储库都有自己的分支名称,但一般来说,当你连接两个克隆时并让他们分享,他们倾向于分享他们的标签名称,这样每个人都有相同的标签。

    由于标签名称不会移动,让我们绘制它。我们将从这个开始:

    ...--G--H   <-- master (HEAD)
    

    然后我们将添加一个标签名称,例如tag:ABC,如下所示:

    ...--G--H   <-- master (HEAD)
            ^
            |
         tag:ABC
    

    如果我们现在创建一个新的提交,我们将得到:

    ...--G--H--I   <-- master (HEAD)
            ^
            |
         tag:ABC
    

    请注意,我们可以这样画:

              I   <-- master (HEAD)
             /
    ...--G--H   <-- tag:ABC
    

    强调标签名称和分支名称非常相似。您可以使用分支名称,而实际上您使用了标签名称。区别——标签名称不会移动,但分支会移动,等等——主要是供人类使用的。 Git 本身并不真正关心:Git 关心的是 哈希 ID


    3可以移动标签。有几种方法可以做到这一点,最明显的是:删除标签,然后创建一个拼写相同但选择不同的提交的标签。这通常是一个坏主意,原因是人类和 Git 存储库都不期望标签移动。任何早先抓住“错误”标签的人都可能会坚持这个错误的价值观:你必须说服他们删除并重新创建,或者以其他方式移动,他们的副本标签也是。


    分离 HEAD 模式

    You noticed that when you run git checkout tag_ABC, or whatever the actual spelling is for your ABC tag, you wind up in detached HEAD mode. 那是因为HEAD 本身只能附加到一个分支名称。

    分支名称移动,它们最常移动的方法是将HEAD 附加到它们。标签名称不应该移动(再次参见脚注 3),为了强制执行,Git 不会将 HEAD 附加到标签名称。

    通常,您还可以检查任何历史提交,以某种方式查看或使用它。例如,假设您决定要查看提交G 一段时间,或者构建它,或者其他什么。您可以直接指示 Git 通过其原始哈希 ID 来检查该提交(例如,在 git log 输出中可以看到),您将得到以下信息:

             I   <-- master
            /
           H   <-- tag:ABC   # drawn on right to save space
          /
    ...--G   <-- HEAD
    

    “分离的 HEAD”只是意味着特殊名称 HEAD 直接指向某个提交。所以如果你现在git checkout ABC,你会得到:

              I   <-- master
             /
    ...--G--H   <-- HEAD
            ^
            |
         tag:ABC
    

    您的索引和工作树充满了来自提交 H 的文件。您的HEAD 标识提交H,您的标签也是如此。同时你的名字master 仍然标识提交I

    要退出分离 HEAD 模式,您只需 git checkout mastergit switch master。这会将HEAD 重新附加到分支名称,并将由分支名称标识的提交(在我们的图纸中提交I)提取到Git 的索引和您的工作树中,以便您查看该版本中的文件。

    激烈?也许

    您链接的其他答案包括:

    git checkout master
    git reset --hard tag_ABC
    git push --force origin master
    

    我不知道这是做什么的,但它看起来很危险/剧烈......

    很危险,是的:特别是 --hard 告诉 git reset 移除所有安全带并禁用所有安全气囊,就像以前一样,--force 是类似的。不过,它可能没有看起来那么激烈。

    git reset 命令非常复杂,但我们这里只看--hard 模式。4 这实际上是三件事:

    • 首先,它移动当前分支名称。为此,HEAD 必须附加到分支名称。这就是为什么我们有git checkout master

    • 然后,它会重置 Git 的索引,以便建议的下一个提交与您刚刚移动到的提交匹配。

    • 最后,它会重置您的工作树,以便您看到的文件是您刚刚移动到的提交中的文件。它这样做不会询问其中一些文件是否有你从未保存在任何地方的东西,并且由于这些文件不在 Git 中,任何被覆盖的数据,Git 都不能恢复,要么。这是最危险或最激烈的部分,就在那里。

    您选择的提交(此处为tag_ABC)是名称现在选择的提交,因此在此git reset --hard 之后,我们有这张图片:

              I   ???
             /
    ...--G--H   <-- master (HEAD)
            ^
            |
         tag:ABC
    

    您可能想知道:提交 I 发生了什么?答案是:什么都没有。它还在那里。但是你将如何找到它呢?

    如果您在 git reset 之前记下提交编号(哈希 ID),您可以通过这种方式找到提交 I。 Git 还有各种“从错误中恢复”日志和命令,可以让你再次找到提交 I。默认情况下,它们至少会再跟踪提交 30 天。所以 commit 仍然存在。你可以拿回来!

    git push --force 实际上更激烈,但要了解原因,我们需要讨论多个 Git 存储库,这部分确实有点复杂。


    4我认为这与git checkoutgit checkout 被拆分为git switchgit restore 之前的情况相同:它有太多的模式。新的拆分命令更简单,因为每个命令只做一些事情。重置可能也应该分开。


    其他 Git 存储库

    我们说 Git 是一个分布式版本控制系统 (DVCS)。这意味着什么可能还不清楚。将其称为复制的 VCS 可能会更好:例如,它的分布方式不像distributed computing。简而言之,它的工作方式是不同的 Git 存储库将相互连接,并且连接后,现在可以共享-复制-提交

    由于每个提交都有一个唯一的编号,因此两个 Git 可以通过传递数字来决定一个提交是否具有另一个提交。这就是git fetchgit push 的主要阶段的全部内容:两个Git 存储库之一有一些提交,而另一个可能没有。发送 Git 向接收 Git 提供哈希 ID。接收 Git 查看其 Git 对象的大数据库,并告诉发送者:请发送该文件不,谢谢,我已经拥有它

    当然,每个提交都会记住其提交的哈希ID。发送 Git 有义务提供它发送的每个提交的父级(或对于合并提交,父级复数)。因此,如果您连续三个新提交而他们没有提交,并且您告诉 Git 发送这三个中的最后一个,那么您的 Git 实际上会发送所有三个。

    虽然收到了一些新的提交,但接收的 Git 现在需要一些方法来找到这些提交。我们已经注意到,我们找到提交的一种方法是使用分支名称。所以接收 Git 可以设置一些分支名称来记住任何新的提交。

    git fetchgit push 命令在此处的工作方式有所不同:当您运行 git fetch 时,您的Git 是接收者,他们的 Git是发送的。您的 Git 没有设置您的 branch 名称。相反,您的 Git 设置了一些其他名称。这比git push 更漂亮(在很多方面也更好),但我们将跳过这一点并考虑使用git push

    当你运行git push 时,你的Git 是发送者,他们的Git 是接收者。您发送您拥有的、他们没有的、他们需要的任何新的提交。在此过程结束时,您的 Git 现在通常会发送一个礼貌的请求:现在,如果没问题,请将您的分支名称 ______ 设置为 ______。让我知道这是否可以。您的 Git 用分支名称填充第一个空白,第二个用哈希 ID 填充。

    您的 Git 要求他们设置的分支名称来自您的 git push 命令。如果你运行:

    git push origin HEAD:master
    

    master 这里的意思是他们的 master 分支。这里的HEAD 意味着您将要求他们设置的提交是您当前提交的任何提交。 (origin 部分是您指定要发送到的 Git 的方式。)

    使用时:

    git push origin master
    

    你真的是在说master:master,也就是说,你希望你的 Git 找到 你的 master 提交——链上最后一个以你的master 结尾的提交——并发送该提交,然后让他们设置他们的master

    所以,假设你和他们都开始:

    ...--G--H   <-- master
    

    你的 Git 和他们的 Git 是同步的。但是现在您在自己的master 上创建了一个新的提交I(无论您是否创建标签)。你现在有:

    ...--G--H--I   <-- master
    

    如果您运行 git push origin master,您的 Git 会调用他们的 Git 并提供提交 I。他们没有那个,所以他们说请发送。你的 Git 现在提供H,因为I 的父级是H;他们说不用了,我已经有了。您的 Git 现在知道他们拥有 G 和之前的所有内容,因为 H 是链中的最后一个提交,它们必须拥有整个链。5所有这些花哨的步法本质上都允许您的 Git 发送,而不是整个 I 提交,而只是 I 提交的部分,他们还没有。它非常高效,而且只需交换两个哈希 ID。

    不管怎样,你的 Git 会通过提交 I 发送——或者只是足以让他们重建它——他们现在有了 I。现在你的 Git 请求他们的 Git 取悦,如果可以的话,让他们设置 他们的 master 记住提交 I

    他们会说这没问题,他们会说原因是这只是添加到他们的收藏中。从I开始,他们可以回到H,所以他们不会失去H,也不会失去G,也不会更早。

    请注意,当您git push 一个标签时,您的 Git 会礼貌地请求他们创建或更新同名标签来结束对话。除了他们不应该移动标签,但应该移动一个分支名称,如果它只是添加,这几乎是一样的。


    5本文介绍了浅层存储库的工作方式,但我们现在不必担心。


    git push --force 危险的方式、时间和原因

    假设您已将提交 I 发送到另一个 Git,现在决定使用 git reset 撤回它,正如我们在上面看到的。您的存储库中有:

              I   <-- new/features
             /
    ...--G--H   <-- master (HEAD)
    

    因为您在执行git reset 之前巧妙地将提交I 的哈希ID 保存在一个新分支中(参见LeGEC's answer)。此外,你还做了一个git push origin new/features,让你的 Git 调用他们的 Git,向他们提供提交 I——他们已经拥有——并要求他们设置他们的 new/features 以记住提交 I。他们也同意了。所以就在那一刻,他们有:

    ...--G--H--I   <-- master, new/features
    

    但我们刚刚说过他们正在接受git push 命令。如果某个 third 用户有一个 third Git 存储库怎么办?

    假设第三个用户获取了提交I,并使用它创建了一个新的提交J。第三个用户(我们称他为 Bob)使 他的 存储库具有:

    ...--G--H--I--J   <-- master (HEAD)
    

    然后他运行git push origin master 将提交J 发送到您即将发送到git push --force 的存储库。他们接受提交J 并将其添加到他们的master

    他们现在有:

    ...--G--H--I   <-- new/features
                \
                 J   <-- master
    

    Bob 认为:太好了,我的工作已经完成,出于某种原因,Bob 删除了他的整个存储库。6 提交J 毕竟是安全的地方所有备份和一切,可能在 GitHub 或其他任何地方。7

    现在你来提供他们已经拥有的第二个存储库提交H,然后要求他们将他们的master 设置为指向H。默认情况下,他们会说no,原因是这会导致他们的master 放弃提交IJ

    当然,您知道他们已经提交了I,并且您希望他们放弃它。所以你使用git push --force。这会将最后一个操作从 Please, if it's OK 更改为 立即执行此操作!我命令它!如果他们服从这个命令——这取决于他们,但通常他们会服从——他们会尽职尽责地将他们的master 更改为指向H

    ...--G--H   <-- master
             \
              I   <-- new/features
               \
                J   ???
    

    您的 Git 存储库中,我们注意到如果您忘记先将其哈希 ID 保存在某个地方,您可以通过一些方法找到提交 I。这些方法依赖于 Git 所称的 reflogs。服务器存储库通常禁用 reflog,这意味着它们没有有办法再找到提交 J

    如果无法找到提交J,他们可能会很快删除完全提交J。他们的存储库丢弃了提交J。 Bob 提交了 J,但我们刚刚说 Bob 也删除了 他的 存储库。

    那么,这里发生的是 Bob 的提交 J 丢失了,可能永远丢失了。如果 Bob 保留他的存储库,Bob 仍然有他的提交,并且可以将其恢复到这个共享的 Git 存储库(在 GitHub 上,或任何可能的位置)。


    6这可能是个错误。 ?

    7It Ain’t What You Don’t Know That Gets You Into Trouble. It’s What You Know for Sure That Just Ain’t So.


    git push --force真的很危险吗?

    好吧,也许:如果我们确定没有 Bob,或者 Bob 小心翼翼地保留他的存储库,Bob 可以恢复丢失的提交。作为使用过这样的共享存储库的人(偶尔担任 Bob 角色但没有删除存储库),我会说作为存储库管理员并不是那么有趣。作为偶尔的救援,当然,没关系。只是不要让我一直这样做。 ?

    不过,还有一个不太危险的选择。考虑使用git push --force-with-lease,而不是git push --force。这个相当奇怪的名称实际上意味着最后一个请求或命令——请,如果可以,请将 _____ 设置为 _____将 _____ 设置为 _____!——更改为:我认为你的 _____ 设置为 _____。如果是这样,请将其更改为_____。无论如何,请告诉我。您的 Git 会填补所有这些空白:

    • 分支名称来自您的git push <em>remote</em> <em>mine</em>:<em>theirs</em> 命令。冒号后面的theirs——或者一个名称,如果你省略冒号,它提供了一切——是你要求他们的 Git 设置的分支名称。

    • 我认为你的是 _____ 空白是用你自己的 Git 的远程跟踪名称填充的。例如,如果您在origin 上向master 推送,则这是从您自己的origin/master 填写的。您可以在启动git push 之前检查此值(通常使用git log)。这样你就可以确切地知道你将要求他们丢弃哪些提交。

    • 将其设置为 _____ 空白从冒号的 mine 侧的哈希 ID 中填充,或者如果您仅使用分支名称,则从您的分支名称中填充一个名字代表一切。

    所以git push --force-with-lease origin master 的意思是打电话给origin,然后要求他们强制设置他们的master,但前提是它与我现在在origin/master 中看到的相符。因此,您可以在强制推送之前检查。如果由于您的检查错误而导致强制推送失败,这意味着 Bob(或任何人)设法在两者之间偷偷发送git push,您最好在您之前拿起 Bob 的新提交并弄清楚该怎么做再次强行推动。

    【讨论】:

    • 史诗般的帖子。我实际上读了两本关于 git 的书,但是在几个月没有使用它之后,我已经完全忘记了所有复杂的内部工作原理,因此不得不从头开始。因此,我正在尝试构建一套食谱以使 git 可用,而无需在每次使用前阅读一本书——作为一个对命令和选项记忆力很差的人。请注意,我确实在其他项目中一直使用分支。
    【解决方案3】:

    一个简短的答案,应该可以完成这项工作:

    git checkout -b my_tagged_branch tagname
    

    checkout -b 创建一个新分支并检查它。来自文档here

    $ git checkout v2.0  # or $ git checkout master^^
    
       HEAD (refers to commit 'b')
        |
        v a---b---c---d  branch 'master' (refers to commit 'd')
        ^
        |   tag 'v2.0' (refers to commit 'b')
    
    Notice that regardless of which checkout command we use, HEAD now refers directly to commit b. This is known as being in detached HEAD state. It means simply that HEAD refers to a specific commit, as opposed to referring to a named branch.
    

    然后您可以在 my_tagged_branch 上工作,提交。如有必要,您再次结帐大师,然后git merge my_tagged_branch
    如果您使用遥控器,请不要忘记推送,如果您希望稍后使用git merge --no-ff my_tagged_branch 看到该工作流程(结果当然是相同的,只需检查git log --graph --oneline

    有关详细信息,请参阅@toreks 答案。

    【讨论】:

    • 您好,感谢您的回复。所以如果我从旧标签创建一个新分支,这有什么帮助?接下来我该怎么做?大概,我需要提交新分支,然后签出master,然后在分支中合并,然后提交或类似的?
    • 这正是它的工作原理。有关详细信息,请参阅我的编辑。请不要忘记将答案标记为解决方案,如果它解决了您的问题...
    • 谢谢!关键是“git checkout master”,然后是“git merge my_tagged_branch”。我不知道 git 会做什么 - 它有效地掌握了一些行,而 my_tagged_branch 使用了一些旧代码而没有更改,这会赢吗?
    • 如果我对新创建的分支不做任何工作,我需要提交还是推送它?
    • 好的,上面试过了,没用。我试过这个:1)git checkout -b old_branch old_tag 2)git push origin old_branch 3)git checkout master 4)git merge --no-ff old_branch。在这一点上,它说“已经是最新的”。如果我打开要还原的文件,它会显示主版本,而不是分支中的版本。合并什么也没做。
    猜你喜欢
    • 2023-02-09
    • 2020-10-23
    • 2018-07-28
    • 2016-03-17
    • 2019-04-30
    • 2012-06-05
    • 1970-01-01
    • 2023-04-07
    • 1970-01-01
    相关资源
    最近更新 更多