检查是否需要在Git中拉


622

如何检查远程存储库是否已更改并且需要拉出?

现在,我使用以下简单脚本:

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

但是它很重。

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


14
请注意:“ git pull --dry-run”无法正常运行。似乎git pull将未知选项直接传递给git fetch。结果是正常的git pull。

27
“ pull”只是一次完成“ fetch”和“ merge”的快捷方式,如果您需要检查远程回购状态,则实际上是在模拟“ fetch”。因此git fetch -v --dry-run,您需要什么。
Claudio Floreani

Answers:


857

首先使用git remote update,以使您的远程引用保持最新状态。然后,您可以做几件事之一,例如:

  1. git status -uno会告诉您所跟踪的分支是在前面,后面还是在分支。如果什么也没说,则本地和远程相同。

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

如果-vgit remote updategit remote -v update)一起使用,则可以看到更新了哪些分支,因此您实际上不需要任何其他命令。

但是,您似乎想在脚本或程序中执行此操作,并最终得到true / false值。如果是这样,尽管有四种可能的结果,您不能将其简化为是/否答案,但是有多种方法可以检查您当前的HEAD提交与要跟踪的分支的头之间的关系。但是,如果您准备进行操作,pull --rebase则可以将“本地落后”和“本地分歧”视为“需要拉动”,将其他两个视为“不需要拉动”。

您可以使用来获取任何引用的提交ID git rev-parse <ref>,因此您可以针对masterorigin / master进行比较。如果它们相等,则分支相同。如果它们不相等,您想知道哪个在另一个之前。使用git merge-base master origin/master将告诉您两个分支的共同祖先,如果它们没有分歧,则它们将彼此相同。如果您获得三个不同的ID,则说明分支已经分开。

为了正确地做到这一点,例如在脚本中,您需要能够引用当前分支以及它正在跟踪的远程分支。bash提示设置功能中/etc/bash_completion.d有一些有用的代码来获取分支名称。但是,您实际上可能不需要获取名称。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}

该脚本假设您已经完成了a git fetchgit remote updatefirst操作,以使跟踪分支保持最新状态。我之所以没有将其构建到脚本中,是因为它能够作为单独的操作来进行获取和比较,因此更加灵活,例如,如果您希望在不进行获取的情况下进行比较,因为您已经获取了。


4
@takeshin我想你可以将git ls-remote origin -h refs / heads / master与git rev-list --max-count = 1 origin / master结合起来,如@brool所建议。如果它们返回相同的哈希值,则自从您上次更新远程引用(使用pull,fetch,remote update等)以来,远程分支就没有改变。这将带来的好处是,您不必拉下其中的内容。立即提交所有提交,但可以保留更方便的时间。但是,由于远程更新是非破坏性的,因此您还是应该这样做。
尼尔·梅休

2
您也可以尝试一下git status -s -u no,它给出的输出比短git status -u no
菲利普·

2
@mhulse ,git remote -v update。查看的输出git remote --help以获得更完整的说明。
尼尔·梅休2013年

1
@ChrisMaes好点。较旧版本的git需要更明确的语法。我尝试了各种系统,发现它@{u}与git 1.8.3.2兼容,但@没有。但是@适用于1.8.5.4。故事的寓意:git不断改进,值得拥有最新版本。
尼尔·梅休

1
@现在需要一个说明符。您可以使用@ {0}代替@。
本·戴维斯

132

如果您有上游分支机构

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如果本地有一个默认的远程分支,则甚至可以使用更短的符号。
菲尔·皮罗

@philpirozhkov如果您有默认的远程分支,我认为应该使用简单的“ git status”。我的回答是对任何两个分支的通用回答,其中一个分支可能会也可能不会跟踪另一个分支。
PlagueHammer 2012年

55
git rev-list HEAD...origin/master --count将为您提供两者之间“不同”提交的总数。
杰克·伯杰

1
简短。我最喜欢的解决方案仅显示新的提交(竖起大拇指两次)
spankmaster79 2014年

如何在(Ubuntu)批处理文件中使用此命令,以便在该命令显示需要拉取的情况下可以运行其他命令?
尤利西斯·阿尔维斯

