检查Git中是否需要拉取

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

现在我使用这个简单的脚本:

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

但它相当重。

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

465539 次浏览

该命令

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

将列出远程上的当前头部-您可以将其与以前的值进行比较,或者查看本地存储库中是否有SHA。

我认为最好的方法是:

git diff remotes/origin/HEAD

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

首先使用git remote update,使您的远程引用保持最新。然后您可以执行以下几种操作之一,例如:

  1. git status -uno将告诉您正在跟踪的分支是在前面、后面还是已经发散。如果它什么也没说,则本地和远程是相同的。

  2. git show-branch *master将显示名称以'master'结尾的所有分支中的提交(例如大师来源/主)。

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

然而,看起来你想在脚本或程序中执行此操作,并最终得到一个true/false值。如果是这样,有一些方法可以检查当前HEAD提交和你正在跟踪的分支头部之间的关系,尽管有四种可能的结果,你不能将其减少为是/否答案。但是,如果你准备做一个pull --rebase,那么你可以将“本地在后面”和“本地已经发散”视为“需要拉取”,将另外两个(“本地在前面”和“相同”)视为“不需要拉取”。

您可以使用git rev-parse <ref>获取任何ref的提交id,因此您可以对大师来源/主执行此操作并比较它们。如果它们相等,则分支相同。如果它们不相等,您想知道哪个领先于另一个。使用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}'}允许您选择显式传递上游分支,以防您想检查与为当前分支配置的远程分支不同的远程分支。这通常是远程名称/分支名称的形式。如果没有给出参数,值默认为@{u}

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

运行git fetch (remote)更新你的远程引用,它会告诉你什么是新的。然后,当你签出你的本地分支时,它会告诉你它是否落后于上游。

如果你有上游分支

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没有列出任何提交,那么就没有什么可合并的了。

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

我会按照brool建议的方式执行。以下单行脚本采用您上次提交版本的SHA1并将其与远程源的SHA1进行比较,并仅在它们不同时拉取更改。 它甚至比基于git pullgit fetch的解决方案更轻量级。

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

我建议你去看看脚本https://github.com/badele/gitcheck。我已经编写了这个脚本,以便通过一次检查你所有的Git存储库,它显示了谁没有提交,谁没有推/拉。

这是一个示例结果:

在此处输入图像描述

我将这个解决方案基于@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

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

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

它可以区分常见情况,例如需要拉取和需要推送,并且它是多线程的,因此获取一次发生。它有几个命令,例如拉取和状态。

将符号链接(或脚本)放在路径中的文件夹中,然后它以git all status(等)的方式工作。它只支持原始/主,但可以编辑或与其他方法组合。

如果这是一个脚本,你可以使用:

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

(注意:与之前的答案相比,这样做的好处是您不需要单独的命令来获取当前分支名称。“HEAD”和“@{u}”(当前分支的上游)负责它。有关更多详细信息,请参阅“git reg-parse--help”。)

如果您运行此脚本,它将测试当前分支是否需要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

当您在pulling之前commit时。

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

.git/hooks/pre-commit

chmod +x .git/hooks/pre-commit

这是一个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完成了一行:这是测试构造[ test ]中的Bash字符串比较=,然后是and-list和or-list构造&& true || false

我使用了一个基于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,则可以使用本地Git存储库的可选参数directory name调用它以对其执行操作。如果没有参数调用脚本,则假定当前目录是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

它也可以使用这里

已经有许多功能非常丰富和巧妙的答案。为了提供一些对比,我可以用一个非常简单的线条。

# 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

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

如果您在Windows上,您可以使用Git for Windows提供的Git Bash(安装或可移植)在Windows中运行此脚本。

此脚本需要参数

- local path e.g. /d/source/project1
- Git URL e.g. https://username@bitbucket.org/username/project1.git
- password


if a password should not be entered on the command line in plain text,
then modify the script to check if GITPASS is empty; do not
replace and let Git prompt for a password

该脚本将

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

如果脚本打印了更改,那么您可以继续获取或拉取。脚本可能效率不高,但它为我完成了工作。

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

你也可以找到现在这样做的一个Phing脚本

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

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

如果您想将任务添加为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

下面的脚本工作得很好。

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

使用简单的regexp:

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

对于最终在这个问题上寻找这个的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')
}

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

这个问题的正确和最佳答案是由@Jake Berger给出的,非常感谢伙计,每个人都需要这个,每个人都在评论中错过了这个。 因此,对于每个人都在努力解决这个问题,这里是正确的答案,只需使用此命令的输出即可知道是否需要进行git拉取。如果输出为0,那么显然没有什么可更新的。

@stackoverflow,给这个家伙一个铃铛。 感谢@杰克·伯杰

# 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

所有这些复杂的建议,而解决方案是如此简短和简单:

#!/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

因为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

这个单行代码在zsh中为我工作(来自@Stephen Haberman的回答)

git fetch; [ $(git rev-parse HEAD) = $(git rev-parse @{u}) ] \
&& echo "Up to date" || echo "Not up to date"

要在所需分支上自动执行git pull
使用喜欢:./pull.sh "origin/main"

#!/bin/bash


UPSTREAM=${1:-'@{u}'}
DIFFCOMM=$(git fetch origin --quiet; git rev-list HEAD..."$UPSTREAM" --count)
if [ "$DIFFCOMM" -gt 0 ]; then
echo "Pulling $UPSTREAM";
git pull;
else
echo "Up-to-date";
fi


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

由brool给出的是最轻的方式,如果远程中的某些内容发生了变化,则只需检查

从本地头部开始:

$ git log -1 --oneline @
9e1ff307c779 (HEAD -> master, tag: v5.15-rc4, origin/master, origin/HEAD) Linux 5.15-rc4

