如何创建 git 远程跟踪分支

How to create git Remote-Tracking Branch

They said就是这么简单

You can tell Git to track the newly created remote branch simply by using the -u flag with "git push".

但它对我没有用。

如何创建 git 远程跟踪分支,

Git can now inform you about "unpushed" and "unpulled" commits.

这是我的:

$ git status 
On branch newfeature/v4-json
nothing to commit, working tree clean

与我的预期相比,引用自 above article:

$ git status
# On branch dev
# Your branch and 'origin/dev' have diverged,
# and have 1 and 2 different commits each, respectively.
#
nothing to commit (working directory clean)

即关于 "unpushed" 和 "unpulled" 提交的信息。
即,我想看到与以下内容相同的内容:

$ git status
On branch master
Your branch is ahead of 'origin/master' by 3 commit.
  (use "git push" to publish your local commits)

nothing to commit, working tree clean

然而,从我上面的实际输出中,你可以看到我无法再看到到目前为止我做了多少次提交,尽管我已经做了几次提交.

这是我所做的:

$ git push -u origin newfeature/v4-json
Counting objects: 12, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (11/11), done.
Writing objects: 100% (12/12), 1.87 KiB | 958.00 KiB/s, done.
Total 12 (delta 9), reused 0 (delta 0)
remote: Resolving deltas: 100% (9/9), completed with 9 local objects.
remote: 
remote: Create a pull request for 'newfeature/v4-json' on GitHub by visiting:
remote:      https://github.com/.../pull/new/newfeature/v4-json
remote: 
To github.com:xxx/yyy.git
 * [new branch]      newfeature/v4-json -> newfeature/v4-json
Branch 'newfeature/v4-json' set up to track remote branch 'newfeature/v4-json' from 'origin' by rebasing.

但是我没有 git 设置的来自 'origin' 的远程跟踪分支 'newfeature/v4-json':

A) git remote show origin 根本不显示我的新功能的远程跟踪分支:

$ git remote show origin
* remote origin
  Fetch URL: git@github.com:go-easygen/easygen.git
  Push  URL: git@github.com:go-easygen/easygen.git
  HEAD branch: master
  Remote branch:
    master tracked
  Local branches configured for 'git pull':
    master             rebases onto remote master
    newfeature/v4-json rebases onto remote newfeature/v4-json
  Local refs configured for 'git push':
    master             pushes to master             (up to date)
    newfeature/v4-json pushes to newfeature/v4-json (up to date)

而下面是我想看的,根据http://www.gitguys.com/topics/adding-and-removing-remote-branches

$ git remote show origin
* remote origin
  Fetch URL: /tmp/.../git/rp0
  Push  URL: /tmp/.../git/rp0
  HEAD branch: master
  Remote branches:
    master     tracked
    newfeature tracked
  Local branches configured for 'git pull':
    master     rebases onto remote master
    newfeature rebases onto remote newfeature
  Local refs configured for 'git push':
    master     pushes to master     (up to date)
    newfeature pushes to newfeature (up to date)

注意Remote branches:部分,除了master tracked,还有一个newfeature tracked。根据上述文章,此 newfeature tracked 称为 远程跟踪分支

B) git branch -a:

也不是
$ git branch -a
  master
* newfeature/v4-json
  remotes/origin/HEAD -> origin/master
  remotes/origin/master

那里只有一个 remotes/origin/master 远程跟踪名称,而我期待更多。例如。 (无关紧要,只是为了展示更多远程跟踪名称的情况),

$ git branch -a
* master
  remotes/origin/HEAD
  remotes/origin/master
  remotes/origin/v1.0-stable
  remotes/origin/experimental

C) 也不是 git branch -vv:

$ git branch -vv
  master             75369c3 [origin/master] - [*] allow ...
* newfeature/v4-json 8c98d9c - [*] update ...

虽然我期待看到,

$ git branch -vv
  master             75369c3 [origin/master] - [*] allow ...
* newfeature/v4-json 8c98d9c [origin/newfeature/v4-json] - [*] update ...

此外,

git pull 也没有从 remote 更新我的 local 分支:

$ git pull
From github.com:xxx/yyy
 * branch            newfeature/v4-json -> FETCH_HEAD
Already up to date.
Current branch newfeature/v4-json is up to date.

$ git pull
From github.com:xxx/yyy
 * branch            newfeature/v4-json -> FETCH_HEAD
Already up to date.
Current branch newfeature/v4-json is up to date.

$ git pull
From github.com:xxx/yyy
 * branch            newfeature/v4-json -> FETCH_HEAD
Already up to date.
Current branch newfeature/v4-json is up to date.

