将更改推送到主分支以外的分支

Pushing changes to branch other than main

我从 github 克隆了一个回购,我做了一些更改。我在 github 上的 repo 有 2 个 运行ches,maindev。我想先将我的更改推送到 dev,然后再推送到 main。

我运行:

git branch

在克隆的(本地)回购中,给我输出:

*main

我没有看到我在 github 上制作的开发 b运行ch。

如何将更改推送到开发 b运行ch?

git branch只显示本地分支,要显示远程分支使用git branch -r(要显示本地远程分支,使用git branch -a) .

您可以通过指定源(本地)和目标(远程)分支将任何本地分支推送到任何远程分支:

git push origin main:dev

会将本地主分支上的提交推送到存储库“origin”中的远程开发分支。

一样,git branch 显示 您的 分支名称。其他一些 Git 存储库,例如 GitHub 上的存储库,将具有 自己的 分支名称。

Git 中重要的实际上不是 分支名称 。这些是给的,不是给Git的。更准确地说,它们的存在是为了 Git 可以帮助您找到您想要的 commits。这是因为 Git 实际上是关于 提交 ,而不是关于分支——尽管我们将我们的提交 组织到 分支——而不是关于文件,尽管每次提交都包含文件的快照。

这意味着当你使用Git时,你必须首先考虑提交。您需要在某种直觉层面上确切地知道提交是什么以及为您做什么。否则 Git 所做的所有疯狂事情都将令人沮丧和不可能,并且 you will live in this xkcd comic.

Git 都是关于提交的,那么什么是提交呢?它对你有什么作用?

每个 Git 提交:

  • 已编号。每个提交都有一个 唯一 编号,表示为 哈希 ID(或 object ID ) 在 hexadecimal. This is the commit's true name, without which Git cannot find the commit. (For fun, see also the TV trope.) 哈希 ID 又大又难看,人类不可能记住(或者在大多数情况下发音为 ),所以 Git 有时让我们使用缩短的版本。

  • 完全,完全,read-only:永远冻结。那是因为数字一旦分配,就意味着 提交,这意味着在 每个存储库 中。当您的 Git 存储库将此提交发送到其他 Git 存储库时,其他 Git 将使用 相同的编号 。它永远不会将那个数字用于任何其他提交,甚至不会在你做出那个提交之前。 (这就是神奇之处,在 Git 中,如果您了解散列理论、密码学等,您就会知道这实际上 不能 起作用。有一天,Git 会失败。散列 ID 的绝对大小将那个时间推到我们需要的未来,或者至少,我们希望如此。)

  • 包含两件事:所有文件的快照Git当时你(或任何人)知道made 提交,以及一些 元数据 ,或有关提交本身的信息,例如提交人的姓名。

每个提交的元数据中有一堆信息,但 Git 本身的关键项是每个提交包含一个 list——通常只有一个entry long—previous commit 或 commits 的原始哈希 ID。 Git 将这些称为提交的 parents

每次提交中的快照会一直冻结每个文件,因此拥有存储库的任何人都可以取回每个文件的任何版本。这些文件以一种特殊的格式存储,只有 Git 可以读取,实际上没有任何内容可以写入,并且它们在提交内和提交之间都是 de-duplicated,所以事实上,大多数提交主要是 re-use 来自早期提交的文件,这意味着大多数提交很少 real space。如果你做了一个完全 re-use 旧文件的新提交——这有几种可能——那么新提交实际上根本不需要 space 文件,只有一点点 space 来保存自己的元数据。

快照意味着一次提交使您能够查看每个文件 与您(或任何人)进行提交时的情况完全一样。但是你不能把committed的文件当作file来使用,因为它是一种特殊的Git-only格式,你不能write ] 就像您的计算机希望的那样。所以这意味着要使用一个快照,你必须Git提取它。我们不会在这里讨论任何细节,但这是 git checkoutgit switch 在您切换到特定提交时正在做的事情:Git extracts 已提交的文件,就像来自存档一样(因为它们 在存档中)。然后,您可以使用或处理提取的文件,而不是 Git 中存储的文件。 这意味着当您使用文件时,这些文件不在 Git. 中,您最终将不得不进行新的提交,以将新的快照存储到Git.