我看到我拉取的原点在该标签处是最新的。git status也这么说。但这只是本地最新的,获取后的(快进)合并。

要检查远程HEAD是否去了某个地方,以及master,也许还有一些新标签:

$ git ls-remote origin HEAD master --tags 'v5.1[56]-rc[345]*'


84b3e42564accd94c2680e3ba42717c32c8b5fc4        HEAD
84b3e42564accd94c2680e3ba42717c32c8b5fc4        refs/heads/master
71a6dc2a869beafceef1ce46a9ebefd52288f1d7        refs/tags/v5.15-rc3
5816b3e6577eaa676ceb00a848f0fd65fe2adc29        refs/tags/v5.15-rc3^{}
f3cee05630e772378957a74a209aad059714cbd2        refs/tags/v5.15-rc4
9e1ff307c779ce1f0f810c7ecce3d95bbae40896        refs/tags/v5.15-rc4^{}

HEAD仍然在同一个分支上,但不再是相同的提交。本地@提交保留在v5.15-rc4标签中。这与git上摘要工作表顶部kernel.org信息大致相同:

Branch: master <commit message> <author> age: 2 hours

只有ls-remote收集的信息较少-但只有我知道我在9e1ff...上,也就是v5.15-rc4。

除了命名引用(HEAD,master)或标签之外,还可以从任何存储库中获取头部或分支的列表:

$ git ls-remote --heads git://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git


af06aff2a0007976513134dfe993d55992dd866a        refs/heads/akpm
20bcee8e95f783c11a7bea1b6a40c70a37873e0a        refs/heads/akpm-base
a25006a77348ba06c7bc96520d331cd9dd370715        refs/heads/master
4d5a088c93cea1c821d02a2217c592391d9682e2        refs/heads/pending-fixes
4de593fb965fc2bd11a0b767e0c65ff43540a6e4        refs/heads/stable

这里URL替换了“起源”。


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

如果你这样问,就拉。

如何检查远程存储库是否最终完成了某些操作并且我想要拉取?

然后你获取、检查和合并。


使用单个git命令:

$ git rev-list -1  master
9e1ff307c779ce1f0f810c7ecce3d95bbae40896
$ git rev-list -1  @
9e1ff307c779ce1f0f810c7ecce3d95bbae40896

这本身并没有说太多,但假设我知道我没有犯任何事,然后:

$ git ls-remote origin HEAD master
60a9483534ed0d99090a2ee1d4bb0b8179195f51        HEAD
60a9483534ed0d99090a2ee1d4bb0b8179195f51        refs/heads/master

会告诉我遥控器有改变。自从上次编辑以来它真的有。kernel.org说Age: 46 min.关于主服务器上的最后一次提交。

git fetch之后:

$ git rev-list -1 master
9e1ff307c779ce1f0f810c7ecce3d95bbae40896


$ git rev-list -1 FETCH_HEAD
60a9483534ed0d99090a2ee1d4bb0b8179195f51


$ git log --oneline ..FETCH_HEAD
60a9483534ed (origin/master, origin/HEAD) Merge tag 'warning-fixes-20211005' of git://git.kernel.org/pub/scm/linux/kernel/git/dhowells/linux-fs
f6274b06e326 Merge tag 'linux-kselftest-fixes-5.15-rc5' of git://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest
ef31499a87cf fscache: Remove an unused static variable
d9e3f82279bf fscache: Fix some kerneldoc warnings shown up by W=1
bc868036569e 9p: Fix a bunch of kerneldoc warnings shown up by W=1
dcb442b13364 afs: Fix kerneldoc warning shown up by W=1
c0b27c486970 nfs: Fix kerneldoc warning shown up by W=1
84b3e42564ac Merge tag 'media/v5.15-3' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media
b60be028fc1a Merge tag 'ovl-fixes-5.15-rc5' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/vfs
df5c18838ea8 Merge tag 'mips-fixes_5.15_1' of git://git.kernel.org/pub/scm/linux/kernel/git/mips/linux
206704a1fe0b media: atomisp: restore missing 'return' statement
740da9d7ca4e MIPS: Revert "add support for buggy MT7621S core detection"
1dc1eed46f9f ovl: fix IOCB_DIRECT if underlying fs doesn't support direct IO
2f9602870886 selftests: drivers/dma-buf: Fix implicit declaration warns
a295aef603e1 ovl: fix missing negative dentry check in ovl_rename()

现在我在本地拥有所有信息,但尚未合并。我还下载了所有对象。git show HASHgit diff HASH工作。

在这种情况下,合并几乎是无操作:快进到最后一次提交,没有额外的(真正的)合并,更不用说冲突了。这可以通过--ff-Only来确保:

$ git merge --ff-only FETCH_HEAD
Updating 9e1ff307c779..60a9483534ed
Fast-forward
...
...

那么我怎么知道什么时候拉呢?只要这两个哈希是/将是不同的:Updating 9e1ff307c779..60a9483534ed Fast-forward。它们不能相同,那将是“无更新”。

最新的reflg提交也说了同样的话:

$ git log -10 --oneline -g
60a9483534ed (HEAD -> master, origin/master, origin/HEAD) HEAD@{0}: merge 60a9483534ed0d99090a2ee1d4bb0b8179195f51: Fast-forward
9e1ff307c779 (tag: v5.15-rc4) HEAD@{1}: pull: Fast-forward

看看这个,出现一个新的标签可能是最好的触发器,在这种情况下也是目标;这又回到了git ls-remote origin --tags PATTERN


…不要告诉我git remote show另一个方法:

Show提供有关遥控器的一些信息。

使用-n选项,远程头不是首先使用git ls-Remote查询;而是使用缓存信息。