也就是说,无论我拉多少次,我都不会得到与

相同的输出
$ git pull
Already up to date.
Current branch master is up to date.

以上都是正常。我之前用MS VS 创建过Remote-Tracking Branch 很多次,结果和我预想的一样,不是上面的。但是,我不喜欢黑魔法,所以我想知道如何用普通的 git 做同样的事情。

那么创建 git 远程跟踪分支的正确方法是什么?

编辑地址已更新(git branch -agit branch -vv)输出:是的, 丢失。目前还不完全清楚哪里出了问题,但我有一个猜测。这部分git push -u输出:

 * [new branch]      newfeature/v4-json -> newfeature/v4-json
Branch 'newfeature/v4-json' set up to track remote branch 'newfeature/v4-json' from 'origin' by rebasing.

显示您的 Git 将您的 origin/newfeature/v4-json(分为两部分)设置为 newfeature/v4-json 的上游。但是您的 git branch -agit branch -vv 输出显示 origin/newfeature/v4-json 不存在。

我可以通过制作 single-branch 克隆来重现此行为的关键要素。使用 git clone --depth=<em>number</em>git clone --single-branch 将产生这样的克隆。这样做的副作用是你的 Git 永远不会为任何分支 创建任何 remote-tracking 名称,除了 你告诉 Git 你的那个分支关心的。如果此 问题,解决方法是将克隆转换为普通 (multi-branch) 克隆。 (如果您使用 --depth 创建了 single-branch 方面,那么 unshallow 克隆也可能是明智的。)

查看您的 origin 克隆是否设置为 single-branch:

$ git config --get-all remote.origin.fetch

在正常的克隆中,这将打印:

