【问题标题】:What will happen if I push changes from one branch to another?如果我将更改从一个分支推送到另一个分支会发生什么?
【发布时间】:2023-03-31 15:27:01
【问题描述】:

我的项目中有两个分支。

maintest_deploy

在没有任何测试的情况下将更改推送到主分支是不正确的,但没关系。

我使用 main 分支在本地进行一些更改,并使用 test_deploysettings.py 中进行其他更改(例如 DEBUG=False,使用云存储我的模型图像等)和部署文件(Procfile、runtime.txt 等)。

例如,我要添加一个新应用并将其推送到main 分支和test_deploy 分支(以获取我的项目的最新版本)

我有一个问题。如果我将这些更改提交到main 分支,将其推送到此分支并同时将其推送到test_deploy 分支,会发生什么?我会遇到必须手动修复的冲突吗?还是我先从test_deploy 分支拉取所有文件然后推送?(我不想从test_deploy 分支拉取文件。这就是我问这个问题的原因)

总结

所以,一般来说,我想将更改从 main 分支推送到 test_deploy,但不从 test_deploy 分支中提取单独的文件,因为它们在我在本地使用的主分支上是多余的。

【问题讨论】:

标签: django git github


【解决方案1】:

TL;DR

可能无法按照您想要的方式执行此操作。您可能想使用cherry-pick,如Alexandr commented

如果您认为 Git 是关于更改、文件和/或分支的,这将是 lead you down the garden path。不要那样做:将 Git 视为关于 commits,因为它确实如此。每次提交:

  • 有编号。提交具有唯一的哈希 ID。这是一个用hexadecimal 表示的(非常大,随机-看起来但根本不是随机的)数字。一旦 Git 将该编号分配给 您的 新提交,它现在对于 所有其他提交 都是禁止的。1 如果任何其他 Git 存储库声明要拥有该哈希 ID,他们必须有 您的 提交,而不是其他提交。

  • 是只读的:任何提交的任何部分(实际上是任何内部 Git 对象)都不能被更改。

  • 存储两件事:每个文件的完整快照(但经过压缩和 Git 化,并且至关重要的是,去重,因此即使一百万次提交使用一个大文件,也只有一个副本那个大文件)和元数据。元数据通常包括其他提交的原始哈希 ID(通常正好是一个,前一个或 提交)。

  • 找到它的哈希 ID,这意味着您必须向 Git 提供原始哈希 ID,或者提供 Git 可以用来查找原始哈希 ID 的东西。

分支名称为 Git 提供原始哈希 ID,因此分支名称允许 Git 找到一个特定的提交。然后,该提交提供 previous 提交的哈希 ID,以便 Git 可以找到 两个 提交。上一次提交提供 its 上一次提交的哈希 ID:这意味着 Git 可以找到 三个 次提交。第三次提交提供了另一个哈希 ID,然后我们继续。


1这不是—cannot be—永远正确,但是如果任何其他 Git 存储库使用相同的编号进行不同的提交,您将永远无法拥有您的Git 成功地从另一个 Git 获取或推送到该 Git。散列 ID 的大尺寸使其对于 millions er thousands urk tens来说似乎是正确的> 年(SHA-1 不再足够大,Git 正在转向 SHA-256)。


使用分支

我使用main 分支在本地进行一些更改...

这真正意味着你使用名称main 来定位某个特定的提交——最新的 提交“在”你的main。您让 Git 从该提交的快照中提取文件,以便您可以读取它们(请记住,只有 Git 可以读取快照中的 Git 化副本)并写入它们(进行更改)。

如果并且当您进行 new 提交时,Git 将:

  • 保存更新的文件;2
  • 保存一些元数据,说明您进行了此新提交;
  • 包括当前 (latest-on-main) 提交哈希ID 作为新提交的
  • 将所有这些内容一次性写入一个永远无法更改的冻结快照副本;和
  • 从而分配一个新的唯一哈希 ID,Git 现在将其写入您的姓名main

做出新的提交是最重要的部分。事实上,Git 将新提交的哈希 ID 保存在你的名字 main 下,这会“改变你的分支”:你的最新提交现在是你的新提交,其父项是 em> 您刚才的最新提交。所以这就是分支“增长”的方式,一次提交一个。

在这里要意识到的最后一件事是 Git 是分布式的: 在你的存储库中,所有提交是你从其他 Git 存储库获得的,你可以找到通过您的分支名称,或者有时通过您的远程跟踪名称,例如origin/main。他们在他们的 Git 存储库中拥有他们拥有的所有提交,他们可以通过他们的 分支 名称找到这些提交。 他们的分支名称是他们的;你的分支名称是你的。 让它们保持同步(是否同步,如果是,多久同步一次)取决于你:你的 Git 将它们的 branch 名称复制到你的 remote-tracking names 并且会自动更新这些名称,但您的 Git 不会更新它们的 branch 名称。

当您进行新的提交并更新您的 分支名称时,其他 Git 存储库中什么也没有发生 - 至今。这就是git push 可能出现的地方:

