git:如何在不丢失历史记录的情况下将旧的标记版本放入 master



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

我(轻量级)标记了正在生产的源代码版本,并将标签推送到源代码

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

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

这个答案:如何在 git 中将主分支恢复为标签?

是执行以下操作:

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

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

大概我需要像签出主分支,合并标签,或者签出标记的版本,然后合并主分支之类的东西?

例如

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

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

我已经看到您可以将主分支"位置标签"设置为提交。 所以我猜我只需要做

git reset XXX

其中 XXX 是标记中提交的提交号。 如果此方法有效,如何获取标签的提交号(git hist 或 git 历史记录在 mac 上不起作用)? 如果这这么容易,为什么会有力量和困难的东西?

如果我签出我的标签,并执行 git 状态,它会说"HEAD 在 mytag 分离">

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

更新

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

  1. 签出了旧的标记版本。
  2. 将我更改的文件内容剪切并粘贴到 onenote 中。
  3. 结帐头。
  4. 在编辑器中打开修改后的文件,然后粘贴 OneNote 中的内容。
  5. 将更改提交到主节点。

我相信有更好的方法。

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

这也许是这里真正的错误。 Git 中的分支既便宜又好,你应该开始使用它们。 请参阅LeGEC的食谱答案(但请通读所有这些注意事项和启发!

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

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

这意味着你应该学习 Git 在这里真正做什么。 只是有点复杂!


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


Git 是关于提交的

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

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

  • 每个提交都有一个唯一的编号。 不过,这些数字并不是简单的计数数字:它们不是从提交 #1 开始,而是计数到 #2、#3 等。 相反,每次提交都会得到一个看起来随机的——但实际上根本不是随机的——像385c171a018f2747b329bcfa6be8eda1709e5abd这样的丑陋的大哈希 ID

    。这些数字必须如此之大和丑陋,因为这个数字意味着从现在开始承诺。您的任何提交都不能使用该数字。阿拉伯数字提交的编号(其哈希 ID)实际上是该提交内容的加密校验和。 这意味着任何提交一旦完成就无法更改,这是巨大的后果。

    Git 可以查找提交或任何内部 Git 对象,但我们在这里只担心提交 - 通过丑陋的数字。 Git 存储库主要只是一个大的键值数据库,哈希 ID"数字"是键(好吧,加上第二个名称到哈希 ID 数据库,我们稍后会谈到)。

  • 每个提交存储两件事:

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

    • 它存储一些元数据或有关提交本身的信息。 例如,这包括提交者的姓名和电子邮件地址。 每次提交中都有一个日期和时间戳(实际上是其中两个),您的日志消息会转到此处。 但是,对于 Git 本身来说最重要的是,每个提交都在此元数据中存储了上一个提交的大丑陋哈希 ID。

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

A <-B <-C

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

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


2从技术上讲,你的 Git可以重复使用这个数字,只要你从不将你的 Git 引入持有 Git 本身的仓库的 Git。 有关此内容的更多信息,请参阅新发现的 SHA-1 冲突如何影响 Git?


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

这就是像master这样的分支名称的用武之地。 每个名称存储一个哈希 ID。 根据定义,分支名称中的哈希 ID 是该链中的最后一个提交。 因此,我们可以将上述内容重新绘制为:

A--B--C   <-- master

名称master 很容易被人类记住,它包含提交C的实际哈希 ID。 我们将检查此提交,这将是我们当前的提交master是我们当前的分支。 所以现在我们在master,如果我们添加一个新的提交——通过我们在这里没有描述的常用方式——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

让我们选择要使用的名称dev,并带有git checkout dev,并像这样绘制它:

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

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

现在,让我们在使用此dev名称时进行提交D。 Git 会像以前一样写出一个新的提交,但这一次,它更新的名称dev,而不是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 调用的索引暂存区域中,或者有时(现在很少)缓存中。因为它是预先删除重复的,所以它并不是真正的副本(Git 索引中的内容是另一个大丑陋的哈希 ID,用于内部blob 对象,而不是直接提供实际的文件数据)。 但是将其视为副本效果很好。

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

由于索引包含每个文件的已消除重复、随时可以提交的副本,因此考虑 Git 索引的一个好方法是它包含您建议的下一次提交。 运行git add是你告诉 Git 的方式:使用我在工作树中所做的更新,立即更改我建议的下一次提交。

标签

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

分支名称和标记名称之间有几个主要区别:

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

  • 分支名称移动,正如我们在上面进行新的提交D时看到的那样。 无论您将哪个分支名称作为附加的 HEAD,这都是移动的名称。 标记名称不会移动。3

  • 分支和标记名称位于不同的命名空间中。 我们不会在这里详细介绍,但标签名称比分支名称更"全局":每个 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 存储库都不希望标签移动。 任何之前抓住"错误"标签的人都可能会坚持这个错误的值:你必须说服他们删除并重新创建或以其他方式移动他们的标签副本。

<小时 />

分离式头部模式

您注意到,当您运行git checkout tag_ABCABC标签的实际拼写时,您最终会进入分离的 HEAD模式。 这是因为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模式。这实际上是三件事:

  • 首先,它移动当前分支名称。 要使其生效,必须将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 天。 所以提交仍然存在。 你可以把它拿回来!

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


4我认为这与git checkoutgit checkout被分成git switchgit restore之前一样:它有太多的模式。 新的拆分命令更简单,因为每个命令只执行几件事。 重置可能也应该分开。


其他 Git 存储库

我们说 Git 是一个分布式版本控制系统 (DVCS)。 这意味着什么可能尚不清楚。 最好将其称为复制的VCS:例如,它不像分布式计算那样分布。 简而言之,这种工作方式是不同的 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 不会设置您的分支名称。 相反,你的 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的答案)。 此外,您还做了一个git push origin new/features,让您的 Git 调用他们的 Git,为他们提供提交I(他们已经拥有),并要求他们设置new/features以记住提交I。 他们也说好。 所以就在那一刻,他们有:

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

