检查 Git 中是否需要拉取(pull)

752

如何检查远程仓库是否已更改,需要进行拉取操作?

目前我使用以下简单脚本:

git pull --dry-run | grep -q -v 'Already up-to-date.' && changed=1

但它相当沉重。

有没有更好的方法?理想的解决方案应该检查所有远程分支,并返回每个已更改分支的名称和新提交数量。


16
请注意:"git pull --dry-run" 的实际效果可能与预期不同。似乎 git pull 命令会将未知选项直接传递给 git fetch 命令,导致结果与普通的 git pull 命令相同。 - user1004858
36
“pull” 是一种同时执行“fetch”和“merge”的简便方法,如果您需要检查远程仓库状态,则实际上是在模拟“fetch”。因此,您需要运行 git fetch -v --dry-run 命令来完成这个任务。 - Claudio Floreani
我尝试了OP提出的解决方案,但它没有返回任何结果。可能不是最好的方法? - carloswm85
我不明白原问题是关于一个shell脚本(在编写脚本时保持清晰)还是在日常shell使用中保持简单。您能在问题中具体说明吗? - Valerio Bozz
你可能对Git的新功能感兴趣:autostash - undefined
28个回答

1022

首先使用git remote update命令,将您的远程引用更新到最新状态。然后您可以执行以下几个操作之一:

  1. git status -uno 告诉您正在跟踪的分支是前进、落后还是已分叉。如果没有输出,本地和远程相同。

  2. git show-branch *master 显示所有名称以“master”结尾的分支(例如 masterorigin/master)中的提交。

如果您在 git remote update 中使用 -v 选项(git remote -v update),则可以查看哪些分支已更新,因此您不需要任何其他命令。

但是,看起来您想在脚本或程序中执行此操作,并最终得到一个真/假值。如果是这样,有方法可以检查当前 HEAD 提交与正在跟踪的分支的 HEAD 之间的关系,尽管由于存在四种可能的结果,您无法将其简化为是/否答案。但是,如果您准备执行 pull --rebase,那么您可以将“本地落后”和“本地已分叉”视为“需要拉取”,而将另外两个(“本地领先”和“相同”)视为“不需要拉取”。

您可以使用git rev-parse <ref>获取任何引用的提交ID,因此您可以针对masterorigin/master执行此操作并进行比较。如果它们相等,则分支相同。如果它们不相等,则需要知道哪个领先于另一个。使用git merge-base master origin/master将告诉您两个分支的公共祖先,如果它们没有分歧,则这将与其中一个相同。如果您获得三个不同的ID,则分支已经分叉。
要正确执行此操作(例如在脚本中),您需要能够引用当前分支及其远程跟踪分支。 /etc/bash_completion.d中的bash提示符设置函数具有一些有用的代码可用于获取分支名称。但是,您可能实际上不需要获取名称。 Git有一些简洁的方式来引用分支和提交(如git rev-parse --help中所述)。特别地,您可以使用@表示当前分支(假设您不处于分离头状态),@{u}表示其上游分支(例如origin/master)。因此,git merge-base @ @{u}将返回当前分支和其上游分支分叉的(哈希值的)提交,git rev-parse @git rev-parse @{u}将为您提供两个提示的哈希值。这可以在以下脚本中总结:
#!/bin/sh

UPSTREAM=${1:-'@{u}'}
LOCAL=$(git rev-parse @)
REMOTE=$(git rev-parse "$UPSTREAM")
BASE=$(git merge-base @ "$UPSTREAM")

if [ $LOCAL = $REMOTE ]; then
    echo "Up-to-date"
elif [ $LOCAL = $BASE ]; then
    echo "Need to pull"
elif [ $REMOTE = $BASE ]; then
    echo "Need to push"
else
    echo "Diverged"
fi

注意: 较旧版本的git不允许单独使用@,因此您可能需要改用@{0}

UPSTREAM=${1:-'@{u}'}允许您显式传递上游分支,以防您想要针对与当前分支配置的不同远程分支进行检查。这通常采用remotename/branchname的形式。如果未给出参数,则该值默认为@{u}

脚本假定您已经先执行了git fetchgit remote update,以使跟踪分支保持最新状态。我没有将其构建到脚本中,因为将获取和比较作为单独操作更加灵活,例如,如果您想在不获取的情况下进行比较,因为您已经最近获取了。