所以,一般来说,我想将更改从 main 分支推送到 test_deploy

这里注意一个重要问题:没有一个(main 分支。有你的名字main(在你的Git存储库中),他们的名字main(在他们的Git存储库中)和你的名字origin/main(在你的 Git 存储库中,记住——每当你将你的 Git 连接到他们的 Git 并让你的 Git 更新你的 Git 的内存时——他们在他们的内存中拥有什么)。

您的两个 Git 存储库之间共享的不是名称,而是提交。当你第一次克隆他们的存储库时,你得到了所有的提交并且没有一个分支:你的 Git 把他们所有的 branch 名字变成了 远程跟踪名称。然后,最后,您的 Git 使用您的 Git 存储在您的远程跟踪名称下的提交哈希 ID 创建了一个您自己的新分支名称:创建一个与 同名的分支名称还有很长的路要走 持有 相同的哈希 ID,但这正是它所做的。它不是一个分支,它是两个:两者都命名为main,但它们位于两个不同的 Git 存储库中。向您的main 添加提交不会向他们的main 添加任何提交,也不会向他们的test_branch 添加任何提交。


2您必须首先在每个更新的文件上使用git add,原因未包含在此答案中。您可以使用git commit -a 推迟了解这些原因,但这会让您对其他一些重要的 Git 事情一无所知,所以不要拖延太久。


画树枝

在我们讨论git push 的工作原理之前,让我们一些分支。让我们使用大写字母代替巨大的十六进制数字来代替提交哈希 ID:它们对人类更加友好。 (当然我们会很快用完——这就是 Git 使用大数字的原因——但我们会保持绘制的分支很小。)我们将从 maintest_branch 开始,我们将绘制如下这个:

...--G--H   <-- main
         \
          I   <-- test_branch

这是在您克隆它之前在他们的 存储库中的样子。请注意,main 上的最新提交是 Htest_branch 上的最新提交是稍后——它是 I——但 I 的父级是 H。这意味着main 上的所有提交也在test_branch 上。 test_branch 上只有一个更新的提交比 main 上的提交。

当你 git clone 这个存储库时,你只会得到一个 main,而不是 test_branch:你的 Git 将它们的 main 更改为你的 origin/main,将它们的 test_branch 更改为你的 origin/test_branch,并且然后从他们的main 创建你自己的main

...--G--H   <-- main, origin/main
         \
          I   <-- origin/test_branch

注意两个名称如何识别提交H。没关系!您可以拥有任意多个名称,所有名称都指向一个提交。如果愿意,您可以创建一个分支test_branch现在指向提交I

...--G--H   <-- main, origin/main
         \
          I   <-- test_branch, origin/test_branch

签出切换到main的行为告诉你的Git存储库从提交H中提取快照——名称为@987654375的那个@ 点 — 并将名称 main 设为 当前分支,我们可以通过在 main 旁边添加单词 HEAD 来绘制它。 git log 命令使用彩色文本执行此操作(HEAD -&gt; main,尤其是在git log --decorate --oneline --graph 输出中);我喜欢用括号中的main 来做到这一点:

...--G--H   <-- main (HEAD), origin/main
         \
          I   <-- test_branch, origin/test_branch

您现在更改了 工作树 中的一些文件(Git 放置可用副本的位置)并可能运行一些测试或其他什么,然后像往常一样运行 git addgit commit。这会生成一个新的提交,它会让你的名字main 指向新的提交。新提交指向现有提交H,如下所示:

          J   <-- main (HEAD)
         /
...--G--H   <-- origin/main
         \
          I   <-- test_branch, origin/test_branch

注意没有其他事情发生:其他名称没有受到干扰,并且现有提交没有更改:H 仍然向后指向 GI 仍然向后指向 H ,并且新的J 也向后指向H

现在我们可以谈谈git push 的工作原理。请记住,git push 调用了在另一个 Git 存储库上运行的另一组 Git 软件。我们将使用短名称origin,它存储您的 Git 软件到达其他 Git 软件的 URL。你可能会跑:

git push origin ...

稍后我们将填写... 部分。你的 Git 会调用他们的 Git,然后两者将通过哈希 ID 讨论提交。你的 Git 会告诉他们新的提交 J,他们不会拥有,然后告诉他们提交 J 的父 H,他们确实拥有。所以他们会说请发送J,但我不需要H,因为我有这个,而且之前的每一次提交也是如此。您的 Git 现在可以找出一个最小的包,它将向他们提供他们重新创建提交 J 所需的一切,并在 他们的 存储库中使用提交 J 的相同哈希 ID。

因此他们最终得到了这样的结果:

          J   [no way to find it yet]
         /
...--G--H   <-- main
         \
          I   <-- test_branch

请记住,他们没有任何 origin/ 名称,只有他们的分支名称。

git push 的最后一步将是你的 Git 要求他们的 Git 设置一个他们的分支名称。您可以选择现有名称(例如maintest_branch)或全新的名称。您将让您的 Git 向他们发送一个礼貌的请求:如果可以,请创建或更新您的姓名 ________ 以指向提交 J。让我知道是否可以。

您的 Git 将根据您提供给 git push 的内容在此处填写提交 J 的哈希 ID。您的 Git 将根据您为同一 git push 命令提供的内容填写名称的空白。这决定了 ... 部分的内容:

git push origin main:new_branch

将向他们发送提交J,因为main:new_branch 左侧的部分显示main,这意味着您的 存储库中提交J,其中main 指向 J。它将用名称new_branch 填充空白,因为它位于main:new_branch 中冒号的右侧。

如果你只是运行:

git push origin main

您的 Git 将在此处使用单一名称 —main — 来在您的存储库中找到提交(提交 J),并填写空白 为其存储库中的名称 (main)。所以这会要求他们将提交 J 添加到他们的 main

不过,您希望他们将test_branch 设置为指向提交J,因此您可以使用:

git push origin main:test_branch

这要求他们设置他们的名字test_branch 指向提交J

但如果他们这样做了呢?他们现在有:

          J   <-- test_branch
         /
...--G--H   <-- main
         \
          I   [lost!]

他们不再有办法找到提交I。他们不能使用他们的main:指向提交H。他们不能使用他们的名字test_branch,如果他们更新它,因为它将指向J,它链接回H,它又链接回G,等等。没有人链接 forward(永远,在 Git 中),因此无法找到提交 I

简而言之,他们会对您的礼貌请求说。你会从你的git push 得到一个! rejectednon-fast-forward 错误,这是 Git 的行话式的说法,“如果我这样做,我会丢失一些提交”。

那么你能做什么呢?

这里的问题并不是真正的 分支名称 本身,而是您的新提交 J 链接回现有提交 H 的事实,那就是 怎么了。您需要一个链接回现有提交 I 的新提交——他们的 test_branch 上的最新提交。

请注意,在您开始执行任何操作之前,建议您先运行:

git fetch origin

这一步让你的 Git 调用他们的 Git。他们列出了他们所有的分支名称和提交哈希 ID——与你第一次运行 git clone 时所做的相同——你的 Git 现在可以确定他们是否有任何你没有的提交。例如,也许有人在test_branch 上添加了一个新的提交K,这样他们现在就有了:

...--G--H   <-- main
         \
          I--K   <-- test_branch

如果是这样,你的 Git 将从他们那里获得新的提交 K——你的 Git 可以告诉你它是新的,因为你没有那个编号的提交——然后会更新你的 origin/test_branch——你对他们@的记忆987654460@——匹配。

现在(更新后)是时候创建您自己的test_branch,指向他们在他们的 test_branch 上的最后一个提交。如果他们仍在提交I,那很好。如果他们添加了提交K,甚至添加了几十个提交,那也没关系。你最终会得到你自己的test_branch 指向与他们的test_branch 相同的提交,这是你的origin/test_branchgit fetch 之后标识的提交。

无论如何,无论他们现在拥有什么,您现在都可以复制您现有的提交 J 到一个新的且略有不同的提交 - 我们称之为 J' 以显示它有多相似到J——添加到他们的test_branch 结束的任何地方。您现在将运行git switch test_branch 来创建它,或者运行git switch test_branch &amp;&amp; git merge --ff-only origin/test_branch 来更新它,或者甚至可能运行git switch test_branch &amp;&amp; git reset --hard origin/test_branch 来更新您的test_branch

          J   <-- main
         /
...--G--H   <-- origin/main
         \
          I--K   <-- test_branch (HEAD), origin/test_branch

现在你运行:

git cherry-pick main

它会指示您的 Git 找到提交 J(通过您的名字 main 找到)并尽最大努力复制该提交。复制提交涉及找出提交更改的内容,即运行git diff,然后在您现在所在的任何地方应用该更改(在上图中的提交K 上)并根据结果进行新的提交。从技术上讲,cherry-pick 是一个完整的三向合并,但如果您愿意,您可以将其视为“差异和补丁”过程,并且您不会离得太远。 /p>

假设一切顺利,最终结果如下所示:

          J   <-- main
         /
...--G--H   <-- origin/main
         \
          I--K   <-- origin/test_branch
              \
               J'   <-- test_branch

git show main 显示HJ 中的快照之间的差异,git show test_branch 显示KJ' 中的快照之间的差异,将显示 对H-vs-J中的相同的文件进行了相同的更改(必要时进行调整)。

您现在可以运行了:

git push origin test_branch

它使用你的名字 test_branch 来定位提交 J',将该提交发送到 origin——他们已经拥有了 之前 J' 之前的所有内容,所以你只需发送一个提交——然后让他们将提交J' 添加到他们的 test_branch。由于这增加了K,他们可能会接受这个礼貌的请求。

【讨论】:

    猜你喜欢
    • 2015-01-01
    • 2021-12-26
    • 2021-06-27
    • 2022-11-15
    • 2020-02-20
    • 2021-10-11
    • 1970-01-01
    • 2019-12-10
    • 2020-06-03
    相关资源
    最近更新 更多