编辑以更新地址(git branch -a 和 git 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 -a 和git branch -vv 输出显示origin/newfeature/v4-json 不存在。
我可以通过制作 单分支 克隆来重现此行为的关键元素。使用git clone --depth=<em>number</em> 或git clone --single-branch 将产生这样的克隆。这样做的副作用是你的 Git 永远不会为任何分支创建任何远程跟踪名称除了你告诉 Git 你关心的那个分支。如果这是问题,解决方法是将克隆转换为普通(多分支)克隆。 (如果您使用--depth 创建单分支方面,unshallow 克隆可能也是明智之举。)
查看origin 的克隆是否设置为单分支:
$ git config --get-all remote.origin.fetch
在正常的克隆中,这将打印:
+refs/heads/*:refs/remotes/origin/*
在选择了分支master 的单分支克隆中,这将打印:
+refs/heads/master:refs/remotes/origin/master
它告诉你的 Git:为master 创建一个远程跟踪名称,而不是前者的为* 创建一个远程跟踪名称,即所有分支 .
要取消 origin 克隆的单分支:
$ git config remote.origin.fetch '+refs/heads/*:refs/remotes/origin/*'
(或直接编辑.git/config,例如git config --edit,这是我的首选方法)。另见How do I "undo" a --single-branch clone?
要将浅克隆转换为完整(非浅)克隆,只需运行:
$ git fetch --unshallow
请注意,此操作独立于单分支,尽管git clone 默认将它们联系在一起(您可以在git clone 时间用git clone --depth=<em>number</em> --no-single-branch 覆盖它)。在 2.15 之前的 Git 版本中,没有针对浅层的命令行测试;在 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 确实是关于 commits。提交是 Git 的 raison d'être;没有提交,我们根本不会使用 Git。那么让我们看看什么是提交。
每个提交包含个文件,但它不仅仅是一组文件。它是您拍摄快照时所有文件的快照,1但它也有一些元数据:信息关于存储的数据。最明显的是您在git log 输出中看到的内容:您的姓名和电子邮件地址,计算机对您提交提交的日期和时间的看法,以及您保存的原因用于提交,即您的日志消息。这些都是为你或其他人在未来使用的:有一天,也许明天,也许几个月或几年后,你可能会回顾你刚刚做出的这个承诺,并问自己:到底为什么我做了那个吗?答案应该在您的日志消息中。
因为提交将文件存储为快照、及时冻结、不可变并且永远存在(或只要提交本身存在)——它们非常适合存档。在未来的任何时候,您都可以回到过去并确切地查看您当时保存的内容。你无法改变它:它是过去的,固定的,被时间冻结了。甚至 Git 也无法改变它,我们稍后会看到。
为了找到一个提交,Git 需要一个名称。这些名称不是分支名称!或者,更准确地说,您可以开始使用分支名称,但这不是 Git 需要的名称。任何提交的真实名称都是它的 hash ID。每个提交的哈希 ID 似乎是随机的,但实际上,它是提交的全部内容的加密校验和,对提交的每一位数据 in 非常敏感:所有冻结的快照,还有您的姓名和时间戳以及您的日志消息。这就是为什么您或任何人都无法更改提交:更改任何内容都会更改哈希 ID,然后您将拥有一个新的和不同的提交。在提交之前,没有人知道 new 提交的哈希 ID 是什么。那时,它会获得一个唯一的 ID。没有人会将该 ID 用于任何其他提交!并且没有人可以更改提交中的任何内容:如果您尝试,Git 会知道,因为 ID 将不再匹配。2
这个特殊的拼图游戏有最后一两个关键部分。首先是在每个 new 提交中,Git 将 previous 提交的哈希 ID(真实名称)存储为元数据的一部分。也就是说,Git 不仅会保存您的姓名和时间等信息,还会保存您用于 进行此新提交的提交的原始哈希 ID。 Git 将此保存的哈希 ID 称为提交的 parent。这意味着每个提交指向它的父提交,在一个向后看的链中。
例如,假设我们在存储库中只有两个提交 A 和 B。 A 是第一次提交,所以它故意有 no 父级——这是一个特例。但是B是由A组成的,所以B又指向A:
A <-B
如果你提取提交B,做一些工作,并做出一个新的提交C,新的提交会自动指向B:
A <-B <-C
this 的意思是 Git 只需要知道 last 提交的明显随机哈希 ID。在这种情况下,提交C。如果它的实际哈希 ID 是 cba9876... 或其他什么,Git 可以使用它来查找 C 的 contents。这些内容包括提交B 的实际哈希ID。 Git 然后可以使用它来查找B,其内容包括提交A 的实际哈希ID。 Git 可以使用它来查找A,而A 没有父级,所以现在,Git 终于可以停止向后工作了。
从 branch tip 提交(如C)开始向后工作的过程(由 branch name 标识)在 Git 中至关重要。这就是历史存在的方式。 Git 存储库中的历史是提交,由这些向后指向的箭头连接。您从头开始,一次一个提交,遍历历史,看看您可以按照父箭头到达的位置。
这是最后一块拼图进入图片的地方,分支名称和其他此类名称出现。让我们暂停一下,结束这里的脚注,然后深入研究分支名称和绘图。
1Git 实际上是从 index 制作快照,但我们不会在这里详细介绍这些细节,只是说快照的内容——及时冻结,永远,对于那个提交 - 是当时 index 中的任何内容,这至少可能与您在 work-tree 中看到的内容不同你的工作。
2Git 实际上会检查这一点,只要它看起来方便或合适。这会自动检测 Git 存储库的意外损坏,例如当您尝试在 Dropbox 中存储时发生 - Dropbox 有时会在您(和 Git)背后修改文件,而 Git 会捕获它。不幸的是,几乎没有修复损坏的存储库的好方法——相反,Git 倾向于依赖于 Git 存储库被复制到各处的想法。你可能在其他地方有一份不错的副本,所以你把这一份完全扔掉。
分支名称查找提交哈希 ID
任何现有的存储库——嗯,除了一个完全空的、新鲜的、新的、no 提交的存储库之外的任何存储库——都有一些提交。这些提交形成了我们刚刚看到的后向链,例如:
A <-B <-C
我们——和 Git——需要某种方式来记录这个链中last提交的哈希 ID。
Git 实现这一点的方式是使用 Git 所称的 references 或 refs。 refs 的形式有很多种,但三巨头是:
- 分支名称,例如
master。
- 远程跟踪名称,例如
origin/master。 (Git 称这些 remote-tracking 分支名称 或 remote-tracking 分支,我认为这是一个坏名字;我已改用 remote-tracking 名称,我认为这更难出错。)
- 标签名称,例如
v1.3。
它们实际上都是由相同的底层技术实现的,但我们在这里将它们视为单独的名称形式。 分支名称有一个特殊的属性; 所有其他名字都没有这个属性。
其中一个名称的含义非常简单:它只是 Git 对象的实际原始哈希 ID,通常是一次提交。3 所以像 master 这样的分支名称点到分支中的最后次提交——在此图中提交C:
A--B--C <-- master
请注意,连接提交的箭头来自子节点并指向(不可变的)父节点,从而为我们提供了这种向后遍历的方法。我们不必费心把它们画进去。但是,branch 名称中出现的箭头,change。
当我们向master 添加新 提交时,Git 自动更新名称master 以保存新提交的哈希ID。所以如果我们现在创建一个新的提交,新的提交D 将指向C:
A--B--C <-- master
\
D
但Git会立即调整master,使其不指向C,而是指向D:
A--B--C--D <-- master
由于D 指向C,我们仍然可以找到所有提交:我们从最后开始,像往常一样向后工作。 C 现在是此过程中的第二个提交,而不是第一个。
3分支名称必须包含提交对象哈希ID,而标签名称更灵活。我们不需要在这里关心这个。因为远程跟踪名称的值是从分支名称复制的,所以远程跟踪名称也只包含提交哈希 ID。
分支名称对每个存储库都是私有的,但存储库相互通信
Git 是一个分布式 版本控制系统。这意味着每个 Git 存储库都是一种自包含的孤岛,所需的一切都在该存储库本地。如果有多个提交的多个分支,则它们全部在那个存储库中:
A--B--C--D--G--H <-- master
\
E--F <-- dev
为了让 Git 真正有用,我们经常使用 Git 与其他 Git 用户交流工作。为此,我们交换提交。由于加密校验和技巧,它们的哈希 ID 在所有地方的 所有 Git 中都是通用的。给定快照和元数据,每个 Git 将计算相同的哈希 ID。因此,如果我的存储库有这样的提交 A 到 H - 请记住,这些单个大写字母代表唯一的、大而丑陋的哈希 ID - 我连接到 您的 存储库和 你有提交H,你的仓库也必须有和我一样的提交。
如果你没有提交H,我有一个你没有的提交。如果你有一些提交I 或J,你有一个我没有的提交。无论哪种方式,我们的 Git 都可以交换哈希 ID 来查看谁拥有什么。发送提交的人将发送它们,接收提交的人将接收它们,发送者将向接收者提供所需的任何新提交。
假设您正在接受我的新提交。我有新的提交 I 和 J,我的新提交 J 有一个 name 可以记住它的哈希 ID。在 my 存储库中,我有这个:
A--B--C--D--G--H <-- master
\
E
\
I--J <-- dev
无论出于何种原因,我没有提交您在dev 上的F。相反,在(共享)提交E 之后,我在dev 上提交了I-J。
这就是远程跟踪名称的用武之地
你的 Git 接受我的提交 I 和 J。我的提交I 有父E。所以你的仓库现在有了这个:
A--B--C--D--G--H <-- master
\
E--F <-- dev
\
I--J <-- ???
你的 Git 存储库将使用什么 name 来记住我的提交 I?最好不要使用dev:如果你的Git让你的dev指向commit I,你怎么会再次找到commit F?请记住,它有一个明显随机的哈希 ID。你永远无法猜测它。
所以,您的 Git 所做的是使用 远程跟踪名称 来记住 my 分支。你的 Git 会这样做:
A--B--C--D--G--H <-- master, origin/master
\
E--F <-- dev
\
I--J <-- origin/dev
(假设我的master 指向提交H)。
您的存储库中的名称origin/master 和origin/dev 是(您的)远程跟踪名称,记住我的master 和我的dev。4 此外,假设您现在查询您的 Git,要求它比较从 dev 与来自 origin/dev 的提交集,在 Git 使用的普通 walk-backwards 方法中。
从dev 开始,您将访问的提交是F,然后是E,然后是D,以此类推回到A。从origin/dev 开始,您将访问的提交是J,然后是I,然后是E,然后是D,以此类推回到A。 哪些提交对于哪个 walk 是唯一的?您从 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 从位于 origin 的 Git 上找到的分支名称创建远程跟踪名称,特定于 git fetch。当你的 Git 调用 origin 的 Git 并将 他们的 提交给 你 时,就会发生这种情况。
当然,您可以让您的 Git 通过 origin 调用他们的 Git 并 send 提交。这就是git push 操作,非常相似。你的 Git 告诉他们的 Git 你有哪些提交,而他们没有。让我们画一些。我们将从这个开始:
A--B--C--D--G--H <-- master, origin/master
\
E--F <-- dev
\
I--J <-- origin/dev
现在我们将运行git checkout master 和git 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 调用 Git,地址为 origin,使用:
git push -u origin newfeature/v4-json
你的 Git 拨通他们的 Git 并宣布你有提交 K 和 H。5 他们没有 K 但他们有 H 所以他们有你的Git 通过提交K 发送其快照和元数据。你的 Git 可以知道,因为他们有 H,所以他们也有 G 和 D 以及之前的所有内容,所以你只需要向他们发送 K 及其内容。
然后,最后,你的 Git 会问他们:现在,如果没问题,请设置你的名字 newfeature/v4-json 指向提交 K。 请注意,你没有他们设置xpt/newfeature/v4-json 或类似的东西。你让他们设置他们的分支!他们实际上还没有有newfeature/v4-json,所以他们可以设置一个。所以他们做到了!他们现在在他们的存储库中有一个newfeature/v4-json,指向提交K。
你的 Git 现在创建你的远程跟踪名称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 也会立即运行:
git branch --set-upstream-to=origin/newfeature/v4-json newfeature/v4-json
这会为您的分支 newfeature/v4-json 设置 upstream 设置。您的每个分支都可以有 一个 (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:告诉他们提交K、H、G、D、C、B和/或 A 根据需要。您的其他未共享提交故意保持私密。
6请记住,由于哈希 ID 的魔力,提交K 在每个 Git 无处不在 中是通用的。 每个 Git 要么有K,通过它的哈希ID,然后它是那个 提交;或者根本没有K,所以没关系。
(这不一定是 100% 保证的。假设 K 的哈希 ID 实际上是 b5101f929789889c2e536d915698f58d5c5c6b7a。这是 Git 存储库中 Git 本身的提交的哈希 ID。如果您从不连接 您的 Git 存储库到 Git 的 Git 存储库,你和他们使用相同的哈希 ID 进行不同的提交是可以的。但是如果你确实曾经将你的 Git 存储库连接到 Git 的 Git 存储库, 发生了一些不太好的事情。简短的版本是你只是没有得到 Git 的提交,而他们只是没有得到你的提交:这两个存储库此时根本无法合并。这可能对两者都很好您和维护 Git 的人。但另请参阅 How does the newly found SHA-1 collision affect Git?)