如何通过 GitHub 重命名本地分支并删除重复的远程分支后拉取



一切都与GitHub上的远程存储库同步。

然后我在Sublime Merge中重命名了一个本地分支(右键单击→重命名分支)。

然后我推 - 这没有同步远程存储库,相反,我现在有两个相同的分支,一个是旧分支,一个是新名称。所以我通过GitHub删除了旧分支,因为我不知道如何从Sublime Merge中做到这一点。

现在,当我尝试引入Sublime Merge时,它说:

Ihre Konfiguration gibt an, den Merge mit Referenz 'refs/heads/<old-branch-name>' des Remote-Repositories durchzuführen, aber diese Referenz wurde nicht angefordert.

通过谷歌翻译进行英文翻译:

Your configuration says to merge with reference 'refs/heads/<old-branch-name>' of the remote repository, but this reference was not requested.

我该怎么做才能解决这个问题?我想我必须从我的配置中删除旧的分支条目,但是如何以及在哪里?

简短版本:使用git branch --set-upstream-to修复上游设置,例如:

git branch --set-upstream-to=origin/bract bract

(请注意,在这种格式中,名称的origin/版本在等号之后,没有空格;分支名称本身是可选的,如果你省略它,Git 将假定"当前分支名称")。

更长:如何理解这里发生的事情

在 Git 中,分支名称实际上并不重要。 这意味着您可以随时更改它们。 但是这里有一些问题,因为每个 Git存储库都有自己的(单独的)分支名称。 更改一个存储库中的名称不会影响另一个存储库中的名称。

除了分支名称,Git 还有其他类型的名称,例如标签名称(通常用v书写,例如v1.7v2.12.1)和远程跟踪名称1您可能至少熟悉,也许非常熟悉origin/mainorigin/master等名称,这些是这些远程跟踪名称的示例。

每当您运行git fetch(包括git pull运行的git fetch)时,您都在指示您的 Git 软件使用存储库中的数据,在某个 URL 上调用其他一些 Git 软件。 URL 本身通常是 Git 在运行git clone创建 Git存储库时保存的 URL 本身。 Git 将以origin的名义保存该 URL。 这个名字——origin——实际上是可以改变的,但人们通常不会改变origin太多,如果有的话。

这个简短的名称,通常在这里origin,只是帮助您跟踪互联网上某个地方"那里"有另一个 Git 存储库这一事实的一种方式。 短名称既存储 URL,以便您以后可以运行git fetch而无需自己记住它,并构成了远程跟踪名称的基础

因此,当您运行git fetch时(包括git pull runsgit fetch,以及最初创建存储库时git clone运行的git fetch),您的 Git 软件会调用其他 Git 软件,该软件从某些现有的 Git 存储库读取。 该 Git存储库有自己的分支和标记名称。 它甚至可能具有远程跟踪和/或其他类型的名称。 你的 Git 软件可以看到所有这些名称(或者至少是他们愿意向你展示的所有名称)以及随之而来的 Git 哈希 ID。 实际上,您可以使用一个命令来转储所有这些内容:

git ls-remote origin

将在origin调用另一个 Git,让它列出它会让你看到的名称,然后将它们全部打印出来,而实际上没有对它们做任何有用的事情。

但是当你运行git fetch时,Git 使用相同的机制来查看它们的所有分支和标签名称,并使用它来从它们获取提交(如果已启用,则获取标签,就像默认情况下一样)。 然后,一旦您的 Git 将他们提交集中的任何提交添加到您的存储库中,您的 Git 通常会更新您的所有远程跟踪名称。 这涉及创建新名称(如果它们显示您尚未拥有的名称作为远程跟踪名称)或更新现有名称(如果它们显示您确实拥有的名称)。

所有这些意味着,如果您克隆一个存储库,该存储库在克隆时具有名为mainbr1br2的分支,您将在自己的克隆中获得:

origin/main        (corresponds to their main)
origin/br1         (corresponds to their br1)
origin/br2         (corresponds to their br2)

在这一点上,你没有分支,这是在 Git 中工作的一种非常悲惨的方式(尽管可以做到)。 因此,git clone现在根据您提供给git clone-b参数,在您的存储库中创建这三个分支名称之一。 如果您没有给出-b论点,并且大多数人大多没有,您将获得他们的软件推荐的名称,通常是mainmaster阿拉伯数字

但是现在假设您使用git push删除托管站点上的名称br1并将其替换为名称anch. 如果现在运行git ls-remote origin,您将看到三个名称mainbr2anch。 这也是您的 Git 软件将看到的内容,因此git fetch origin创建一个anch,将其拼写为origin/anch。 这是您为其anch的新远程跟踪名称。

br1会怎样?无。它仍然在那里,作为一个陈旧的剩菜。 您的 Git 软件会为他们现在拥有的分支名称创建或更新适当的远程跟踪名称,但不会清除 Git 软件之前创建的名称的无效名称,这些名称不再具有任何明显的用途。


