相关链接:列出尚未推送到远程的Git提交记录
git rev-parse HEAD
可以给出工作区中最新的提交记录,但它可能是一个本地提交的 Git哈希值。也就是说,这个提交记录尚未被推送到远程仓库。
如何查找最新的提交记录,并且在工作区和远程仓库中都存在呢?
相关链接:列出尚未推送到远程的Git提交记录
git rev-parse HEAD
可以给出工作区中最新的提交记录,但它可能是一个本地提交的 Git哈希值。也就是说,这个提交记录尚未被推送到远程仓库。
如何查找最新的提交记录,并且在工作区和远程仓库中都存在呢?
获取当前所在分支配置的远程分支的最新提交,执行以下操作:
# first get your remote-tracking branches up-to-date with remote
git fetch
# then do
git rev-parse @{upstream}
# or even just
git rev-parse @{u}
(注: @{upstream} / @{u}
不是占位符,需要按照原样输入)
从文档中得知:
[<分支名>]@{upstream},例如 master@{upstream},@{u}
分支名后缀 @{upstream}(简写为 @{u})指的是该分支构建在哪个分支之上(通过 branch..remote 和 branch..merge 进行配置)。如果省略分支名,则默认使用当前分支。
技术上,git rev-parse HEAD
给出了当前提交的哈希 ID。这不一定是最新的,并且即使在正常使用中,它也可能与工作树中的内容不匹配(因为工作树可以被修改但尚未提交)。这些要点会干扰回答您的问题:也许您并不想要最新的提交。此外,您可以通过 git push
推送到某个远程存储库的提交通常不在任何工作树中,因为这些远程存储库通常是裸存储库:裸存储库通常接受 git push
请求,而非裸存储库则不接受。
所有这些都不重要,你可能只需要一个简单的:
git rev-parse origin/master
或者:
git rev-parse origin/<some-other-name-here>
或者:
git rev-parse @{upstream}
这些内容中的最后一个需要进一步解释。前两个只是使用您现有的名称,在您现有的Git存储库中,以与git rev-parse HEAD
相同的方式找到哈希ID,尽管通常较少复杂。
有可能您的本地Git存储库与其他(远程)Git存储库不同步。在这种情况下,您可能需要运行:
git fetch origin
首先要获取开发者的任何新提交并更新各种远程跟踪名称,例如 origin/master
和 origin/develop
等名称。
Git 将一个分支名定义为一种名称,例如 master
、main
、develop
、feature/tall
或其他持有此存储库中某个现有有效提交的哈希 ID 的名称。1 此哈希 ID 定义上是该分支上的最后提交。
Git 处理这个问题本身有点复杂,但如果我们注意到大多数提交——所有的普通的提交2——都为其直接父提交存储了恰好一个哈希 ID,我们就可以像在一条字符串上放置珠子或玻璃珠一样依次放置提交:
... <-F <-G <-H ...
H
代表某个现有提交的哈希 ID。该提交存储其父(较早)提交 G
的哈希 ID。提交 G
再次存储更早提交 F
的哈希 ID,以此类推。...--G--H <-- main
git checkout
或git switch
选择某个分支名称作为当前分支时,特殊名称HEAD
会附加到分支名称上:...--G--H <-- main (HEAD)
此时,git rev-parse main
和git rev-parse HEAD
将产生相同的哈希ID,即提交H
的哈希ID。
如果您添加了一个新的提交,Git会通过编写该提交的快照和元数据并使元数据包括H
的哈希ID来构建新提交,以便新提交I
向后指向现有提交H
:
...--G--H ...
\
I
git commit
的最后一步骤,Git会将新提交的哈希ID写入HEAD
所附加的任何名称中,从而得到:...--G--H
\
I <-- main (HEAD)
HEAD
仍然附加在名称main
上,但是名称main
现在表示提交I
是该分支上的最后一次提交。G
的快照,因此运行git checkout hash-of-G
或类似命令。结果是:...--G <-- HEAD
\
H--I <-- main
git rev-parse HEAD
命令现在显示提交G
的哈希ID:不是提交H
的哈希ID,也不是提交I
的哈希ID,而是提交H
的哈希ID。这是因为HEAD
不再附加(到分支名称),而是分离(意味着HEAD
直接包含提交的哈希ID)。I
并进入main
分支,我们可以使用git checkout main
或git switch main
。这将重新附加HEAD
。)
1除非您的存储库已损坏,否则不存在现有但无效的提交。这里的重点是强调,尽管哈希 ID 看起来像随机垃圾,但您不能随意制作一个。实际上,它们是通过运行加密校验和生成的大型数字的十六进制表示形式,因此它们根本不是随机的。
2这里的“普通提交”定义为存储一个父哈希 ID。 “合并提交”的定义是存储两个或多个父哈希 ID 的提交,而 Git 有第三种提交类型,“根提交”,它不存储任何父哈希 ID。根提交 - 在非空、非浅层存储库中始终至少有一个 - 通常是某人为该存储库制作的第一个提交。可能会出现更多的根提交 - 出于错误或故意 - 但很少有好的理由这样做;它只是 Git 使用的图形算法的副产品。
为了使Git像这样工作,它会在每个提交中插入一些独特的内容。具体而言,每个提交都有一个时间戳,在正常使用中很难预测某个未来提交的未来时间戳。虽然在理论上可能会出现问题,但至少今天来说,这些问题是不切实际的,即使用作恶作剧也是如此。git push
分支名称是特定于每个Git仓库的。您的Git仓库保存了您自己的分支名称。其他一些Git仓库有它自己的分支名称。
当您创建一个分支名称时,只需为其选择一些现有的提交:
...--G--H <-- main (HEAD)
可能会变成:
...--G--H <-- develop (HEAD), main
develop
并选择现有提交H
。如果您现在创建一个新的提交I
,则结果包括更改存储在develop
中的哈希ID,以产生:...--G--H <-- main
\
I <-- develop (HEAD)
develop
的分支,因为HEAD
附加到了develop
而不是main
。git branch
(可能带有-D
以删除)、git checkout -b
或git switch -c
创建或销毁分支名称。他们也可以随时创建新的提交。git push
将这些提交发送到其他Git。他们获得完整的提交——每个提交的完整快照和元数据——并且计算相同的加密校验和,因此他们将这些相同的提交分配给与你的Git分配给它们相同的哈希ID。但是有一个问题。就像您自己的 Git 使用某个分支名称找到您的最新提交一样,他们的 Git 也使用他们的分支名称来查找他们的最新提交。因此,如果您要将提交I
发送到位于 origin
的其他 Git 存储库中:
git push origin develop
...--G--H <-- main
main
设置为指向一些新的提交J
,只要J
最终指向H
(可能通过I
)即可:...--G--H <-- main
\
I--J <-- request: please make "main" go here
...--G--H--I <-- main
\
J <-- request: please make "main" go here
main
上有...-G-H
,而他们从某个地方获取了一些新的提交I
。与此同时,我们在main
上添加了一个新的提交J
。因此,我们最初都是相同的:...--G--H <-- main
I
: I <-- (main in their Git)
/
...--G--H
我们添加了J
:
...--G--H
\
J <-- main (in our Git)
git fetch
时,我们会获取到他们的新提交: I <-- (main in their Git)
/
...--G--H
\
J <-- main
main
,因为如果它这样做了,我们就会失去我们自己的提交 J
。所以,我们的 Git 会将他们的分支名称 main
改变成一个远程跟踪名称,无论他们是否添加了任何新的提交。我们的 Git 在他们的分支名称前加上 origin/
,这样我们最终得到的是这样的结果: I <-- origin/main
/
...--G--H
\
J <-- main
注意:如果我们当前已经检出 main
分支,那么将 HEAD
添加到其中。
这个 git fetch
步骤:
这意味着通常跟随 git fetch
的是使用 git rebase
或 git merge
命令。Git 提供了一个方便的命令 git pull
,它可以组合两个操作。由于许多原因,我不喜欢这个命令,并鼓励那些刚开始使用 Git 的人至少在熟悉整个过程之前使用单独的 fetch
和第二个 Git 命令序列。5
git fetch
操作通常会更新它们所有,而 git push
操作在成功推送到一个分支时会更新一个。我们的 Git 从他们的 Git 得到确认,他们接受了我们的请求,因此我们的 Git 现在知道他们的 Git 已将该名称设置为该哈希 ID。6
4从技术上讲,远程跟踪名称位于单独的名称空间中,因此即使我们意外地调用了一个(本地)分支origin / xyz
,Git 也能够区分我们基于其xyz
分支的origin/xyz
分支与我们的origin/xyz
远程跟踪名称。但这涉及到愚蠢的人类技巧,会让机器人Bender发笑;不要这样做。
5并非所有人都对git pull
如此谨慎。我对它的厌恶部分原因是早期它存在一些非常严重的错误,我曾多次因此失去了很多工作。但主要问题在于,在我看来它做得太多了。目前有一股运动正在兴起,旨在通过默认设置使git pull
更好地运行,尽管我不确定这是否会很快发生。如果真的发生了,我仍然会建议分步操作,但不会那么快地建议新手避免使用git pull
:如果它能正常工作,它就是正确的,否则就没有单一正确的方法。
6一些自动获取时间更新是在Git 1.8.4中新增的,因此,如果您使用的Git版本比这还古老,请务必使用git fetch origin
而没有任何约束来更新所有内容。在这些古老的Git版本中,git fetch
(另一个对git fetch
持怀疑态度的原因)经常无法更新任何内容。
@{upstream}
每个分支名称都允许但不需要有一个(1)上游设置。通常,像main
或develop
这样的分支的上游设置被设置为origin/main
或origin/develop
:在你自己的Git仓库中的远程跟踪名称。
有了这个设置,可以使用一些方便的项目。它实际上从来没有被要求过。当你在自己的仓库中创建一个全新的分支名称时,不使用远程跟踪名称(因为origin
Git还没有这个分支),它还没有上游,你将需要使用git push -u origin HEAD
或类似命令在那里创建该分支。这将在本地创建适当的远程跟踪名称,并且-u
会使你的Git将远程跟踪名称设置为该分支的上游。
@{upstream}
后缀附加到任何分支名称上,告诉Git查找该分支的上游。也就是说,假设您已将master
的上游设置为默认的origin/master
,那么master@{upstream}
就是origin/master
。每个分支名称都是如此。裸的@{upstream}
文本就像这样写,意味着HEAD@{upstream}
。因此,它使用HEAD
来确定您所在的分支,然后使用该分支的上游设置来确定要在您自己的本地Git存储库中使用哪个远程跟踪名称。以上所有内容解释了RomainValeri's answer为什么是这个问题的简短版本以及如何实现。 :-)