69

如果这是用于脚本的,则可以使用:

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

(请注意:此答案与先前答案的好处是,您不需要单独的命令即可获取当前分支名称。“ HEAD”和“ @ {u}”(当前分支的上游)将为您提供帮助。请参见“ git rev-parse --help”了解更多详细信息。)


我独立发现@ {u},并在看到您的答案之前更新了我的答案。
尼尔·梅休

1
git rev-parse @{u}实际显示的最新承诺,而一个git fetch
凯尔·斯特兰德

3
这是票!虽然,您的逻辑正在使用==,意思是“如果上游没有变化”。我曾经!=为我的应用程序检查“上游是否有更改”。别忘了git fetch先!
ChrisPrime

1
我添加了git fetch,因为确实有必要回答原始问题。@HEADbtw的缩写。
user1338062

Windows用户将需要用单引号引起来,@{u}例如git rev-parse '@{u}'
spuder

36

命令

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

会在遥控器上列出当前磁头-您可以将其与以前的值进行比较,或者查看本地存储库中是否包含SHA。


1
是否有任何示例脚本可以比较这些值?
Takehin 2010年

18
git rev-list HEAD...origin/master --count将为您提供两者之间“不同”提交的总数。
杰克·伯杰

3
@jberger进行澄清,这只会显示您落后的提交数量(而不是落后的数量),并且仅在您git fetchgit remote update第一次使用时才有效。git status还显示了一个计数,顺便说一句。
丹尼斯

1
我以为@Dennis ..是“在源/主提交中,减去HEAD”(即后面的提交数)。然而,...对称差(即我们之前和之后)
杰克贝格

3
优秀的。据我所知,这是唯一检查更新来源但不隐式执行的解决方案fetch
凯尔·斯特兰德

35

这是一个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 命令替换语法对命令进行分组和嵌套。
  • git rev-parse --abbrev-ref @{u}返回缩写的上游ref(例如origin/master),然后通过管道sed命令(例如)将其转换为以空格分隔的字段origin master
  • 该字符串被送入git ls-remote,该字符串返回远程分支的起始提交。该命令将与远程存储库通信。管道cut命令仅提取第一个字段(提交哈希),删除制表符分隔的参考字符串。
  • git rev-parse HEAD 返回本地提交哈希。
  • Bash语法[ a = b ] && x || y完善了单行代码:这是测试构造中的Bash 字符串比较 ,其后是and-list和or-list构造。=[ test ]&& true || false

2
我会不会,如果你在分支名称中使用斜杠的使用sed / g的。那只是“ sed's / \ // /”。
马丁·戴维斯

@wjordan当远程存储库是不可达(或下维护)和将触发“最新的”你的溶液失败
框架

20

我建议您去看看脚本https://github.com/badele/gitcheck。我已经对该脚本进行了编码,以便一次检查所有Git存储库,并显示谁尚未提交,谁尚未推送/拉动。

这里是一个示例结果:

在此处输入图片说明


6
整洁,正在考虑用纯壳重写它
Olivier Refalo

1
现在,您还可以直接从Docker容器中使用gitcheck(文件位于主机中),有关更多信息,请参见gitcheck github项目
BrunoAdelé15年

bash git-multi-repo-tooling中的类似工具。 git mrepo -c这将显示所有未决的提交。
格雷格

11

我基于@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的深处,尤其是远程和差异。从那以后已经有几个月了,很多知识被埋藏在我的大脑中。;)如果您正在寻找两者之间“不同”提交的数量,那么这...似乎是解决方案的有效部分。
杰克·伯杰

1
谢谢。这很干净。
Shobhit Puri 2015年

10

已经有许多功能非常丰富且独具匠心的答案。为了提供一些对比,我可以做一个非常简单的线条。

# Check return value to see if there are incoming updates.
if ! git diff --quiet remotes/origin/HEAD; then
 # pull or whatever you want to do
fi

2
原始答案缺少“!” 在如果。当没有变化时,git diff的返回值为零。
thuovila '16