1Git 现在相当一致地称这些为"远程跟踪分支名称",但在我看来,这里的分支一词只是把事情搞得乱七八糟。 这些名称是"远程跟踪",因此这部分有意义的。 他们跟踪的是其他一些仓库的分支名称,因此是名称的其余部分——但由于它们在您的仓库中,并且实际上不是分支名称,我认为完全省略分支这个词更有意义。它们是名称,它们跟踪(如"跟随")远程的分支名称,但它们根本不是分支名称。

2要更改 GitHub 上的建议,请使用 GitHub 的 Web 界面设置"默认分支"。 要在Bitbucket上更改它,请使用Bitbucket的Web界面。 要在谷歌托管上更改它...好吧,他们忘了给你改变它的方法! 哎呀。 但这是一般的想法:你不能从 Git 中做到这一点,你必须使用一些附加组件。 这可能是 Git 中的一个错误,但考虑到它已经这样了近二十年了,它可能会保持这种状态一段时间。


分支名称的特殊属性

在 Git 中,所有不同种类的名称都只包含一 (1) 个哈希 ID,或者更正式地包含对象 ID,即一大串丑陋的字母和数字,如c3ff4cec66ec004d884507f5296ca2a323adbbc5。 这实际上只是一个非常大的十六进制编码,用于在 Git 的"对象数据库"中定位对象,该数据库是构成 Git 存储库核心的两个数据库之一(另一个数据库是名称到 ID 数据库)。 正是这两个数据库的组合使 Git 存储库工作;这两个数据库,加上一组最少的辅助文件,是任何 Git 存储库的必要基本部分。