但我们只是说他们正在接受git push命令。 如果第三个用户有第三个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。 默认情况下,他们会说,原因是这会导致他们的master放弃提交IJ

当然,您知道他们已经有提交I,并且您希望他们放弃它。 所以你用git push --force. 这会将最后一个操作从"请,如果现在可以执行此操作"更改为">请"! 我指挥它!如果他们服从这个命令——这取决于他们,但通常他们被设置为服从——他们会尽职尽责地改变他们的master指向H

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

I   <-- new/features

J   ???

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

如果没有办法找到提交J,他们可能会很快完全删除提交J。 他们的存储库删除提交J. 鲍勃已经提交了J,但我们只是说鲍勃也删除了他的仓库。

那么,这里发生的事情是,鲍勃的提交J丢失了,也许是永远的。 如果 Bob 保留他的仓库,Bob 仍然有他的提交,并且可以将其还原到这个共享的 Git 仓库(在 GitHub 上,或者它可能在任何地方)。


6这可能是一个错误。

7不是你不知道的,会给你带来麻烦。这是你确定的,只是不是这样。


git push --force真的危险吗?

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

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

  • 分支名称来自您的git pushremotemine:theirs命令。 冒号后面theirs(或者一个名称,如果你省略冒号,提供所有内容)是你要求他们的 Git 设置的分支名称。

  • 我认为你的是 _____空白从您自己的 Git的远程跟踪名称中填写。 例如,如果您要在origin上推送master,这是从您自己的origin/master中填写的。 您可以在开始git push之前检查此值(通常使用git log)。 这样你就可以确切地知道你要要求他们扔掉什么提交。

  • 将其设置为 ______空白是从冒号mine侧的哈希 ID 填充的,或者如果您只对所有内容使用一个名称,则从您的分支名称填充。

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

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

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

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

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

git checkout -b my_tagged_branch tagname

checkout -b创建一个新分支并将其签出。从这里的文档

$ 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,提交。如有必要,您可以再次签出 master,然后git merge my_tagged_branch.
如果您使用遥控器,请不要忘记推送,如果您希望稍后看到该工作流程使用git merge --no-ff my_tagged_branch(结果当然是一样的,只需与git log --graph --oneline检查即可。

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

相关内容

最新更新