您如何以编程方式检查本地副本是否位于远程副本之后?

How do you programmatically check if the local copy is behind the remote?

目前我正在获取最新的 运行 git status 并解析 Your branch is up to date with 'origin/master' 的输出,但这感觉像是一个 hack。

我研究过使用 git status --porcelain,但这只包括在系统上所做的文件更改,不包括在远程上所做的更改。我不关心实际进行了哪些更改,我只想知道是否存在任何更改(本地或远程)。

我怎样才能干净地实现这个目标?

要以编程方式获取 count 个在当前分支与其上游不同的提交,例如使用 git rev-list --count --left-right HEAD...@{upstream}git rev-list --count master...master@{upstream} .注意这里的三个点,它们将分支名称或 HEAD<em>branch</em>@{upstream} 分开 git status ] 或 git branch -vv 打印 ahead 1behind 2up to date 或其他。

请注意,这假设您首先 在分支 上,并且分支 前面的上游and/or落后。如果上游是 远程跟踪名称,如 origin/master,这假定存储在远程跟踪名称中的值是您想要存储在其中的值。

还有很多东西要知道

如果您正在编写这些东西的脚本,重要的是准确了解(或定义)您所说的最新的意思。

纯粹在本地——即在一个存储库 + 工作树组合中——需要考虑三个实体:

  • 当前提交,又名 HEAD

    这可能是一个分离的 HEAD,其中 HEAD 包含原始哈希 ID,或者相反,在分支上,其中 HEAD 包含分支本身的名称。在分支上时,分支名称,例如master,包含当前提交的原始哈希 ID。无论哪种方式,HEAD 总是指当前提交。1

    当前提交本身是只读的(完全)和永久的(大多数情况下,您可以故意 放弃 提交,之后它们最终会被删除)。您可以更改哪个提交是当前提交(例如,git checkout <em>different-commit</em>), 但你不能改变提交本身。由于提交无法更改,因此根据定义它永远不会 "out of date" :它就是它。与任何提交一样,当前提交有一些元数据(谁创建的,何时创建的,等等)以及每个文件的完整快照。

    提交中存储的文件采用特殊的 Git 格式(当然是只读的)。

  • 工作树,这是您工作的地方。

    在这里,你可以读写每一个文件。这些文件为普通格式,未压缩且特定于 Git。您也可以在这里找到 Git 不知道的文件,但在我们可以正确地讨论这个之前,我们需要涵盖第三个实体。

  • 索引,也称为暂存区,有时也称为缓存.

    该索引有多种用途(因此有多个名称),但我认为最好将其描述为如果您现在进行提交,您将进行的下一次提交。也就是说,索引(实际上只是一个文件)包含 Git 制作新快照所需的所有信息,以放入新的提交中。因此,索引包含所有 文件 ,这些文件将进入您所做的 next 提交。

    索引中的文件是压缩的,并且是 Git-only 格式,就像提交中的文件一样。不过,对于我们这里的目的而言,关键区别在于索引 中的文件可以 更改。您也可以将新文件放入索引,或从索引中删除现有文件。

    git add <em>file</em> 真正做的是从[=204]复制文件 =]工作树,进入索引。这将替换索引中的先前版本,以便索引现在与工作树匹配。或者,如果你想删除一个文件,git rm <em>file</em> 删除那个文件来自索引 工作树。


1一个新的仓库根本没有提交,所以这个规则有一个例外:HEAD can引用一个根本不存在的分支名称。在一个全新的存储库中就是这种情况:HEAD 表示当前分支是 master,但是 master 在您进行第一次提交之前实际上并不存在。

(git checkout --orphan 命令可以为另一个分支重新创建这个特殊的 "on a branch that does not exist yet" 状态。这不是大多数人大多数时候会做的事情,但它可以出现在程序中检查状态。)


git status 的作用