同时,元数据为您提供了几样东西:

  • 它让您知道谁做出了提交git log 命令将打印用户的姓名和电子邮件地址,以及一个 date-and-time 戳记。 (对于 --pretty=fuller,您会发现每次提交实际上有两个;这部分是 Git 早期的“每个人都通过电子邮件发送补丁”用法遗留下来的。)

  • 它告诉你他们想告诉你什么他们为什么他们做出承诺:这是 teir 日志消息。日志消息的重点不是说 他们做了什么——Git 可以通过比较 this 提交中的快照来表明此提交的 parent 中的快照——而是 为什么 例如,他们将 i++ 替换为 i += 2。它修复了 Bug#123 了吗?这是功能增强吗?那种东西可以放在日志消息中。

  • 使用 parent 元数据,Git 可以 将提交串在一起,向后 。这就是历史:随着时间的推移,这就是这个项目中发生的事情。通过读取 latest 提交,我们找到了当前的源快照,并通过使用它的元数据,我们找到了它更早的 parent 提交。使用存储的哈希 ID,Git 现在可以向您显示 parent 提交。该提交包含 still-earlier grandparent 提交的哈希 ID,因此 Git 现在可以向您展示 grandparent;该提交包含一个 still-earlier 提交哈希 ID,依此类推。

这意味着存储库中的提交存储库中的历史。访问所有 历史,Git 需要 最新 提交。

分支名称帮助你(和Git)找到最新提交

让我们绘制一些提交。我们假设我们有一个只有三个提交的小型存储库。他们会有三个 random-looking、big-and-ugly 哈希 ID,没人能记住或发音,所以我们称他们为提交 ABC反而。提交 C 将是最新的,因此它的元数据中将包含较早提交 B 的实际哈希 ID。我们说提交 C 指向 提交 B,我们这样画:

    B <-C

但是 B 是一个提交,所以它有一个 parent 散列 ID 的列表——再次只是一个提交长——这意味着 B 指向它的 parent A:

A <-B <-C

A 也是一个提交,但是,作为 有史以来的第一个提交 ,它有一个列表 no parents(parents 的空列表)并且不向后指向。这就是 git log 知道停止倒退的方式:什么都没有了。

但是:Git 如何找到正确的哈希 ID 以 提取 提交 C 以便您可以首先处理/使用它?请记住,哈希 ID 看起来是随机的。它们是不可预测的(因为除其他外,它们非常依赖于您进行提交的确切秒数)。只有一种方法可以知道散列 ID,那就是将其保存:写在某处。我们可以自己写下来,然后一遍又一遍地输入,但这一点也不好玩。所以 Git 为我们存储它们。 这就是分支名称。分支名称只存储一个哈希ID,即最新提交的ID.

如果我们有一个名为 main 的分支,那么 Cmain 的最新提交,那么名称 main 包含提交的哈希 ID C。和前面一样,我们说这个指向提交C,用箭头画出来:

A <-B <-C   <--main

在这一点上,我喜欢偷懒,停止绘制从提交向后到上一次提交的箭头 as 箭头,因为图纸即将发生什么,因为我不'有一个好的“箭头字体”可用。这没关系,因为一旦我们进行了提交,从该提交到其 parent 的 backwards-pointing 箭头将一直冻结,就像任何提交的所有部分一样。它必须向后指向,因为我们不知道任何未来哈希ID可能是什么,所以我们只知道它们向后指向:

A--B--C   <-- main

箭头来自分支名称,但是,随时间变化。让我们在 main 上进行新的提交,方法是使用 git switch maingit checkout main 将 select 提交 C 作为我们将处理/处理的提交,然后做一些工作,运行宁 git addgit commit 进行新的提交 D,像这样:

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

新提交 D 指向上一个提交 C。但是现在 D——不管它的真实哈希 ID 是什么——是最新的提交,所以名称 main 需要指向 D。所以git commit的最后一步就是GitD的hash ID写入名字main:

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

(现在我们又可以把整个东西画在一条线上了)。

如果在创建 D 之前,我们创建一个 新分支名称 ,比如 dev,会发生什么情况?让我们画一个,看看会发生什么:

A--B--C   <-- dev, main

我们进行新的提交 D:

A--B--C
       \
        D

更新哪个名称? Git 的答案很简单:Git 更新我们签出的任何分支名称。 所以我们需要知道,我们现在是使用 main 这个名字,还是现在使用 dev 这个名字?

为了记住我们使用的是哪个名称,我们将添加特殊名称 HEAD 到一个分支名称,像这样:

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

这意味着我们正在使用提交 C,但这样做是因为 / 通过名称 main。如果我们 git switch devgit checkout dev,我们得到:

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

我们仍在使用提交 C,但现在我们通过 name dev 使用它。如果我们现在 D 提交我们得到:

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

现在有两个最新提交:C是最新的main提交,D是最新的dev ] 犯罪。请注意,通过 C 的提交在两个分支 上都是 ,并且 D 是最新的 dev 提交这一事实不会干扰这一事实C 是最新的 main 提交。

假设我们切换回 main(如果我们愿意,可以在“上方”而不是“下方”绘制 dev):

        D   <-- dev
       /
A--B--C   <-- main (HEAD)

我们已经使用提交 C 返回到 ,因此我们看到来自提交 C 的文件,而不是来自提交 [=62] 的文件=].如果我们现在创建并切换到一个名为 br2 的新分支,我们将得到:

        D   <-- dev
       /
A--B--C   <-- br2 (HEAD), main

我们仍在使用提交 C,但现在我们正在使用 通过名称 br2。如果我们现在 进行新的提交,我们会得到:

        D   <-- dev
       /
A--B--C   <-- main
       \
        E   <-- br2 (HEAD)

这就是 Git 分支的意义所在。 name 找到最新的提交,然后我们/Git 向后工作。 我们找到[=410] 的一组提交=] 当我们向后工作时,是“在”分支上的一组提交,这就是该分支的历史记录。

根据定义,这些名称会找到 最新的 提交。这允许我们通过强制其中一个分支名称也备份一个或两个或三个提交来“倒退时间”。当我们使用 git reset --hard HEAD~1 来“擦除”提交时,我们会这样做:它实际上并没有消失,我们只是假装我们从未通过确保我们无法 find 它与分支名称。例如,如果我们在 br2 上并进行 错误 提交 F:

        D   <-- dev
       /
A--B--C   <-- main
       \
        E--F   <-- br2 (HEAD)

我们可以使用git reset --hard HEAD~1(或者git reset --hard <em>hash-of-E</em>)得到这个:

        D   <-- dev
       /
A--B--C   <-- main
       \
        E   <-- br2 (HEAD)
         \
          F   ??? [abandoned]

然后我们可以进行更正的提交 G:

        D   <-- dev
       /
A--B--C   <-- main
       \
        E--G   <-- br2 (HEAD)
         \
          F

因为没办法找到F,我们再也见不到它了,好像它已经消失了。 (Git 将 可能 ,最终——在 30 天或更多天后——决定我们真的不想要它,并完全放弃它,但提交很难摆脱. 如果你 did 将哈希 ID 保存在某处,例如在纸上或文件中,然后将其提供给 to git show , 你可能会发现提交 F 仍然存在。什么时候甚至是否 Git 真的 确实取消它被故意保留了一点神秘感。)

多个存储库

一个Git库主要由两个数据库组成:

  • 有一个(通常更大)保存提交 objects 和其他支持 objects。 Git 称其为 object 数据库 object store 并且 Git 需要哈希在此数据库中查找 objects 的 ID。

  • 另外,还有第二个(通常小得多)名称数据库——分支名称、标签名称和许多其他名称——Git 使用它来 查找 提交和其他 objects。每个名称只包含一个哈希 ID。

我们可以将一个 Git 数据库连接到另一个数据库。当我们这样做时,两个 Git 软件包使用哈希 ID 来交换 objects。两个存储库将对相同的 object 使用 相同的哈希 ID,因此它们可以通过比较哈希 ID 来判断另一个存储库有哪些 object。然后一个 Git——无论哪个 发送 东西——只发送 object 另一个 需要 ,使用object它已经必须避免发送它不需要的东西。