分支名称在Git 中特别神奇的原因分为三个部分:

  1. 它被迫仅保存提交哈希 ID。不允许使用其他类型的对象。 (还有三种其他类型的对象,例如,标记名称可以容纳这四种对象中的任何一种。

  2. 您可以使用git checkout或(自 Git 2.23 起)git switch"上"分支。

  3. 分支名称支持其他名称缺少的特定设置。

实际上,这里最关键的是第 2 项。 "在"分支上是 Git 跟踪最新提交的方式。 每当"在"某个分支上进行提交时,Git都会更新存储的哈希 ID,以使用新提交的新、唯一、明显随机的哈希 ID。 Git需要提交的哈希 ID 才能找到该提交。 分支名称存储最新提交的哈希 ID。 提交本身存储早期提交的哈希 ID,因此通过查找最新的提交,Git 可以找到较早的提交。

鉴于分支名称将在您进行提交时自动更新,这使得分支名称可以查找所有提交。 但由于这不是本答案/文章的目的,我们将在这里停止,不再进一步解释。 相反,我们将继续讨论第 3 项,即设置。

任何一个特定分支名称的设置包括:

  • 是否希望此分支名称与git pull"合并"或"变基",如果您选择使用git pull;以及
  • 存储库中
  • 另一个分支的名称,或在存储库中查找远程跟踪名称所需的信息。3

第一项严格适用于git pull。 第二项也被git pull使用,git rebasegit merge也使用。

git pull命令意味着:

  1. 运行git fetch,然后
  2. 运行我选择的第二个命令,通常git merge,但有时git rebase

因为它是背靠背运行的,所以它实际上可以比每个命令单独知道的更多关于正在发生的事情(这意味着你会收到一个模糊的信息性错误消息,正如我们将看到的)。


3这种有趣的措辞的原因是,由于历史原因,这种"上游"设置的实际编码相当奇特。 要引用存储库中的分支,branch.B1.remote设置为文本字符串.(句点),branch.B1.merge设置为refs/heads/B2,其中B1B2是存储库中显示的两个分支名称。 但是,要引用存储库中的远程跟踪名称,请将这两个名称设置为Rrefs/heads/B2其中R是远程名称,B2远程上显示的分支名称。 然后通过默认获取设置中的 refspec(s) 确定的映射运行这些映射,出于空间原因,我不打算讨论。 这是非常复杂的,即使正常的效果只是,例如,分支mainorigin/main作为其上游。

<小时 />

git pull操作

git fetch命令需要一条或两条信息,git mergegit rebase操作也需要一条信息。 特别是,git fetch需要知道从哪个遥控器git fetch。 我们是在做git fetch origin,还是git fetch rumpelstiltskingit fetch bruce或......? 当然,无论如何,它几乎总是origin的(这是git fetch内置的回退默认值),但它想知道。

第二个命令,无论是合并还是变基,都需要至少知道一件事。 它真正需要的是一个哈希ID。 它可以一个名字:

git merge origin/br2

或:

git rebase origin/br2

例如,两者都可以正常工作,当然前提是您有一个origin/br2,但在这两种情况下,Git 都会将该名称转换为原始哈希 ID 以完成其工作。 然后 Git可以使用该名称,例如,git merge将从该名称生成默认的合并消息。 但是,如果git pull正在运行git mergegit pull会生成自己的合并消息并覆盖此默认值。

在任何情况下,这两个信息位都被编码到分支的上游设置中。 名为B的分支的上游通常origin/B。 也就是说,如果你在你的分支main,你根据originmain(你称之为origin/main)来做,你打算你的新提交去他们的main等等。

这一切都很好,当您根据其分支br1创建分支br1时,您称之为origin/br1,您将获得上游设置为origin/br1的分支br1。 如果您运行以下内容,您将看到以下内容:

git branch -vv

(如果您喜欢长拼写,则git branch --verbose --verbose)。 但是,如果您随后使用git push将名称更改为另一个存储库中的名称,从br1更改为anch,然后运行:

git fetch

你的 Git 会带来一个新名字,anch,并将其变成origin/anch. 如果您提前考虑,您还将本地br1重命名为anch

git switch br1
git branch -m anch

(就在 GitHub 或任何地方重命名分支之前或之后)。 但是:你的分支,现在称为anch,仍然说它的上游origin/b1. 您已经在本地重命名了b1,并且(无论是使用 Web 界面还是git push)您都在托管站点上重命名了b1,但存储库中的上游设置只是一个特殊格式的字符串,并且没有更改

运行:

git fetch --prune origin

告诉你的 Git:查找origin,使用它来获取 URL,调用其他 Git 软件,检查分支名称集,并清除我拥有的任何旧的过时名称。 那会扔掉你陈旧的origin/b1. 但是您的anch分支仍然将旧origin/br1作为其上游设置。 所以现在你需要:

git branch --set-upstream-to=origin/anch

当前分支的上游(您现在称为anch)设置为您的origin/anch,这就是您所说的anch

如果没有这个,git pull认为它应该git fetch origin运行,然后使用对origin/br1所做的任何更新。 由于没有进行任何更新,git pull抱怨道:

Your configuration says to merge with reference
'refs/heads/br1' of the remote repository, but this
reference was not requested.

(尽管实际上英文版本与德语翻译的反向翻译略有不同)。

如果您在不提供其他参数的情况下运行git mergegit rebase,您会收到类似(但有些不同)的投诉,例如:

fatal: No remote-tracking branch for refs/heads/br1 from origin

请注意,这需要首先清除"过时"名称才能获得错误。 如果我们不先跑git fetch --prune,就会有一个"陈旧"的origin/br1。 变基或合并命令将使用存储在该过时名称中的哈希 ID! 由此产生的变基或合并可能根本不会做任何事情,但总的来说,这不是一个好主意。

git fetch的一些轻微缺陷

理想情况下,如果git fetch总是--prune就好了。 你可以让它这样做:

git config --global fetch.prune true

这使得git fetch,包括由git pull运行的那个,只要有可能,就会自动进行修剪操作。 这里有一些小错误,尽管它们通常非常安全:如果你遇到一个奇怪的角落情况,只需再次运行git fetch来解决问题,省略任何导致你遇到小错误的额外参数。

这里我还必须提到另外两件事:

  1. 非常旧的 Git(1.8.4 之前)版本通常无法更新某些远程跟踪名称。
  2. 有一些方法可以运行git fetch,使其无法更新大多数远程跟踪名称。

特别是,如果您运行:

git fetch origin main

你是在告诉你自己的 Git,无论其他 Git 为我们溢出的分支名称列表是什么,我们都应该只更新我们的origin/main。 (这里的斜杠是隐含的;这可以追溯到脚注3和其中提到的歇斯底里的葡萄干。 当你使用这种形式的git fetch时,fetch 无法修剪(即使fetch.prune设置为true也不会这样做)。 如果你的 Git 版本真的很旧,它甚至不会更新origin/main:它只会在.git/FETCH_HEAD中留下足够的信息供这个旧版本的 Git 完成git pull操作。

请注意,当您像这样运行git pull时;

git pull

这运行git fetch,而不是git fetch origin main,所以这两种奇怪的情况不会发生。 但是当你运行时:

git pull origin main

git pull命令将这整组参数传递给git fetch,导致git fetch origin main,因此在古代 Git 中,这两种特殊情况(没有修剪和没有远程跟踪更新)确实会发生,而在现代 Git 中,一种情况(没有修剪)会发生。

事实上,它是如此复杂,我需要提到这一点,是对 Git 的一种控诉。 我发现 Git 非常有用,而且大部分都很好,但它确实有很多意义 3 的实例。

我做了什么:

# Read eftshift0´s and torek´s comments
# Duckduckify "upstream" knowledge, find shorturl.at/tEXY3.
git help                           # too broad
git branch help                    # better
git branch --list                  # OK, renamed branch is active, but too terse
git branch --list --all            # too terse
git branch --list --all --verbose  # too terse
# make console full screen
git branch --list --all -vv        # or `--verbose --verbose`
# OK, better
# lists deleted remote
# blue shows deleted remote
# thats wrong
git fetch --prune                  # eftshift0´s comment
git branch --list --all -vv        # SUCCESS, deleted remote branch not listed
git branch --set-upstream-to=origin/new-correct-remote
# torek´s comment
git branch --list --all -vv        # SUCCESS, correct blue remote branch

添加到~/.bash_aliases以供将来参考:

gitlistbranches() {
git branch --list --all -vv
}
gitsetremote() {
git fetch --prune
git branch "--set-upstream-to=origin/$1"
}

最新更新