4
我认为你可以像@brool建议的那样,将git ls-remote origin -h refs/heads/master与git rev-list --max-count=1 origin/master结合起来。如果它们返回相同的哈希值,则表示自上次更新远程引用(使用pull、fetch、remote update等方法)以来,远程分支没有更改。这样做的好处是您不必立即拉取所有提交的内容,而可以在更方便的时间留下来完成该任务。然而,由于remote update不会破坏数据,您也可以随时进行更新。 - Neil Mayhew
3
你也可以尝试使用git status -s -u no,它比git status -u no的输出更短。 - Phillip Cloud
2
@mhulse,git remote -v update。查看git remote --help的输出以获得更详细的说明。 - Neil Mayhew
1
现在需要为@指定一个限定符。您可以使用@{0}代替@。 - Ben Davis
1
@BenDavis:git rev-parse --help 表示 @HEAD 的快捷方式。我正在使用 git 版本 2.1.1 - Neil Mayhew
显示剩余20条评论

150

如果你有一个上游分支

git fetch <remote>
git status

如果您没有上游分支

比较这两个分支:

git fetch <remote>
git log <local_branch_name>..<remote_branch_name> --oneline

例如:

git fetch origin

# See if there are any incoming changes
git log HEAD..origin/master --oneline

(我假设origin/master是您的远程跟踪分支)

如果输出中列出了任何提交记录,则表示您有待处理的更改--您需要进行合并。 如果git log未列出任何提交记录,则没有需要合并的内容。

请注意,即使您在功能分支上--没有跟踪远程分支,这也可以正常工作,因为它明确地引用origin/master而不是隐式使用Git记住的上游分支


2
即使本地分支有默认的远程分支,也可以使用更短的表示法 git fetch; git log HEAD.. --oneline - phil pirozhkov
68
git rev-list HEAD...origin/master --count 会给你两者之间所有 "不同" 提交记录的总数。 - Jake Berger
1
我最喜欢的解决方案只显示新提交记录(点赞两次) - spankmaster79
我该如何在(Ubuntu)批处理文件中使用它,以便在此命令显示需要拉取时运行其他命令? - Ulysses Alves
1
@JakeBerger的一则附注,你首先必须执行git fetch以接收远程最新更改。 - José Manuel Blasco
显示剩余4条评论

107

如果这是为了脚本,你可以使用:

git fetch
$(git rev-parse HEAD) == $(git rev-parse @{u})

(注意:与之前的答案相比,这种方法的好处在于您不需要单独使用命令来获取当前分支名称。" HEAD "和" @{u} "(当前分支的上游)会处理它。有关更多详细信息,请参见" git rev-parse --help "。)


我独立地发现了@{u},在看到你的回答之前已经更新了我的答案。 - Neil Mayhew
1
git rev-parse @{u}会在没有进行git fetch的情况下显示最新的提交吗? - Kyle Strand
6
这就是答案!不过,你的逻辑使用了==,意思是“如果与上游没有变化”。而我用!=来检查“如果与上游有变化”,以供我的应用程序使用。别忘了先运行git fetch命令! - ChrisPrime
4
我添加了git fetch,因为这对回答原始问题确实是必要的。顺便说一下,@代表HEAD - user1338062
1
Windows用户需要在@{u}周围加上单引号,例如git rev-parse '@{u}' - spuder

46

这里是一个Bash单行命令,比较当前分支的HEAD提交哈希值和其上游远程分支的哈希值,无需进行繁重的git fetchgit pull --dry-run操作:

[ $(git rev-parse HEAD) = $(git ls-remote $(git rev-parse --abbrev-ref @{u} | \
sed 's/\// /g') | cut -f1) ] && echo up to date || echo not up to date

以下是这个相对复杂的命令行语句解释:

  • 使用 $(x) Bash command-substitution 语法,将命令分组并嵌套。
  • git rev-parse --abbrev-ref @{u} 返回一个简写的上游引用(例如 origin/master),然后利用管道字符 sed 命令将其转换为以空格分隔的字段,例如 origin master
  • 此字符串被传递给 git ls-remote 命令,该命令返回远程分支的头提交。此命令将与远程存储库通信。管道字符 cut 命令提取第一个字段(提交哈希),删除制表符分隔的引用字符串。
  • git rev-parse HEAD 返回本地提交哈希。
  • Bash 语法 [ a = b ] && x || y 完成了这个一行命令:这是一个 Bash string-comparison = 在测试结构 [ test ] 中,接着是 and-list 和 or-list 结构 && true || false

