3

相关:列出尚未推送到源的 Git 提交

git rev-parse HEAD给我工作区中的最新提交,但这可以是本地提交的 githash。换句话说,尚未推送到远程的提交

如何在工作区的远程中找到也存在的最新提交

4

2 回答 2

6

要获取当前签出分支的已配置远程分支的最新提交,请执行

# 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}不是占位符,它们应该按原样输入)

文档

[<branchname>]@{upstream}, eg master@{upstream}, @{u}
一个branchname的后缀@{upstream}(缩写@{u})是指branchname指定的branch所设置的分支在(配置有分支..远程和分支..合并)之上构建。缺少的分支名称默认为当前分支名称。

于 2021-07-28T05:26:30.013 回答
3

从技术上讲,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 存储库中,以相同的方式查找哈希 ID,git rev-parse HEAD尽管通常不那么复杂。

相对于其他(远程)Git 存储库,您的本地 Git 存储库可能已过期。在这种情况下,您可能需要运行:

git fetch origin

首先是为了获取他们拥有的任何新提交,并更新您的各种远程跟踪名称:名称origin/masterorigin/develop

这里发生了什么

Git 将分支名称定义为一个名称(如masteror main、 or develop、或feature/tall其他名称),该名称包含此存储库中某个现有有效提交的哈希 ID。1 根据定义,该哈希 ID 是该分支“上”的最后一次提交。

Git 对此的处理本身就有点复杂,但如果我们注意到大多数提交(所有普通提交2 )都为其直接父提交存储了一个哈希 ID ,我们会发现我们可以将提交彼此相邻放置,就像珍珠一样或串珠:

... <-F <-G <-H ...

在这里,H代表一些现有提交的哈希 ID。该提交存储其父(早期)提交的哈希 ID G,. CommitG又存储了更早的 commit 的哈希 ID F,以此类推。

因为提交无法更改,并且哈希 ID 是不可预测的,3这些箭头总是向后指向。然后分支名称仅指向链中的最后一个提交:

...--G--H   <-- main

此外,Git 进行了设置,以便当您使用git checkoutgit switch选择某个分支名称作为当前分支时,特殊名称HEAD附加到分支名称:

...--G--H   <-- main (HEAD)

此时,两者git rev-parse maingit rev-parse HEAD都会产生相同的哈希 ID,即 commit H

如果您添加一个的提交,Git 会通过写出该提交的快照和元数据并使元数据 includeH的哈希 ID 来构造新的提交,以便新的提交I指向现有的提交H

...--G--H   ...
         \
          I

然后,作为 的最后一步git commit,Git 将新提交的哈希 ID 写入HEAD附加到的名称中,给出:

...--G--H
         \
          I   <-- main (HEAD)

nameHEAD仍然附加到 name 上main,但 namemain现在表示 commitI是分支上的最后一次提交。

然而,Git 确实有一种称为分离 HEAD模式的模式。在这里,我们告诉 Git 选择一些提交,而不是分支名称。例如,我们可能希望查看 commitG的快照,因此可以运行或类似的。结果是:git checkout hash-of-G

...--G   <-- HEAD
      \
       H--I   <-- main

git rev-parse HEAD命令现在显示 commit 的哈希 ID G:不是 commit 的H,也不是 commit 的I,而是 commit 的H。这是因为HEAD不再附加(到分支名称),而是分离(意味着HEAD直接包含提交的哈希 ID)。

(要返回提交I并在分支上main,我们将使用git checkout maingit switch main。这些将重新附加HEAD。)


1除非您有损坏的存储库,否则不存在现有但无效的提交。这里的想法是要强调,虽然哈希 ID看起来像随机垃圾,但你不能只是编造一个。它们实际上是通过运行加密校验和产生的大数的十六进制表示,因此它们根本不是随机的。

2这里普通commit的定义是它存储了一个父哈希ID。合并提交的定义是存储两个或多个父哈希 ID 的提交,而 Git 有第三种提交,即根提交,它存储父哈希 ID。根提交(在非空、非浅层存储库中总是至少有一个)通常是某人为该存储库所做的第一次提交。进行更多的根提交是可能的——无论是错误的还是故意的——但很少有充分的理由这样做;它只是不属于 Git 使用的图形算法。

3为了让它像这样工作,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。他们还可以随时创建新的提交

但是,每个提交都会获得一个唯一的哈希 ID。一旦你创建了一些提交,你就可以将这些git push提交发送到其他 Git。他们完全按原样获取整个提交(每个提交的完整快照和元数据),并且计算相同的加密校验和,因此他们为这些相同的提交分配了 Git 分配给它们的相同哈希 ID。

通过使用这个原理,这两个 Git 实际上只通过查看哈希 ID就可以确定谁有哪些提交。这就是实现 Git 存储库的分布式特性的原因。神奇之处在于哈希。

但是有一个问题。就像您自己的 Git使用某个分支名称找到您的最新提交一样,他们的 Git使用他们的分支名称找到他们的最新提交。因此,如果您要将提交发送到其他 Git 存储库,请在 at上使用:Iorigin

git push origin develop

从您的角度来看,他们将不得不在存储库中设置一些分支名称。按照惯例——<a href="https://memegenerator.net/img/instances/62271644/stupid-humans.jpg" rel="nofollow noreferrer">因为人类很容易被愚弄——我们倾向于使用相同的他们的存储库和我们的存储库中的分支名称。所以git push上面要求他们设置他们的 develop.