IMO最佳解决方案,不过我需要用“原件/母版”或其他修订版本替代“ remotes / origin / HEAD”
Matthias Michael Engh,

9

我认为最好的方法是:

git diff remotes/origin/HEAD

假设您已注册此refspec。如果已经克隆了存储库,则应该这样做(即,如果回购是在本地重新创建的,并已推送到远程),则需要显式添加refspec。


9

下面的脚本运行完美。

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

6

我会按照布鲁尔的建议去做。下面的单行脚本采用您最后提交的版本的SHA1并将其与远程源版本的SHA1进行比较,并仅在它们不同的情况下提取更改。而且,基于git pull或的解决方案甚至更轻巧git fetch

[ `git log --pretty=%H ...refs/heads/master^` != `git ls-remote origin
-h refs/heads/master |cut -f1` ] && git pull

如果git存储库使用“ --depth 1”克隆(以限制下载大小),则此命令失败。你知道吗,有办法解决吗?
Adam Ryczkowski

git log返回很多行,并给出错误“ bash:[:参数过多”。我要切换到git rev-parse --verify HEAD
Drew Pierce 2014年

1
这是bash完成的简单字符串比较。如果出现问题,我建议您检查语法(即您输入的错误)​​。首先运行 git log --pretty=%H ...refs/heads/master^ 以获取上一个提交版本的SHA1,然后运行 git ls-remote origin -h refs/heads/master |cut -f1 以获取远程源的SHA1。这两个是git命令,与bash无关。bash在方括号内的作用是将第一个命令的输出与第二个命令的输出进行比较,如果相等,则返回true并运行git pull
克劳迪奥·弗洛雷阿尼

“如果它们相等,则返回true并运行git pull”。我知道我很挑剔,但是为了避免混淆,这应该是“如果他们相等”。另外,无论出于何种原因,第一个git命令对我都不起作用。(我在git上2.4.1。)所以我只是在使用git log --pretty=%H master | head -n1。但是我不确定是否完全一样。
xd1le 2015年

6

如果运行此脚本,它将测试当前分支是否需要git pull

#!/bin/bash

git fetch -v --dry-run 2>&1 |
    grep -qE "\[up\s+to\s+date\]\s+$(
        git branch 2>/dev/null |
           sed -n '/^\*/s/^\* //p' |
                sed -r 's:(\+|\*|\$):\\\1:g'
    )\s+" || {
        echo >&2 "Current branch need a 'git pull' before commit"
        exit 1
}

将它作为Git钩子预先提交来避免它非常方便

Merge branch 'foobar' of url:/path/to/git/foobar into foobar

当你commit之前pulling

要将此代码用作钩子,只需将脚本复制/粘贴到

.git/hooks/pre-commit

chmod +x .git/hooks/pre-commit

6

我只想将其发布为实际发布,因为很容易在评论中错过它。

@Jake Berger给出了此问题的正确和最佳答案,非常感谢老兄,每个人都需要这个,每个人都在评论中错过了。因此,对于每个在此方面苦苦挣扎的人来说,这都是正确的答案,只需使用此命令的输出即可知道是否需要执行git pull。如果输出为0,则显然没有任何更新。

@stackoverflow,给这个家伙一个钟声。谢谢@杰克·伯格

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

4

运行git fetch (remote)以更新您的远程引用,它将向您显示新功能。然后,当您结帐本地分支机构时,它将显示是否在上游分支机构后面。


我认为他已经有本地分支签出了,所以他需要其他东西来显示它是否落后等。他可以使用git status来做到这一点。
尼尔·梅休

没错,在您获取遥控器后,git status也会显示出来。

1
这是心情的一部分git pull --dry-run,但是我认为每分钟运行一次cron脚本太重了。

@takeshin:您必须先登录网络才能检查远程存储库。如果没有任何新内容,fetch除了检查状态之外,不会做其他事情。如果您需要对远程更新进行非常快速和轻量级的响应,则可能需要考虑将某种通知挂接到远程存储库。