这样,两个存储库就结束了共享提交。它们实际上具有相同的 objects 和相同的哈希 ID,因此它们彼此共享提交。两者都有所有内容的完整副本。

但是,此名称数据库中的

分支名称 特定于此特定存储库。我们可以让我们的 Git 向其他 Git 展示 它们,而其他 Git 可以获取这些名称和哈希 ID 并用它们做一些事情,但它们是 我们的 分支名称。 Tag names in this database, we try to share: 如果其他一些 Git 存储库也有标签名称,我们尝试使用他们的名字 as-is,并分享我们的用 as-is 标记名称,因此 v1.2 在两个存储库中表示相同的哈希 ID。但是 branch 名称不是这样共享的!每个存储库都有 自己的 .

而不是共享分支名称,然后,当 yu 运行 git fetchgit fetch origin,你告诉你的 Git:调用他们的 Git 软件,让它连接到他们的存储库,并通过他们的分支分支名称找到他们所有最新的提交。然后带来所有的提交。把他们所有的 branch 名字改成我的 remote-tracking 名字。 他们的 main 变成你的origin/main;他们的 dev 变成了你的 origin/dev;等等。这样,无论他们是否已将提交添加到 他们的 分支,你的 分支都不会受到干扰。你会得到他们的任何新提交,你的 remote-tracking 名称会记住他们最新的提交哈希 ID。

但这对 git push 来说并非如此。

"Non-fast-forward" 来自 git push

的错误

当您 运行 git push origingit push origin dev 时,您的 Git 调用他们的 Git 软件和存储库,就像您为git fetch,但这次你选择让 你的 Git 发送 你的 新提交 给他们。而不是让你的Git读取他们的分支名称并找到他们的new-to-提交,你有你的Git 发送哈希 ID 并找到 你的 new-to-他们 提交。你的 Git 然后将这些新的提交 object 交给他们......然后要求或命令他们设置他们的 分支 名称之一。他们没有 remote-tracking 名字给你!你只要让他们直接设置他们的 branch 名称即可。

假设你有:

        D   <-- dev (HEAD)
       /
A--B--C   <-- main

你的仓库中,但是他们他们的中获得了一些提交E ] 像这样的存储库:

A--B--C   <-- main [their main, in their repository]
       \
        E   <-- dev [their dev, in their repository]

你的 Git 发送你的 new-to-他们提交 D 并要求他们设置他们的 dev 以记住提交 D.

如果他们这样做了——他们不会这样做——他们将如何找到他们的提交E?请记住,他们的 Git 将使用 他们的分支名称 来查找最新的提交。如果他们的 dev 移动到定位提交 D,并且 D 不会导致 E——而且不会——他们将“失去”他们 提交 E.

如果发生这种情况,他们会说:不,我不会将我的 dev 设置为记住 D 作为最新提交,因为那会丢失一些其他最新提交。 这在您的 git push 中显示为:

! [rejected]        dev -> dev (non-fast-forward)
error: failed to push some refs to ...
hint: Updates were rejected because a pushed branch tip is behind its remote
hint: counterpart. Check out this branch and integrate the remote changes
hint: (e.g. 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

发生这种情况时,您需要:

  1. 运行 git fetch 获得任何新的提交 他们 缺乏;
  2. 检查您和他们的提交 以及这些提交可能相互之间的关系(parent、child、同级等) : 使用 git log 和各种选项;见 Pretty Git branch graphs
  3. 根据需要返工,例如,使用git rebase;
  4. 既然你已经纠正了问题,请重复你的git push,或者如果你确定你应该告诉他们git push --force-with-lease =581=] 存储库 是的,丢失那些提交!

很多东西要知道。但是,如果您要使用 Git 和分布式存储库,则需要了解它。至少应该模糊熟悉,commits的概念以及它们为您所做的事情应该非常熟悉。

你可以试试这个:

git fetch
git checkout dev
git add .
git commit -m "your commit message here"
git push

git fetch 将从您的远程仓库更新所有现有分支到您的本地。然后,您可以执行 git checkout dev 切换到 dev 分支。然后最后 commitpushdev

然后推送到main,可以做一个pull request,批准,合并到main

显然,

git branch dev
git checkout dev
git pull origin dev
git push origin dev:dev

成功了。