develop如果是名字就好了。main如果我们不打算丢失他们的任何提交,我们也可以要求他们设置他们的。也就是说,假设他们有:

...--G--H   <-- main

我们可以要求他们将它们设置main为指向一些新的提交J,只要J指向H最终(可能通过I):

...--G--H   <-- main
         \
          I--J   <-- request: please make "main" go here

Git 将这种请求称为快进操作并且通常允许它。(许多像 GitHub 这样的附加站点添加了更高级的分支保护系统,让您更加挑剔;不过,这种快速检查是基本 Git 中内置的全部内容。)基本 Git不会让您做的事情是这样的:

...--G--H--I   <-- main
         \
          J   <-- request: please make "main" go here

因为如果他们这样做,他们将无法访问他们的 commit I

远程跟踪名称和git fetch

要解决此类问题,我们应该先使用git fetch,然后再运行git push. 当我们运行时git fetch,我们的 Git 会调用他们的 Git——就像 for 一样git push,我们会在其中向他们发送我们的新提交——但我们没有向他们发送提交,而是让我们的 Git 向他们的 Git 询问任何对我们来说新的提交。他们将这些信息连同关于他们的哪些分支名称指向哪些提交的信息一起发送过来,我们的 Git 现在有他们拥有的任何新提交,而我们没有。

让我们假设我们都拥有...-G-H我们的main,并且他们已经从某个地方获得了一些新的提交I。不过,与此同时,我们Jmain. 所以我们俩的开始都是一样的:

...--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

我们的 Git 无法更新我们的main,因为如果更新了,我们就会丢失自己的 commit J 所以我们的 Git 所做的——不管他们是否添加了任何的提交——是获取他们的分支名称main,并更改它。我们的 Git通过粘贴在其前面将其分支名称转换为远程跟踪名称。4 所以我们最终得到这个:origin/

          I   <-- origin/main
         /
...--G--H
         \
          J   <-- main

(注意:如果我们有它作为我们当前的签出分支,请添加HEAD到我们的)。main

git fetch一步:

  • 获取他们拥有的任何新提交;
  • 更新我们所有的远程跟踪名称;因此
  • 准备我们做任何必要的事情来加入新的提交行(变基或合并)。

git fetch这意味着用 agit rebase或 a跟进 a 通常是明智的git merge。Git 提供了一个方便的命令,git pull它结合了这两个操作。由于各种原因,我不喜欢它,并鼓励那些刚接触fetchGit 的人至少在他们非常熟悉整个过程之前使用单独的和第二个 Git 命令序列。5

无论如何,所有这一切的总结是,远程跟踪名称是 Git 记住其他 Git 存储库在其分支名称中的内容的方式,这是我们的 Git 上次与他们的 Git 交谈的时候。git fetch操作倾向于更新所有这些,并且该git push操作在成功推送到一个分支时更新一个。我们的 Git 从他们的 Git 得到确认,他们接受了我们的请求,所以我们的 Git 现在知道他们的 Git 将该名称设置为该哈希 ID。6


4从技术上讲,远程跟踪名称位于单独的命名空间中,因此即使我们不小心调用了(本地)分支origin/xyz,Git 也能够保持我们的origin/xyz 分支,而不是origin/xyz基于其xyz分支的远程跟踪名称。但这要追溯到 Stupid Human Tricks,它让机器人 Bender 发笑。不要那样做。

5不是每个人都对git pull. 我不喜欢它的一些原因是因为它在早期就有一些非常糟糕的错误,而且我git pull不止一次失去了很多工作。但在我看来,主要问题是它做得太多了。默认情况下,正在进行一项改进行为的运动git pull,尽管我不确定这种情况在短期内发生的可能性有多大。如果它确实发生了,我仍然会推荐单独的步骤,但不会很快建议新手避免 git pull:这将是一个命令,如果它有效,它会做正确的事情,如果不是,那么这不是一件正确的事。

6一些自动获取时间更新是 Git 1.8.4 中的新功能,所以如果你有一个非常古老的 Git,比这更老,一定要git fetch origin在没有限制的情况下使用来更新所有内容。在这些古老的 Git 版本中git fetchgit pull运行的通常根本无法更新任何内容(另一个需要警惕的原因)。git fetch


分支、上游和@{upstream}

每个分支名称都允许(但不是必需)具有一 (1) 个上游设置。通常,分支的上游设置maindevelop设置为origin/mainorigin/develop:您自己的 Git 存储库中的远程跟踪名称

拥有这一套可以启用一些便利项目。它从来都不是真正需要的。而且,当您在自己的存储库中创建一个全新的分支名称时,不使用远程跟踪名称(它还不存在,因为originGit 还没有这个分支),它还没有上游,你将要使用git push -u origin HEAD或类似的方法在那里创建分支。这将在本地创建适当的远程跟踪名称,-u并将让您的 Git 将远程跟踪名称设置为分支的上游。

一旦你设置了一个上游,@{upstream}后缀——从技术上讲,它是一个可以附加到任何分支名称的后缀——告诉 Git 找到该分支的upstream。也就是说,master@{upstream}假设origin/master您已将master' 上游设置为默认值origin/master。这对每个分支名称重复。

@{upstream}像这样写的裸文字“意味着” HEAD@{upstream}。因此,这用于HEAD确定您在哪个分支,然后使用分支的上游设置来确定在您自己的本地 Git 存储库中使用哪个远程跟踪名称。

以上所有都是为什么以及如何RomainValeri 的答案是这个的简短版本。:-)

于 2021-07-28T06:21:15.217 回答