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

Huy*_*hạm 1 git version-control github

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

 git push MyCodeBase <commit_SHA>:HEAD:main 
Run Code Online (Sandbox Code Playgroud)

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

git push MyCodeBase HEAD <commit_SHA>:main 
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

概括:

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

tor*_*rek 5

长话短说

\n

您想要的是语法,其内容类似于:git push repository refspecrefspec

\n
a123456:refs/heads/main\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

长的

\n

首先,在深入了解细节之前,请记住 Git 存储库的定义是 \xe2\x80\x94 或多或少1 \xe2\x80\x94提交的集合。Git 并不是真正更改、文件、分支或我们在根级别上使用它所做的任何事情有关:在该级别,它与提交有关。

\n

这意味着您需要准确地了解提交是什么以及对您的作用,这可以归结为以下几件事:

\n
    \n
  • 每次提交都会存储一组文件的完整快照。这些不是更改,而是快照。它们类似于 tarball 或 zip 文件。如果您要下载并解压这样的存档,您不会将其视为“更改”。当然,您可以下载两个档案并比较它们以发现更改。同样,你不应该将 Git 提交视为更改\xe2\x80\x94,但是如果你有两个提交并比较它们,你可以找到更改。

    \n
  • \n
  • 同时,每个提交还存储一些元数据:有关提交本身的一些信息。这包括提交者的姓名和电子邮件地址以及日期和时间戳。(事实上​​,它有两组数据:一组用于作者,一组用于提交者。)它有一条任意日志消息,无论谁进行提交,都可以写入该消息,以便稍后告诉人们为什么该特定提交存在。\xe2\x80\x94 对于 Git\xe2\x80\x94 至关重要的是,每个提交都存储了一定数量的父提交哈希 ID 。

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

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

    \n
  • \n
\n

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

\n

这样,从某种意义上说,哈希 ID就是提交。你要么有哈希 ID,在这种情况下你有提交,要么你没有,在这种情况下你找到一些 Git\xe2\x80\x94any Git Anywhere\xe2\x80\x94that does have that hash ID and get他们的承诺。

\n

那么,git pushgit fetch的作用是确保接收的Git\xe2\x80\x94forgit push是“其他”Git;对于git fetch,这就是您的Git\xe2\x80\x94,其中包含发送 Git 希望发送的部分或全部提交

\n
\n

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

\n

2只要出现一些非唯一哈希 ID 的两个 Git 存储库实际上从未相遇,Git 就可以在这个目标上失败。但是,Git 并没有尝试猜测哪些存储库可能会相互交换数据,哪些永远不会,而是尝试让每个提交哈希 ID 具有普遍唯一性。

\n
\n

Git 不“喜欢”独立提交

\n

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

\n

如果我们再次查看提交的定义\xe2\x80\x94一个存档加上元数据,其中元数据包括提交\xe2\x80\x94的一个或多个父级的原始哈希ID ,我们可以很快地绘制出一幅图这可能意味着:

\n
first-commit  <-... <-commit  <-commit  <-commit  <-... <-last-commit\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

实际的哈希 ID 看起来是随机的,而且非常大且丑陋,人类不可能记住,3因此出于绘图目的,我喜欢使用大写字母来代表哈希 ID:

\n
A <-B <-C   <--main\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

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

\n

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

\n
A--B--C   <-- main\n
Run Code Online (Sandbox Code Playgroud)\n

然后我们添加一个新的提交,新的提交会获得一些看起来随机的、又大又难看的唯一哈希 ID,我们将其称为D。在 内部,元数据包括当前提交D的哈希 ID (我们或 Git 通过名称找到它)。所以新的提交指向现有的提交。现在,为了使提交成为该分支的最后一次提交,Git 只需将\哈希 ID(无论是什么)写入名称中:CmainDCDDmain

\n
A--B--C--D   <-- main\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

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

\n
A--B--C   <-- main, develop\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n
A--B--C   <-- main\n       \\\n        D   <-- develop\n
Run Code Online (Sandbox Code Playgroud)\n

