繁体   English   中英

(Git) 在不丢失当前更改的情况下将先前的提交推送到新的存储库

[英](Git) Push a previous commit to a new repository without losing current changes

我一直在开发一个应用程序。 随着应用程序的扩展,创建自己的代码库以供将来重用是常识,因此我不必重新做所有事情。 我创建了一个名为 MyCodeBase 的全新存储库。 现在我想将特定的先前提交推送到这个新分支,而不会丢失我已经对当前存储库所做的任何更改(未暂存的文件和未推送的提交) 这个任务可以吗? 我试过的:

 git push MyCodeBase <commit_SHA>:HEAD:main 

(错误:src refspec <commit_SHA>:HEAD 不匹配任何)

git push MyCodeBase HEAD <commit_SHA>:main 

(错误:您提供的目的地不是完整的参考名称)我们试图猜测您的意思:

  • 在远程端寻找与“main”匹配的引用。
  • 检查被推送的 ('<commit_SHA>') 是否是“refs/{heads,tags}/”中的引用。 如果是这样,我们添加一个相应的
    refs/{heads,tags}/ 远程端的前缀。 都没有效果,所以我们放弃了。 您必须完全符合裁判的资格。 提示:refspec 的一部分是一个提交对象。 提示:您的意思是通过推送到 <commit_SHA>:refs/heads/main 来创建一个新分支
git push MyCodeBase <commit_SHA>:main

错误:您提供的目的地不是完整的引用名称(即以“refs/”开头)。 我们试图猜测你的意思:

  • 在远程端寻找与“main”匹配的引用。
  • 检查被推送的 ('<commit_SHA>') 是否是“refs/{heads,tags}/”中的引用。 如果是这样,我们添加一个相应的
    refs/{heads,tags}/ 远程端的前缀。

都没有效果,所以我们放弃了。 您必须完全符合裁判的资格。 提示:refspec 的一部分是一个提交对象。 提示:您的意思是通过推送提示来创建一个新分支:'<commit_SHA>:refs/heads/main'? 错误:无法将一些引用推送到“https://github.com/<my_user_name>/MyCodeBase.git”

概括:

  • 有:具有未提交更改的当前存储库。 一个空白的存储库
  • 想要:将先前的提交推送到空白存储库,而不会丢失未提交的更改

TL; 博士

你想要的是git push repository refspec语法, refspec是这样的:

a123456:refs/heads/main

确保您确切地知道它的作用(即,阅读较长的部分)。

首先,在深入了解细节之前,请记住 Git 存储库的定义(或多或少1)是提交的集合 在根级别,Git更改、文件、分支或我们用它做的许多事情中的任何一个无关:在该级别,它与commits 相关

这意味着您需要准确地知道提交是什么以及为您做什么,这可以归结为以下几点:

  • 每个提交存储一些文件集的完整快照。 这些不是变化,这些是快照。 它们类似于 tarball 或 zip 文件。 如果您要下载和解压这样的档案,您不会将其视为“更改”。 当然,您可以下载两个档案并进行比较以查找更改。 同样,您不应该将 Git 提交视为更改——但是如果您有两个提交并比较它们,您可以找到更改。

  • 同时,每个提交也存储了一些元数据:一些关于提交本身的信息。 这包括提交者的姓名和电子邮件地址以及日期和时间戳。 (事实上​​,它有两套这样的数据:一套是作者的,一套是提交者的。)它有一个任意的日志消息,任何提交的人都可以写入,以便稍后告诉人们为什么该特定提交存在。 而且——对于 Git 来说至关重要——每个提交都有一些存储在其中的父提交哈希 ID

  • 每个提交都有一个唯一的哈希 ID。 当我说独特时,我也不是说有点独特 我什至不是“非常独特”的意思,这在某些方面是对这个词的误用。 作为一种目标,Git 有一个想法,即为任何人在任何地方所做的每一次提交提供一个哈希 ID,该哈希 ID 是该提交所特有的。 2

  • 任何提交的任何部分都不能更改,一旦完成并获得了其唯一的哈希 ID。

这个唯一的哈希 ID 和提交的不变性质意味着一个 Git 总是可以通过让两个 Git 交换哈希 ID来判断其他某个 Git 是否已经具有相同的提交 他们不需要交换整个文件集,甚至不需要交换文件的某个子集。 哈希 ID 本身就说明了整个故事。

这样,从某种意义上说,哈希 ID就是提交。 你要么有哈希 ID,在这种情况下你有提交,要么你没有,在这种情况下你会找到一些 Git——任何地方的任何 Git——确实有哈希 ID 并从它们那里获取提交。

那么, git pushgit fetch是关于确保接收Git 的——对于git push ,那是“另一个”Git; 对于git fetch ,这就是您的Git——具有发送方Git 希望发送的部分或全部提交