因为索引和工作树都是可写的,所以两者都可以是 "dirty" 或以某种方式导致某些东西成为 "out of date"。如果您认为工作树文件是最新的,则可能是 index 副本过时了,因为它与工作树副本不匹配。一旦将工作树文件复制到索引中,索引将不再与 HEAD 提交匹配,并且在某个时候需要新的提交。

git status除了运行宁git rev-list --count --left-right与分支机构及其上游并获得这些数字之外,2是什么运行s,实际上是两个 git diffs(带有 --name-status,因为它对详细补丁不感兴趣):

  1. 比较HEAD索引。无论此处 不同 是什么,这些都是 为提交 准备的更改,因为如果您现在提交,Git 将快照整个索引,并且该快照将与 当前 恰好在这些文件中提交不同。

  2. 将索引与工作树进行比较。无论这里有什么不同,这些都是未提交提交的更改。一旦你 运行 git add 这些文件,索引副本将匹配工作树副本,但不再匹配 HEAD 副本,所以现在这些将是 准备提交。


2请注意 git status 首先检查您是否 一个分支,如果是,则该分支具有 上游设置。另外,这都是内置的,所以它不必运行一个单独的程序,但原理是一样的。


未跟踪,可能被忽略

我们现在也可以正确定义未跟踪文件的含义。未跟踪文件很简单,就是不在索引中的文件。也就是说,如果我们使用 git rm --cached 从索引中删除一个文件(仅),或者如果我们在工作树中创建一个文件而不在索引中创建相应的文件,我们有一个工作树文件具有索引中没有相同的 name。这是一个 未跟踪的文件

如果文件 未被跟踪,git status 通常会抱怨它:将索引与工作树进行比较的差异 运行 表示啊,工作树中有一个文件不在索引 中,Git 会告诉您它未被跟踪。如果它未被跟踪故意,你可以让git status闭嘴,通过在[=42]中列出该文件或匹配它的路径名模式=] 文件。本质上,就在抱怨某些文件未被跟踪之前,Git 查看忽略指令。3 但是如果文件 在索引,Git 从不在任何 .gitignore.

中查找其名称

3忽略指令还告诉 git add 任何 en-masse "add everything" 应该 避免 添加文件,如果它当前未被跟踪。


上游和远程

分支的上游可以是远程跟踪名称,例如origin/master。这些名称是您 Git 记住其他 Git 分支的方式。要更新远程 origin 的远程跟踪名称,您只需 运行 git fetch origin.

请注意,您可以拥有多个遥控器!如果你在某一秒 URL 添加第二个遥控器 fredgit fetch fred 将在 that [=299= 调用 Git ],并更新您的 fred/master 等等。所以运行git fetch右边遥控器很重要。

运行 git fetch 没有附加名称将获取当前分支上游的远程,或者从 origin 当前分支没有上游,或者没有当前分支,所以这通常只是 运行ning git fetch.

的问题

子模块

子模块实际上只是对另一个 Git 存储库的引用,但这给总体计划带来了全新的麻烦。每个 Git 存储库都有自己的 HEAD、工作树和索引。这些可以像以前一样干净或脏,如果子模块不处于分离头状态,则子模块的分支可以在 and/or 之前 its 上游之后。

但是,子模块存储库通常处于分离头状态。 超级项目 中的每个提交都列出了 特定的提交 ,您的 Git 应该将子模块 Git 分离到该提交中。当超级项目 Git 检查提交时,超级项目 Git 将子模块的哈希 ID 存储到超级项目的索引中。这样每个新的超级项目提交都会记录正确的哈希 ID。

更改 hash ID,超级项目中git add复制current实际签出的hash ID子模块,进入超级项目存储库中的索引(哇!)。因此,如果您移动了子模块(通过那里的 git checkout),您将导航回超级项目,子模块路径上的 运行 git add,现在超级项目的索引记录了正确的哈希 ID ,为下一个超级项目提交做好准备。

(测试子模块是否在超级项目索引所需的提交上比较困难。)