请注意,为了让 Git 使用commit做任何有用的C事情,比如显示其中发生的更改B,Git也需要 commit。为了进行提交,Git 需要先D提交。C一般来说,Git 希望并且需要从端点 \xe2\x80\x94 开始的每个历史提交,其哈希 ID 位于各个分支名称 \xe2\x80\x94 中,并向后工作到第一个提交。

\n

一般来说,这意味着在大多数 Git 存储库中,4都会有每次提交直到最后一次提交。这些提交Git 存储库中的历史记录。不存在“文件历史记录”这样的东西:每次提交都有每个文件的完整快照,作为一种存档。历史记录提交的集合,Git 通过从分支名称 \xe2\x80\x94 的末尾 \xe2\x80\x94 开始并向后工作来找到它。

\n
\n

3为了使它们具有普遍的独特性,这一切都是必要的。

\n

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

\n
\n

这对您意味着什么git push

\n

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

\n
git push <repository> <refspec>\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

真正的魔力在于 <refspec> 部分。refspec可以

\n
    \n
  • 分支名称本身;或者
  • \n
  • 原始提交哈希 ID,后跟冒号,后跟引用名称;或者
  • \n
  • 单词HEAD,后跟冒号,然后是参考名称
  • \n
\n

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

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

重复这一过程,直到他们最终说他们确实拥有哈希 ID,或者您的 Git 没有可用的提交,因为您已经提供了导致并包括您正在推送的提交的每一个历史提交。

\n

这可以让你的 Git 知道他们的Git 已经拥有你的哪些文件。这就是我上面提到的含义。如果你提供 commit C,而他们没有,但你提供 commitB而他们确实有,这会告诉你的 Git 他们有提交B ,并且 A两者都有,因此他们拥有提交中存在的所有文件。因此,您的 Git 现在可以压缩您的提交,知道它们已经提交,从而引用这些现有提交中的文件。BACA-B

\n

当然,如果他们没有这些提交\xe2\x80\x94,例如,如果这是一个新的、完全空的存储库\xe2\x80\x94,你的 Git 将必须发送直到你最后一个提交的每一个提交。 \'重发

\n

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

\n
\n

5语法不止一种;这是您在这里需要的通用的。

\n

6使用远程名称比仅此 URL 缩短功能添加了更多便利功能,但这就是我将在此处介绍的全部内容。

\n
\n

参考名称

\n

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

\n

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

\n

大多数时候,当您运行 时git push,您会要求 Git 将提交从一个或多个分支发送到其他某个 Git,当您这样做时,您希望它们设置其中一个分支来记住相同的最后一次提交。当你这样做时,例如:

\n
git push origin main\n
Run Code Online (Sandbox Code Playgroud)\n

或者:

\n
git push origin develop\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

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

\n
git push <url-or-remote> <hash>:refs/heads/somebranch\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

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

\n

请注意,当您运行时git push,Git 发送的是提交。它将提交\xe2\x80\x94和带有元数据\xe2\x80\x94的快照发送到另一个Git,然后该Git将它们暂时存储在隔离区域中。您的 Git 不会发送更改,而是发送整个提交。7 然后,您的 Git 会要求其 Git 创建或更新一些引用名称\xe2\x80\x94 分支名称、标记名称或您喜欢的任何其他类型的名称\xe2\x80\x94,以便记住您在push.

\n

如果你有未提交的代码,这些东西不在Git 中。您的 Git 实际上还无法发送它。要发送它,您的 Git 必须先提交它。8 但是您将把到目前为止的整个提交历史记录发送到您的其他存储库。如果这就是你想要的,\xe2\x80\x94,那就是\xe2\x80\x94,你都很好:去做吧。

\n
\n

7您的 Git 确实使用了压缩,这可能会将整个提交转变为推送操作\xe2\x80\x94 中的更改,但是这些更改(如果有)被构建,取决于您的 Git 所了解的 Git 的提交内容。他们的 Git 可能需要重新扩展这些内容,然后重新压缩它们,具体取决于许多因素。

\n

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

\n