@takeshin:如果您想每分钟检查一次远程仓库,我想您已经错过了DVCS的要点。整个想法是能够独立发展一段时间,然后稍后将它们平稳地整合在一起。它不像cvs,svn,p4等,您总是必须在存储库中的最新内容之上工作。如果您确实需要其他人在做的事情,则应该使用其他通信机制(例如电子邮件)来告诉您何时准备就绪。
尼尔·梅休

4

解决方案如此短而容易时,所有这些复杂的建议:

#!/bin/bash

BRANCH="<your branch name>"
LAST_UPDATE=`git show --no-notes --format=format:"%H" $BRANCH | head -n 1`
LAST_COMMIT=`git show --no-notes --format=format:"%H" origin/$BRANCH | head -n 1`

git remote update
if [ $LAST_COMMIT != $LAST_UPDATE ]; then
        echo "Updating your branch $BRANCH"
        git pull --no-edit
else
        echo "No updates available"
fi

即使有更改,LAST_COMMIT和LAST_UPDATE也始终相等
canbax

此解决方案既简单又好,需要git remote update在代码执行之前获取最新的原始提交信息
ak93,19年

不应该git remote updategit show命令前附加?
Setop

2

这是我的Bash脚本版本,该脚本检查预定义文件夹中的所有存储库:

https://gist.github.com/henryiii/5841984

它可以区分常见情况,例如需要上拉和需要下推,并且它是多线程的,因此提取一次完成。它具有多个命令,例如pull和status。

将符号链接(或脚本)放在路径中的文件夹中,然后用作git all status(等)。它仅支持原点/母版,但可以对其进行编辑或与其他方法结合使用。


1
git ls-remote | cut -f1 | git cat-file --batch-check >&-

将列出不在您的仓库中的任何远程服务器中引用的所有内容。要捕获对您已有的东西的远程ref更改(例如,重置为先前的提交),需要花费一些时间:

git pack-refs --all
mine=`mktemp`
sed '/^#/d;/^^/{G;s/.\(.*\)\n.* \(.*\)/\1 \2^{}/;};h' .git/packed-refs | sort -k2 >$mine
for r in `git remote`; do 
    echo Checking $r ...
    git ls-remote $r | sort -k2 | diff -b - $mine | grep ^\<
done

1

也许是这样,如果您要将任务添加为crontab:

#!/bin/bash
dir="/path/to/root"
lock=/tmp/update.lock
msglog="/var/log/update.log"

log()
{
        echo "$(date) ${1:-missing}" >> $msglog
}

if [ -f $lock ]; then
        log "Already run, exiting..."
else
        > $lock
        git -C ~/$dir remote update &> /dev/null
        checkgit=`git -C ~/$dir status`
        if [[ ! "$checkgit" =~ "Your branch is up-to-date" ]]; then
                log "-------------- Update ---------------"
                git -C ~/$dir pull &>> $msglog
                log "-------------------------------------"
        fi
        rm $lock

fi
exit 0

1

使用简单的正则表达式:

str=$(git status) 
if [[ $str =~ .*Your\ branch\ is\ behind.*by.*commits,\ and\ can\ be\ fast-forwarded ]]; then
    echo `date "+%Y-%m-%d %H:%M:%S"` "Needs pull"
else
    echo "Code is up to date"
fi

这是行不通的。git status只是本地检查,因此如果您已经更新了远程def,它只会告诉您分支是否落后。
minhaz1

0

我使用基于Stephen Haberman回答的脚本版本:

if [ -n "$1" ]; then
    gitbin="git -C $1"
else
    gitbin="git"
fi

# Fetches from all the remotes, although --all can be replaced with origin
$gitbin fetch --all
if [ $($gitbin rev-parse HEAD) != $($gitbin rev-parse @{u}) ]; then
    $gitbin rebase @{u} --preserve-merges
fi

假设git-fetch-and-rebase调用了该脚本,则可以使用directory name本地Git存储库的可选参数调用该脚本以对其执行操作。如果不带任何参数调用脚本,则它将假定当前目录为Git存储库的一部分。

例子:

# Operates on /abc/def/my-git-repo-dir
git-fetch-and-rebase /abc/def/my-git-repo-dir

# Operates on the Git repository which the current working directory is part of
git-fetch-and-rebase