2
如果您在分支名称中使用斜杠,请不要在sed上使用/g。只需使用“sed 's/// /"即可。 - Martyn Davis
@wjordan 当远程仓库无法访问(或正在维护)时,您的解决方案会失败,并触发“最新”的提示。 - frame
在几乎所有的使用情况下,@frame 都是首选的结果。如果远程当前无法访问,则您的本地语义上将与您上次运行脚本的时间一样。 - cowbert

44

该命令

git ls-remote origin -h refs/heads/master

会列出远程仓库的当前 HEAD -- 你可以将其与先前的值进行比较,或查看你的本地仓库是否有该 SHA。


1
有任何比较这些值的示例脚本吗? - takeshin
35
git rev-list HEAD...origin/master --count 可以给出两者之间“不同”的提交总数。 - Jake Berger
3
为了澄清,这只会显示你落后的提交次数(不是向前或向后),并且只有在你先执行git fetchgit remote update命令的情况下才能正常工作。另外,git status也会显示提交次数。 - Dennis
1
@Dennis 我认为 .. 表示 "在 origin/master 中提交,减去 HEAD"(即落后的提交数)。而 ... 则表示 对称差异(即前后都有的提交)。 - Jake Berger
@jberger 感谢提示,但我不确定如何使用它。这个对吗?https://dev59.com/w3A75IYBdhLWcg3wf5RV#17192101 - ma11hew28
4
很好。据我所知,这是唯一一个实际检查更新源但不隐式执行“fetch”的解决方案。 - Kyle Strand

23

以下脚本完美运行。

changed=0
git remote update && git status -uno | grep -q 'Your branch is behind' && changed=1
if [ $changed = 1 ]; then
    git pull
    echo "Updated successfully";
else
    echo "Up-to-date"
fi

可以在Ubuntu上使用.sh脚本工作,并且可以处理已更改的文件(检查是否需要拉取而不是处于未清理状态)。 - Noel Schenk

23

我建议您查看脚本https://github.com/badele/gitcheck。我编写了这个脚本以便一次性检查所有的Git仓库,并且它可以显示谁没有提交过代码,谁没有推送/拉取代码。

以下是一个示例结果:

这里输入图片描述


6
整洁,考虑重新用纯shell重写。 - Olivier Refalo
1
现在,您也可以直接从 Docker 容器中使用 gitcheck(将您的文件放在主机上)。有关更多信息,请参阅 gitcheck GitHub 项目。 - Bruno Adelé
在bash中有一个类似的工具 git-multi-repo-tooling。使用 git mrepo -c 命令可以显示所有待提交的更改。 - Greg

16

我只是想将这个作为实际帖子发布,因为在评论中很容易错过。

这个问题的正确和最佳答案由 @Jake Berger 给出,非常感谢你,大家都需要这个答案,而且每个人都会在评论中错过它。所以,为了所有挣扎的人,这里是正确的答案,只需使用此命令的输出即可知道是否需要进行 git pull 操作。如果输出为 0,则显然无需更新任何内容。

@stackoverflow,请给这个家伙敲响铃铛。感谢 @Jake Berger。

# will give you the total number of "different" commits between the two
# Jake Berger Feb 5 '13 at 19:23
git rev-list HEAD...origin/master --count

感谢你让它看起来漂亮,Arnaud :) - diegeelvis_SA
1
这需要一个fetch才能按预期工作,还是对origin/master的引用意味着git将在不获取本地任何内容的情况下查询远程? - Nick Chammas
@NickChammas 这个命令是本地的,所以它在 fetch 之后和 pull/merge/reset 之前非常有用。 - Akom

14

我认为最好的方法是:

git diff remotes/origin/HEAD

假设您已经注册了此 refspec。 如果您已克隆存储库,则应该可以使用它; 否则(即如果是在本地新建并推送到远程存储库),您需要显式添加 refspec。


12

我基于@jberger的评论提出了这个解决方案。

if git checkout master &&
    git fetch origin master &&
    [ `git rev-list HEAD...origin/master --count` != 0 ] &&
    git merge origin/master
then
    echo 'Updated!'
else
    echo 'Not updated.'
fi

关于您之前的评论,目前我无法给出明确的答案。在我发表那些评论时,我正在深入研究git,特别是远程和差异。从那时起已经过了几个月,很多知识都深藏在我的脑海中。 ;) 如果您正在寻找两者之间“不同”提交的数量,则 ... 似乎是您解决方案的有效部分。 - Jake Berger
1
谢谢。这很简洁。 - Shobhit Puri

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接