+refs/heads/*:refs/remotes/origin/*

在选择了分支 master 的 single-branch 克隆中,这将打印:

+refs/heads/master:refs/remotes/origin/master

它告诉你的 Git:master 创建一个 remote-tracking 名称,而不是前者的 创建 remote-tracking * 的名称,即所有分支.

un-do origin 的 single-branch-ness 克隆:

$ git config remote.origin.fetch '+refs/heads/*:refs/remotes/origin/*'

(或直接编辑 .git/config,例如 git config --edit,这是我的首选方法)。另见 How do I "undo" a --single-branch clone?

要将浅克隆转换为完整 (non-shallow) 克隆,只需 运行:

$ git fetch --unshallow

请注意,此操作独立于 single-branch-ness,尽管默认情况下 git clone 将它们联系在一起(您可以在 git clone 时使用 git 克隆 --depth=<em>number</em> --no-single-branch)。 2.15之前的Git版本没有command-line测试shallow-ness;在 2.15 或更高版本中,使用:

git rev-parse --is-shallow-repository

但在此之前你必须测试文件是否存在 .git/shallow:

if [ -f $(git rev-parse --git-dir)/shallow ]; then
    echo true
else
    echo false
fi

模拟git rev-parse --is-shallow-repository.

顺便说一句,您想要看到的输出有问题。您说您希望将 newfeature 视为远程上的一个分支——但是 不可能发生 因为名称 newfeature/v4-json 需要存在,这排除了newfeature 存在的能力。

(原始答案在下方。)


$ git push -u origin newfeature/v4-json

这完全符合您的要求。在您显示的其余输出中,一切都很好。所以不清楚你认为是错误的;实际上没有错。我将处理您显示的另一条消息:

# Your branch and 'origin/dev' have diverged,
# and have 1 and 2 different commits each, respectively.

以下。

这一切意味着什么? (长)

复习一下 Git 的工作原理和一些 Git 相当奇特的术语可能会有所帮助。特别是,您使用的短语 remote-tracking branch 在我看来是一个 糟糕的 术语,具有误导性。它 一个 Git 术语,所以我们应该理解人们使用它时的意思,但它是一个 不好的 术语,这意味着人们 滥用 它,如果您对某些人的用法感到困惑,可能值得退后一步并再次考虑这些事情。

首先,让我们注意 Git 实际上就是 提交 。提交是 Git 的 raison d'être;没有提交,我们根本不会使用 Git。那么让我们看看什么是提交。

每个提交 包含 个文件,但它 不只是 一组文件。这是快照,所有 文件的快照,截至您拍摄快照时,1 但它也有一些 元数据: 关于 存储数据的信息。最明显的是您在 git log 输出中看到的内容:您的姓名和电子邮件地址,以及计算机对您进行提交时的日期和时间的想法,以及 原因 您为进行提交而保存,即您的日志消息。这些都是为了你——或其他人——在未来使用:某一天,也许明天,也许几个月或几年后,你可能会回顾你刚刚做出的这个承诺,问问自己:为什么我到底做了什么那个答案应该在你的日志消息中。

因为提交存储文件——作为快照、及时冻结、不可变且永远存在(或者只要提交本身存在)——它们非常适合归档。在未来的任何时候,您都可以回到过去并完全查看您当时保存的内容。你无法改变它:它是过去的,固定的,及时冻结的。甚至 Git 也无法改变它,我们稍后会看到。

为了找到一个提交,Git需要一个名字。这些名称是 而不是 分支名称!或者,更准确地说,你可以开始唱一个分支名称,但这不是 Git 需要的名称。任何提交的真实名称都是它的 哈希 ID。每个提交的哈希ID看似随机,但实际上,它是提交全部内容的加密校验和,对数据的每一位都非常敏感in 即提交:所有冻结的快照,还有你的名字和 time-stamp 以及你的日志消息。这就是 为什么 你或任何人不能更改提交:更改任何内容都会更改哈希 ID,然后你将拥有一个新的和不同的提交。在 new 提交之前,没有人知道哈希 ID 是什么。届时,它会获得一个唯一的 ID。没有人会将该 ID 用于任何其他提交!并且没有人可以更改任何内容 in 提交:Git 将知道您是否尝试,因为 ID 将不再匹配。2

这个特殊的拼图游戏还有最后一两个关键部分。第一个是在每个 new 提交中,Git 存储 previous 提交的哈希 ID——真实名称,作为一部分该元数据。也就是说,Git 不仅会保存您的姓名和时间等信息,还会保存您使用 进行此新提交的提交的原始哈希 ID。 Git 将此保存的哈希 ID 称为提交的 parent。这意味着每个提交 指向 其父提交,在 backwards-looking 链中。

例如,假设我们在存储库中只有两个提交 ABA 是第一个提交,所以它有意 没有 父级——这是一个特例。但是 B 是由 A 生成的,所以 B 指向 A:

A <-B

如果您提取提交 B,做一些工作,然后进行新提交 C,新提交会自动指向 B:

A <-B <-C

this的意思是Git只需要知道last的apparently-random哈希ID犯罪。在本例中是提交 C。如果它的实际哈希 ID 是 cba9876... 或其他什么,Git 可以使用它来找到 Ccontents。这些内容包括提交的实际哈希 ID B。 Git 然后可以使用它来查找 B,其内容包括提交 A 的实际哈希 ID。 Git 可以用它来找到 A,而 A 没有父级,所以现在,最后,Git 可以停止向后工作了。

这个从 分支提示 提交向后工作的过程,如 C,由 分支名称 标识,是至关重要的在 Git。这就是历史 存在的方式 。 Git 存储库 中的历史是 提交,由这些 backwards-pointing 箭头连接。你从头开始走,一次一个提交,通过历史,看看你可以沿着父箭头到达哪里。

这是最后一个 jigsaw-puzzle 进入图片的地方,当 分支名称 和其他类似名称出现时。让我们暂停一下,完成这里的脚注,然后深入研究分支名称和 graph-drawing.


1Git 实际上是从 index 做快照,但我们不会在这里深入这些细节,除了说被快照的东西——在时间上永远冻结,因为那个提交——是当时 index 中的任何东西,这至少可能与你看到的不同在你工作的work-tree

2Git 实际上会检查这个,只要它看起来方便或合适。这会自动检测 Git 存储库的意外损坏,例如当您尝试在 Dropbox 中存储时发生的情况——Dropbox 有时会在您(和 Git 的)背后修改文件,并且 Git抓住它。不幸的是,很少有修复损坏的存储库的好方法——相反,Git 倾向于依赖 Git 存储库在各处被复制的想法。您可能在其他地方有一份很好的副本,所以您只需将这个完全扔掉。


分支名称查找提交哈希 ID

任何现有的存储库——好吧,除了一个完全空的、全新的、没有没有提交的新存储库之外的任何存储库——都有一些提交集。这些提交形成了我们刚刚看到的 backwards-looking 链,例如:

A <-B <-C

我们——和Git——需要某种方式来记录此链中最后次提交的哈希ID。

Git 实现这一点的方法是 Git 调用 referencesrefs。 refs有很多种形式,但三大是:

  • 分支名称,例如 master
  • Remote-tracking 个名字,例如 origin/master。 (Git 调用这些 remote-tracking 分支名称 remote-tracking 分支 ,我认为这是一个坏名字; 我我已经改用 remote-tracking 名称,我认为这更难出错。)
  • 标签名称,例如 v1.3

它们实际上都是由相同的底层技术实现的,但我们在这里将它们视为单独的名称形式。 Branch名字有一个特殊的属性; 所有 其他名称缺少此 属性.

其中一个名称的含义非常简单:它只是一个 Git 对象的实际原始哈希 ID,通常是一个提交。3 所以一个分支名称如 master 指向 分支中的 最后一个 提交——提交 C 在此图中:

A--B--C   <-- master

请注意,将提交彼此连接起来的箭头从子项中出来并指向(不可变的)父项,为我们提供了这种向后遍历的方法。我们不必费心去画它们。branch 名字出来的箭头,然而,change.

当我们添加 new 提交到 master,Git 自动更新 名称 master 来保存新提交的哈希 ID。因此,如果我们现在创建一个新提交,新提交 D 将指向 C:

A--B--C   <-- master
       \
        D

但是Git会立即调整master指向D而不是C:

A--B--C--D   <-- master

由于D指向C,我们仍然可以找到所有的提交:我们从最后开始,像往常一样向后工作。 C 现在是此过程中的 第二个 提交,而不是第一个。


3分支名称必须保存提交对象哈希ID,而标签名称更灵活。我们这里不需要关心这个。因为 remote-tracking 名称的值是从 分支名称复制的,所以 remote-tracking 名称也只包含提交哈希 ID。


分支名称对于每个存储库都是私有的,但存储库会相互通信

Git 是一个分布式 版本控制系统。这意味着每个 Git 存储库都是一种 self-contained 岛,它需要的一切都在该存储库本地。如果有多个分支有很多提交,它们在那个存储库中是 all:

A--B--C--D--G--H   <-- master
          \
           E--F   <-- dev

为了使 Git 真正有用,我们经常使用 Git 与其他 Git 用户交换工作。为此,我们交换 提交 。由于加密校验和技巧,它们的哈希 ID 在 all Git 中是通用的。给定快照和元数据,每个 Git 都会计算相同的哈希 ID。因此,如果我的存储库像这样从 AH 提交——请记住,这些单个大写字母代表独特的、又大又丑的哈希 ID——然后我连接到 你的 存储库和 有提交 H,你的存储库也必须有和我一样的提交。

如果您没有提交H,我有一个您没有的提交。如果你有一些提交IJ有一个没有的提交。无论哪种方式,我们的 Git 都可以交换哈希 ID 来查看谁拥有什么。发送提交的人将发送提交,接收提交的人将接收提交,发送方将向接收方提供任何 new 所需的提交。

假设您正在接受我的新提交。我有新的提交 IJ,我的新提交 J 有一个 name 记住它的哈希 ID。在 my 存储库中,我有这个:

A--B--C--D--G--H   <-- master
          \
           E
            \
             I--J   <-- dev

出于某种原因,我没有提交F,而您在dev上提交了F。相反,在(共享)提交 E.

之后,我在 dev 上提交了 I-J

这就是 remote-tracking 名字的来源

你的 Git 接受了我的提交 IJ。我的提交 I 有父项 E。所以 你的 存储库现在有这个:

A--B--C--D--G--H   <-- master
          \
           E--F   <-- dev
            \
             I--J   <-- ???

名称 你的 Git 存储库会使用什么来记住我的提交 I?最好不要使用 dev:如果你的 Git 让你的 dev 指向提交 I,你将如何再次找到提交 F?请记住,它有一个 apparently-random 哈希 ID。你永远无法猜到它。

所以,你的 Git 所做的是使用 remote-tracking names 来记住 my 分支。您的 Git 这样做:

A--B--C--D--G--H   <-- master, origin/master
          \
           E--F   <-- dev
            \
             I--J   <-- origin/dev

(假设我的 master 指向提交 H)。

您的存储库中的名称origin/masterorigin/dev是(您的)remote-tracking名称,记住我的 master 和我的 dev.4 此外,假设您现在查询 Git,要求它比较可访问的提交集来自 dev 与来自 origin/dev 的那些,在 Git 使用的普通 walk-backwards 方法中。

dev,您将访问的提交是 F,然后是 E,然后是 D,依此类推回到 A。从 origin/dev 开始,您将访问的提交是 J,然后是 I,然后是 E,然后是 D,依此类推回到 A. 哪些提交对于哪个步行是唯一的?您从 dev 达到多少次您无法从 origin/dev 达到,反之亦然?

数一数,然后与您的 Git 告诉您的进行比较:

# Your branch and 'origin/dev' have diverged,
# and have 1 and 2 different commits each, respectively.

实际上我们的拼图游戏中还缺少另一块,我们将在下面讨论 git push 的最后一节中简单描述一下。


4Git 有时称此为 tracking 而不是 remembering,但是这是另一个地方 Git 严重滥用一个词。我在短语 remote-tracking 中使用了它,但至少在这里它是带连字符的,并将该词用作形容词来修饰 remote


git push 不同于 git fetch

在上面的过程中,您的 Git 从 Git 在 [=48= 处找到的分支名称创建了 remote-tracking 名称 ],特定于 git fetch。当你让你的 Git 在 origin 调用 Git 并将 他们的 提交给 .

当然,您可以让您的 Git 在 origin 发送 提交时调用他们的 Git。这就是 git push 操作,它非常相似。您的 Git 告诉他们 Git 您拥有的提交,而他们没有。让我们画一些。我们将从这里开始:

A--B--C--D--G--H   <-- master, origin/master
          \
           E--F   <-- dev
            \
             I--J   <-- origin/dev

现在我们将 运行 git checkout mastergit checkout -b newfeature/v4-json,或者更简单的:

git checkout -b newfeature/v4-json master

我们现在有:

A--B--C--D--G--H   <-- master, origin/master, newfeature/v4-json (HEAD)
          \
           E--F   <-- dev
            \
             I--J   <-- origin/dev

我们已将特殊名称 HEAD 附加到 newfeature/v4-json 以记住 哪个 分支名称在我们添加新提交时得到更新。

现在我们将创建一个新提交。它可能不止一个,甚至 none,但我们只创建一个来说明。新的提交有一些丑陋的大哈希 ID,但我们在这里称它为 K

                 K   <-- newfeature/v4-json (HEAD)
                /
A--B--C--D--G--H   <-- master, origin/master
          \
           E--F   <-- dev
            \
             I--J   <-- origin/dev

现在我们将让您的 Git 在 origin 呼叫 Git,使用:

git push -u origin newfeature/v4-json

你的 Git 拨通他们的 Git 并宣布你有提交 KH5 他们不没有 K 但他们确实有 H 所以他们让你的 Git 发送提交 K 及其快照和元数据。你的 Git 可以告诉他们,因为他们有 H 他们也有 GD 以及之前的所有内容,所以你只需要发送他们 K 及其内容。

然后,最后,你的Git问他们:现在,如果可以的话,请将你的名字newfeature/v4-json设置为指向提交K . 请注意,您没有设置 xpt/newfeature/v4-json 或类似的设置。你让他们设置了他们的分支!他们实际上没有一个newfeature/v4-json,所以他们设置一个是完全可以的.所以他们这样做!他们现在在 他们的 存储库中有一个 newfeature/v4-json,指向提交 K.

您的Git现在创建您的remote-tracking名称origin/newfeature/v4-json,指向提交K,记住 他们的 newfeature/v4-json,指向提交 K.6 但这仅仅意味着你的图中多了一个 name,像这样:

                 K   <-- newfeature/v4-json (HEAD), origin/newfeature/v4-json
                /
A--B--C--D--G--H   <-- master, origin/master
          \
           E--F   <-- dev
            \
             I--J   <-- origin/dev

由于 -u 选项,您的 Git 也立即 运行s:

git branch --set-upstream-to=origin/newfeature/v4-json newfeature/v4-json

这会为您的分支 newfeature/v4-json 设置 upstream 设置。您的每个分支都可以有 one (1) 上游设置,并且以这种方式使用它是非常典型的。有关更多信息,请参阅 Why do I need to do `--set-upstream` all the time?


5您的 Git 可以 告诉他们 F,但只有当您说git push origin dev 这里。使用 git push origin newfeature/v4-json,有或没有 -u,你告诉你的 Git:告诉他们有关提交的信息 KHG, D, C, B, and/or A 根据需要。 您的其他未共享提交有意保持私有。

6记住,由于哈希 ID 的魔力,提交 K 每个 Git 无处不在Every Git 要么有 K,通过其哈希 ID,然后是 that 提交;或者根本没有 K,所以没关系。

(这不一定是 100% 保证。假设 K 的哈希 ID 实际上是 b5101f929789889c2e536d915698f58d5c5c6b7a。这是 Git 存储库中提交的哈希 ID Git 本身。如果您从未将 您的 Git 存储库连接到 Git 的 Git 存储库,那么您和他们有不同的存储库是可以的commits with the same hash ID. But if you do ever connect your Git repository to a Git repository for Git, some not-so-great 事情发生了。简短的版本是你只是没有得到 Git 的提交,他们只是不要得到你的:这两个存储库此时根本无法合并。对于您和维护 Git 的人来说,这可能完全没问题。但另见 How does the newly found SHA-1 collision affect Git?)