也可以在这里获得。


0

在阅读了许多答案和多个帖子,并花了半天时间尝试各种排列之后,这就是我的想法。

如果您使用的是Windows,则可以使用Windows的Git提供的Git Bash(安装或便携式)在Windows中运行此脚本。

该脚本需要参数

-本地路径,例如/ d / source / project1
-Git URL,例如https://username@bitbucket.org/username/project1.git
-密码

如果不应在命令行上以纯文本形式输入密码,
然后修改脚本以检查GITPASS是否为空;不要
替换并让Git提示输入密码

该脚本将

- Find the current branch
- Get the SHA1 of the remote on that branch
- Get the SHA1 of the local on that branch
- Compare them.

如果脚本打印了更改,则可以继续获取或提取。该脚本可能效率不高,但是可以帮我完成工作。

更新-2015-10-30:stderr为dev null,以防止将带有密码的URL打印到控制台。

#!/bin/bash

# Shell script to check if a Git pull is required.

LOCALPATH=$1
GITURL=$2
GITPASS=$3

cd $LOCALPATH
BRANCH="$(git rev-parse --abbrev-ref HEAD)"

echo
echo git url = $GITURL
echo branch = $BRANCH

# Bash replace - replace @ with :password@ in the GIT URL
GITURL2="${GITURL/@/:$GITPASS@}"
FOO="$(git ls-remote $GITURL2 -h $BRANCH 2> /dev/null)"
if [ "$?" != "0" ]; then
  echo cannot get remote status
  exit 2
fi
FOO_ARRAY=($FOO)
BAR=${FOO_ARRAY[0]}
echo [$BAR]

LOCALBAR="$(git rev-parse HEAD)"
echo [$LOCALBAR]
echo

if [ "$BAR" == "$LOCALBAR" ]; then
  #read -t10 -n1 -r -p 'Press any key in the next ten seconds...' key
  echo No changes
  exit 0
else
  #read -t10 -n1 -r -p 'Press any key in the next ten seconds...' key
  #echo pressed $key
  echo There are changes between local and remote repositories.
  exit 1
fi

0

对于最终遇到这个问题的Windows用户,我已将一些答案修改为Powershell脚本。根据需要进行调整,保存到.ps1文件中,然后按需运行或根据需要计划运行。

cd C:\<path to repo>
git remote update                           #update remote
$msg = git remote show origin               #capture status
$update = $msg -like '*local out of date*'
if($update.length -gt 0){                   #if local needs update
    Write-Host ('needs update')
    git pull
    git reset --hard origin/master
    Write-Host ('local updated')
} else {
    Write-Host ('no update needed')
}

0

因为Neils的回答对我有很大帮助,所以这里是没有依赖项的Python翻译:

import os
import logging
import subprocess

def check_for_updates(directory:str) -> None:
    """Check git repo state in respect to remote"""
    git_cmd = lambda cmd: subprocess.run(
        ["git"] + cmd,
        cwd=directory,
        stdout=subprocess.PIPE,
        check=True,
        universal_newlines=True).stdout.rstrip("\n")

    origin = git_cmd(["config", "--get", "remote.origin.url"])
    logging.debug("Git repo origin: %r", origin)
    for line in git_cmd(["fetch"]):
        logging.debug(line)
    local_sha = git_cmd(["rev-parse", "@"])
    remote_sha = git_cmd(["rev-parse", "@{u}"])
    base_sha = git_cmd(["merge-base", "@", "@{u}"])
    if local_sha == remote_sha:
        logging.info("Repo is up to date")
    elif local_sha == base_sha:
        logging.info("You need to pull")
    elif remote_sha == base_sha:
        logging.info("You need to push")
    else:
        logging.info("Diverged")

check_for_updates(os.path.dirname(__file__))

hth


-5

您还可以找到现在执行此操作的Phing脚本

我需要一个解决方案来自动更新生产环境,感谢我分享的这个脚本,我们感到非常高兴。

该脚本是用XML编写的,需要Phing


在此处复制粘贴脚本可能不是一个好主意,我仍在对其进行更新...
Pol Dellaiera
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.