1我稍后也会谈到“或多或少”的部分。

2只要出现某些非唯一哈希 ID 的两个 Git 存储库实际上从未真正满足, Git 就可以在此目标中失败。 但与其尝试猜测哪些存储库可能会相互交换数据,哪些永远不会,Git 试图让每个提交哈希 ID 普遍唯一。


Git 不“喜欢”独立提交

可以说,“Git 哲学”是您始终拥有存储库的所有历史记录 但是存储库的历史究竟什么?

如果我们再看一次提交的定义——档案加元数据,元数据包括一个多个提交的父级或父级的原始哈希 ID——我们可以很快地描绘出这可能意味着什么:

first-commit  <-... <-commit  <-commit  <-commit  <-... <-last-commit

来自提交的每个“箭头”,在这里,实际上是较早提交的哈希 ID 我们说较晚的提交指向较早的提交。

实际的哈希 ID 看起来是随机的,而且又大又丑,人类无法记住, 3所以为了绘图目的,我喜欢使用大写字母来代表哈希 ID:

A <-B <-C   <--main

这是一个只有三个提交的小型存储库的绘图。 它也只有一个分支名称main 名称main用于让 Git 知道三个提交中的哪一个是最后一个。

显然,在像这样的小型存储库中,我们可以只查看所有三个提交。 一提交A在所有-doesn't点回:这是最先提交,并且它不能。 一个指向A ,那一定是我们调用B的第二次提交,最后一个指向B ,所以这必须是三个提交中的最后一次提交。 但是在一个非常大的存储库中,可能会有成千上万的提交。 找到“最后一个”会花费太长时间,并且还有其他缺点。 因此,Git 将分支名称和其他名称(例如标记名称)添加到组合中。 这就是使存储库或多或少成为提交集合的原因:它实际上是提交和一些名称的集合,我们可以通过这些名称找到一些特定的提交。

分支名称特别是找到分支的最后一次提交。 这也是我们向存储库添加提交的方式。 如果我们在main ,并且有:

A--B--C   <-- main

然后我们添加一个新的提交,新的提交会得到一些看起来随机的、丑陋的、独特的哈希 ID,我们将其称为D D ,元数据包括当前提交C的哈希 ID(我们或 Git,通过名称main找到)。 所以新的提交D指向现有的提交C 现在,为了使提交D成为该分支的最后一次提交,Git 只需D的哈希 ID 写入名称main

A--B--C--D   <-- main

现在我们在我们的分支上有更多的提交。

如果我们改为添加一个分支,我们可能会从这个开始:

A--B--C   <-- main, develop

如果我们on branch develop ,正如git status所说,并且我们运行git commit并进行新的提交D ,Git 以与往常相同的方式进行新的提交,但这次 Git 写入的分支名称develop而不是main ,产生:

A--B--C   <-- main
       \
        D   <-- develop

请注意,为了让 Git 对提交C做任何有用的事情,比如显示其中的更改,Git 也需要提交B 要提交D ,Git 需要先提交C 一般来说,Git 想要并需要从端点开始的每个历史提交——它们的哈希 ID 位于不同的分支名称中——并返回到第一个提交。

这通常意味着,在大多数 Git 存储库中, 4每次提交都会导致最后一次提交 这些提交Git 存储库的历史记录。 没有“文件历史”这样的东西:每个提交都有每个文件的完整快照,作为一种存档。 历史记录一组提交,Git 通过从末尾开始(从分支名称开始)然后向后工作来找到这些提交。


3这一切都是必要的,这样它们才能普遍独一无二。

4 Git 支持所谓的浅层存储库,其中的历史记录在某些时候会中断,但一般来说,您不想使用这些。


这对你的git push意味着什么

当您运行git push ,您是在告诉您的 Git 将某些特定提交发送到某个其他 Git 存储库。 其语法为: 5

git push <repository> <refspec>

此处的 <repository> 部分可以是 URL,也可以是远程名称,例如origin 使用名称会增加各种便利功能。 例如,Git 将使用该名称查找 URL,从而避免重复键入相同的长且容易出错的 URL。 6

真正的魔法在 <refspec> 部分。 refspec可以是:

  • 一个分支名称,本身; 要么
  • 原始提交哈希 ID,后跟一个冒号,后跟一个引用名称 要么
  • 单词HEAD ,后跟冒号,后跟参考名称

或者更多的选择,其中大部分我不会在这里讨论。 您正在尝试使用中间或最后一个选项,并且在使用最后两个选项时,名称通常必须是完全限定的引用名称 我们稍后会回到这个问题,但在我们做之前,让我们看看git push会做什么:

  • 给定哈希 ID 或名称HEAD ,您的 Git 将查找相应的提交。
  • 然后,您的 Git 会将该哈希 ID 提供给另一个 Git。 如果他们已经有了那个提交,他们就会对你的 Git 说:不,谢谢,我已经有了那个。 这有一些影响。
  • 如果他们没有那个,你的 Git 现在必须提供该提交的父提交哈希 IDs 大多数普通提交只有一个哈希 ID; 合并提交有两个或更多; 和根提交没有。 无论提交的类型是什么,您的 Git 都有义务提供所有的父项。

如此重复,直到他们最终说他们确实拥有哈希 ID,或者您的 Git 用完了要提供的提交,因为您已经提供了导致并包括您正在推送的提交的所有历史提交。

这允许您的 Git 知道他们的Git 已经拥有您哪些文件。 这就是我上面提到的含义。 如果您提供提交C ,而他们没有,但是您提供了提交B并且他们确实有提交,这会告诉您的 Git 他们同时提交BA ,因此他们拥有提交中存在的所有文件BA已经。 因此,您的 Git 现在可以压缩您的提交C ,知道他们已经提交了AB并因此引用了那些现有提交中的文件。

当然,如果他们没有这些提交——例如,如果这是一个新的、完全空的存储库——你的 Git 将不得不发送每个提交,直到你发送的最后一个提交

一旦这一切都完成了,你的 Git 现在会要求他们的 Git 设置他们的名字之一。 现在让我们结束本节,并正确描述参考


5有不止一种语法; 这是您在此处需要的通用工具。

6使用远程名称增加了更多的便利功能,而不仅仅是这种缩短 URL 的功能,但这就是我将在这里介绍的全部内容。


参考名称

上面我提到过,Git 使用每个分支名称来存储我们想要说的“在分支上”的最后一次提交的哈希 ID,而且 Git 有不止一种名称。 其他类型的名称包括标签名称、远程跟踪名称、处理git stash的“stash”等等。 作为用户,您通常处理的三种名称是分支名称、标签名称和远程跟踪名称,如origin/main

这些名称中的每一个都位于一个命名空间中 特别是分支名称位于refs/heads/ ,而标签名称位于refs/tags/ 这意味着分支main实际上是名称refs/heads/main 标签v1.2实际上是refs/tags/v1.2

大多数时候,当您运行git push ,你问你自己的Git从一个发送提交或更多的分支机构,以一些其他的Git,当你这样做,你希望他们及其分支机构要记住相同的集合中的一个最后提交 当你这样做时,例如:

git push origin main

要么:

git push origin develop

您通常希望他们设置同名的分支。 所以在这里,Git 允许您省略refs/heads/部分冒号以及整个其他部分。 你的 Git 发现main真的意味着refs/heads/main:refs/heads/main 也就是说,您希望您的分支名称main确定您将发送的最后一次提交,然后您希望您的 Git 也要求他们的 Git 设置他们的分支名称main

但是,您想要使用原始哈希 ID。 然后,您的 Git 不知道是否应该要求他们的 Git 设置标签名称、分支名称或其他类型的名称。 您需要做的是使用完全限定的名称:

git push <url-or-remote> <hash>:refs/heads/somebranch

这将要求他们的 Git 在他们的 Git 存储库中创建或更新一个分支名称somebranch ,以记住作为其最后一次提交,即您在git push行中使用其哈希 ID 的提交。 如有必要,它将产生发送该提交及其所有历史记录的副作用。

您实际上无法推送未提交的更改

请注意,当您运行git push ,您的 Git 发送的是commits 它将提交(带有元数据的快照)发送到另一个 Git,然后将它们存储在隔离区域中片刻。 您的 Git 不会发送更改,而是发送整个提交。 7然后你的 Git 要求他们的 Git 创建或更新一些引用名称——分支名称、标签名称或任何其他你喜欢的名称——以便记住你在push命名的特定提交。

如果你有未提交的代码,这些东西不在 Git 中 你的 Git 实际上还不能发送它。 要发送它,您的 Git 必须先提交它。 8但是,您将到此为止的整个提交历史记录发送到您的其他存储库。 如果这就是你想要的——通常是——你都很好:去吧。


7您的 Git 确实使用了压缩,这可能会将整个提交转换为推送操作中的更改——但是这些更改(如果有的话)取决于他们的Git 有哪些提交,您的 Git 知道这些。 他们的 Git 可能需要重新扩展这些,然后再重新压缩它们,这取决于许多因素。

8 当然,可以进行不在任何分支上的临时提交; 然后你的 Git 可以发送这些。 例如,这种临时提交不在任何分支上就是git stash工作方式。 但是现在 Git 没有这样做,接收方 Git 需要使用一些名称来记住它们,这意味着我们又回到了整个 refspec 问题。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM