“git pull --all”命令能更新我本地所有分支吗?

577

我经常至少有三个远程分支:master、staging和production。我有三个本地分支来跟踪这些远程分支。

更新所有本地分支很繁琐:

git fetch --all
git rebase origin/master
git checkout staging
git rebase origin/staging
git checkout production
git rebase origin/production

我希望能够执行"git pull -all",但我一直没能使它起作用。它似乎执行了"fetch --all",然后更新(快进或合并)当前工作分支,但不是其他本地分支。

我仍然需要手动切换到每个本地分支并更新。


9
你是否希望本地跟踪分支仅在快进情况下自动更新?你应该这样做,因为合并可能会出现冲突,需要你解决。 - Jakub Narębski
44
假设需要花费300美元的咨询时间来解决这个问题,根据77,476次浏览次数计算,这个单一问题已经让公司付出了23,242,800美元。现在再考虑一下这个问题:https://dev59.com/ynVC5IYBdhLWcg3wz0h9 和其他所有类似的问题。哇。 - Luke Puplett
29
@Luke,你是我听到的第一个指出尝试让Git做我们想要的事情所花费的时间会给公司带来成本的人。这些简单的事情应该是自动化的,并且应该如此简单,以至于我不必打开浏览器阅读论坛,依我之见。 - Samuel
20
在SO上,关于Git的问题数量几乎是Mercurial的9倍,而且前者大多数似乎都是“我该如何在Git中进行<简单操作>?”这表明Git可能设计不良、文档不够完善、不够直观,或者三者兼而有之。 - Ian Kemp
38
@IanKemp 我不确定在不了解stackoverflow用户群体的情况下做出这种说法是否安全。如果Mercurial在这里没有被普遍使用,或者它的用户使用其他论坛来询问有关它的问题,我会期望看到相同的结果 :) JavaScript的问题数量约为Assembly的51倍 - 因此仅通过这些指标来判断工具可能并不总是准确的。 - danShumway
显示剩余6条评论
30个回答

234
我使用hubsync子命令自动化此过程。我在我的.bash_profile中有alias git=hub,所以我输入的命令是:
git sync

这将更新所有具有匹配上游分支的本地分支。从手册中可以看到:
  • 如果本地分支已过时,则快进它;
  • 如果本地分支包含未推送的工作,请发出警告;
  • 如果该分支似乎已合并且其上游分支已被删除,请删除它。
它还处理当前分支上未提交更改的暂存/取消暂存。我曾经使用过一个类似的工具叫做 git-up,但它已不再维护,而 git sync 几乎完全相同。

2
这个脚本的问题在于它无法获取新创建的远程分支。 - marcamillion
非常好。另一个好处是它自动隐藏和恢复。 - Steve Bennett
我对此进行了负评。这不是一个“纯粹”的git解决方案;我不喜欢这句话:“所以git pull默认合并,而实际上应该是变基。”因为它并不是显而易见的。它没有提供有关git-up和git交互的详细信息。它看起来像是只要运行它,它就能工作,不要问为什么。 - mariotti
“hub” 命令已被废弃。它已经超过2年没有发布了,而且一个“修复”的错误使得 “hub sync” 命令变得几乎无用。每当远程跟踪分支被删除时(在某些存储库中,这会在每次合并分支时发生),“hub sync” 就会崩溃,而没有任何错误消息。 - Hubro
它不起作用。它给了我以下错误:“git:'sync'不是git命令。请参阅'git --help'” - Yustina Ivanova
显示剩余2条评论

219
您描述的pull --all命令行为完全符合预期,尽管不一定有用。该选项会传递给git fetch,然后从所有远程获取所有引用,而不仅仅是需要的一个;pull然后合并(或在您的情况下重新定位)适当的单个分支。
如果您想要检出其他分支,则必须检出它们。是的,合并(和重新定位)绝对需要工作区,因此无法在未检出其他分支的情况下完成。如果您希望,可以将您描述的步骤包装成脚本/别名,但我建议将命令与&&连接起来,以便在其中一个失败时,它不会继续执行。

3
如果您提供一个示例命令行,我会点赞的。我在Github上遇到了这个问题。我在UI上创建了一个分支。现在我需要让本地显示该分支。git pull --all; git branch... 哎呀...命令是:git branch -a。 - mariotti
@mariotti 取决于你想要做什么,从你的评论中并不太清楚。你最好提一个新问题来问。 - Cascabel
4
这个回答的重点是内置命令实际上不能完成OP所要求的操作,因此他们需要进行一系列步骤。这些步骤可以自动化(例如John的回答),但必须执行。因此,如果您试图做的与OP完全相同,那么没有真正的示例可供给出,如果您正在尝试做不同的事情,那么您应该提出一个新问题-这就是StackOverflow的工作方式!(而且您的评论不太清楚,但我最好的猜测是您希望在这里得到与OP不同的东西,所以是的,提出一个新问题吧。) - Cascabel
当存在裸仓库时,合并和变基为何绝对需要工作树? - Felix Dombek
这个答案的要点是你不能在裸仓库中执行这些操作。当然,你可以在带工作树的完整仓库中执行它们,并将结果推送到裸仓库中。 - Cascabel
显示剩余2条评论

55

我知道这个问题已经有将近3年的历史了,但我自己也问过同样的问题,并没有找到现成的解决方案。因此,我自己创建了一个自定义的git命令shell脚本。

下面是 git-ffwd-update 脚本的具体内容...

  1. 它会执行 git remote update 命令来获取最新版本
  2. 然后使用 git remote show 命令来获取跟踪远程分支的本地分支列表(例如,可以与 git pull 一起使用的分支)
  3. 接着,它会使用 git rev-list --count <REMOTE_BRANCH>..<LOCAL_BRANCH> 命令来检查本地分支落后于远程分支的提交数量(反之亦然)
  4. 如果本地分支领先于远程分支1个或多个提交,则无法进行快进合并,需要手动合并或变基
  5. 如果本地分支落后于远程分支1个或多个提交,且本地分支领先于远程分支0个提交,则可以通过 git branch -f <LOCAL_BRANCH> -t <REMOTE_BRANCH> 命令进行快进合并

该脚本可以像下面这样调用:

$ git ffwd-update
Fetching origin
 branch bigcouch was 10 commit(s) behind of origin/bigcouch. resetting local branch to remote
 branch develop was 3 commit(s) behind of origin/develop. resetting local branch to remote
 branch master is 6 commit(s) behind and 1 commit(s) ahead of origin/master. could not be fast-forwarded

完整的脚本应该保存为git-ffwd-update,并且需要在PATH上。
#!/bin/bash

main() {
  REMOTES="$@";
  if [ -z "$REMOTES" ]; then
    REMOTES=$(git remote);
  fi
  REMOTES=$(echo "$REMOTES" | xargs -n1 echo)
  CLB=$(git rev-parse --abbrev-ref HEAD);
  echo "$REMOTES" | while read REMOTE; do
    git remote update $REMOTE
    git remote show $REMOTE -n \
    | awk '/merges with remote/{print $5" "$1}' \
    | while read RB LB; do
      ARB="refs/remotes/$REMOTE/$RB";
      ALB="refs/heads/$LB";
      NBEHIND=$(( $(git rev-list --count $ALB..$ARB 2>/dev/null) +0));
      NAHEAD=$(( $(git rev-list --count $ARB..$ALB 2>/dev/null) +0));
      if [ "$NBEHIND" -gt 0 ]; then
        if [ "$NAHEAD" -gt 0 ]; then
          echo " branch $LB is $NBEHIND commit(s) behind and $NAHEAD commit(s) ahead of $REMOTE/$RB. could not be fast-forwarded";
        elif [ "$LB" = "$CLB" ]; then
          echo " branch $LB was $NBEHIND commit(s) behind of $REMOTE/$RB. fast-forward merge";
          git merge -q $ARB;
        else
          echo " branch $LB was $NBEHIND commit(s) behind of $REMOTE/$RB. resetting local branch to remote";
          git branch -f $LB -t $ARB >/dev/null;
        fi
      fi
    done
  done
}

main $@

1
谢谢您提供这个脚本。有没有可能有人将该脚本转换为Windows批处理文件? - Saariko
@RyanWilcox 谢谢,我每天都在使用它(工作日...);-) 你可能想看一下我的点文件,以获取更多与git相关的脚本和别名:https://github.com/muhqu/dotfiles - muhqu
2
@muhqu 在更新的git版本中,-t和-l不应该在一个git branch调用中同时使用。我删除了-l,将调用更改为git branch -f $LB -t $ARB >/dev/null;,现在脚本按预期工作。 - Radek Liska
1
@Idbrii 哈哈,我才意识到我在10年前给出了这个答案,而我仍然在我的日常git工作中使用这个脚本... 我从不使用git pull... 只要坚持自己的方法就好。 - muhqu
我喜欢这个脚本,但是如果你的连接需要输入密码来执行获取/更新操作,它似乎会在git remote update $REMOTE命令上卡住。有没有办法让这个命令正确处理用户输入? - undefined
显示剩余6条评论

24

自动化并不是很难:

#!/bin/sh
# Usage: fetchall.sh branch ...

set -x
git fetch --all
for branch in "$@"; do
    git checkout "$branch"      || exit 1
    git rebase "origin/$branch" || exit 1
done

4
在脚本中最好不要使用别名。这并没有实际获取任何内容,只是在已经获取的内容重新基于。你应该将“git rebase origin/$branch”改为“git pull”,以便从适当的跟踪分支(可能在origin上)获取,并根据配置确定是合并还是基于重置。 - Cascabel
@Jefromi:我忘记了fetch。已经编辑过了;额外的功能/修复取决于OP。 - Fred Foo
8
我仍然认为你可能想要使用 pull(或检查 branch.<branch>.rebase),这样你就不会意外地对已经设置为正常拉取(合并)的分支进行变基。 - Cascabel
1
考虑使用 set -e 代替 || exit 1,以使解释器在第一个错误时退出。 - crishoj

22

我仍然希望这是自动的,并且应该有一个选项可以实现 - 还应该进行一些检查,以确保只有在快进更新时才能发生这种情况(这就是为什么手动执行拉取要安全得多!!),但是除了警告之外,您可以:

git fetch origin
git update-ref refs/heads/other-branch origin/other-branch

不必检出本地分支即可更新其位置。

注意:您将失去当前的分支位置并将其移动到源分支所在的位置,这意味着如果您需要合并,则会丢失数据!


1
这正是我一直在寻找的解决方案。我通常不会在多个分支上有未推送的更改,只想更新我的各个本地分支以匹配远程分支。这个解决方案比我通常的删除/重新检出方法要好得多! - Dave Knight
7
合并为一个命令:git fetch origin other-branch:other-branch,意思是从远程仓库拉取other-branch分支并将其合并到本地同名分支。 - fabb
致命错误:origin/other-branch:不是有效的SHA1。 - Klaas van Schelven

19

这里有很多答案,但没有一个使用git-fetch来直接更新本地引用的,这比检出分支简单得多,也比使用git-update-ref更安全。

在这里,我们使用git-fetch来更新非当前分支,对于当前分支则使用git pull --ff-only。它:

  • 不需要检出分支
  • 只在可以快进的情况下更新分支
  • 当无法快进时会报告错误

以下是代码:

#!/bin/bash
currentbranchref="$(git symbolic-ref HEAD 2>&-)"
git branch -r | grep -v ' -> ' | while read remotebranch
do
    # Split <remote>/<branch> into remote and branchref parts
    remote="${remotebranch%%/*}"
    branchref="refs/heads/${remotebranch#*/}"

    if [ "$branchref" == "$currentbranchref" ]
    then
        echo "Updating current branch $branchref from $remote..."
        git pull --ff-only
    else
        echo "Updating non-current ref $branchref from $remote..."
        git fetch "$remote" "$branchref:$branchref"
    fi
done

git-fetch 的手册页面上写道:

   <refspec>
       The format of a <refspec> parameter is an optional plus +, followed by the source ref <src>,
       followed by a colon :, followed by the destination ref <dst>.

       The remote ref that matches <src> is fetched, and if <dst> is not empty string, the local ref
       that matches it is fast-forwarded using <src>. If the optional plus + is used, the local ref is
       updated even if it does not result in a fast-forward update.

通过指定git fetch <remote> <ref>:<ref>(不带任何+),我们可以获得一个仅在可以快进时更新本地引用的获取。

请注意,这假设本地和远程分支具有相同的名称(并且您想要跟踪所有分支),它应该真正使用关于您拥有哪些本地分支以及它们如何设置跟踪的信息。


1
“仅在可以快进的情况下更新分支” - 快进的意义是什么?如果我想要所有分支中的最新源代码,那么为什么我要关心是否快进呢?正是这些东西让我嘲笑Git及其粉丝。你不能只用一个命令来完成这个操作。相反,您需要执行***c*n***步骤(而不是1步),其中c是一些重复命令的数量,n是分支的数量。 - jww
@jww 当世界上大多数人使用的版本控制系统是Git时,嘲笑Git及其狂热支持者并没有帮助。但我偏题了...我认为在这种“全局合并”脚本的情况下,如果非当前分支存在合并冲突,最好不要尝试对其进行更改。 - Ville
1
这很有帮助,谢谢。唯一不喜欢的是它为每个远程分支(包括我不感兴趣的分支)在本地创建了一个分支,所以我将 git branch -r | grep -v ' -> ' | while read remotebranch 更改为 git branch -r | grep -v ' -> ' | grep -f <(git branch | cut -c 3- | awk '{print "\\S*/"$0"$"}') | while read remotebranch 以限制它只针对我已经在本地拥有的分支。此外,我在开始时添加了一个 git fetch --prune 来更新远程分支列表,以避免一些警告。 - Nate Cook
1
@jww 如果你在一个分支中有未推送的更改,你可能不想让它们丢失并最终被垃圾回收。为了避免丢失提交,git pull 将自动运行 git merge 而不是仅仅覆盖当前分支以指向传入的提交。如果你不喜欢自动合并,你可以指定只快进,这样你就可以 1. 不会有每次执行 git pull 时自动、可能无意的嘈杂合并提交;2. 不会删除和丢失未推送的本地提交。 - binki

14
这个问题目前还没有解决,至少不容易或不需要脚本来解决:请参考 Junio C Hamano 在 git 邮件列表中的这篇帖子,其中解释了这种情况并提出呼吁一个简单解决方案。
主要的论据是你不应该需要这样做:

使用 Git 最新版本(即 v1.5.0 或更新版本),就没有必要再使用纯粹跟踪远程的本地“dev”了。如果你只想去看一下,请使用“git checkout origin/dev”直接检出远程跟踪分支。

这意味着唯一我们需要为用户提供便利的情况是,在你有本地更改或计划进行更改时,处理那些“跟踪”远程分支的本地分支。

如果你在标记为跟踪远程“dev”的“dev”上有本地更改,并且你处于与“dev”不同的分支上,那么在“git fetch”更新远程跟踪“dev”之后,我们不应该做任何操作。它不会快速前进。

提出解决方案的呼吁是为了一个选项或外部脚本来修剪跟踪现在远程跟踪分支的本地分支,而不是像原帖作者请求的那样通过快速前进来使它们保持最新。

那么 "git branch --prune --remote=<upstream>" 怎么样?它遍历本地分支,如果

(1) 它不是当前分支;并且
(2) 它标记为跟踪从 <upstream> 取出的某个分支;并且
(3) 它自己没有任何提交;

那么移除该分支呢?"git remote --prune-local-forks <upstream>" 也可以;我不太在意哪个命令实现了这个功能。请注意,截至Git 2.10,不存在这样的解决方案。请注意,git remote prune子命令和git fetch --prune是用于删除远程不存在的分支的远程跟踪分支,而不是用于删除跟踪远程跟踪分支的本地分支(对于其上游分支是远程跟踪分支的情况)。

不要只发布链接,请发布实际内容,并使用链接作为参考。那个链接现在已经失效了,太糟糕了,看起来很有前途。(我意识到这个答案是来自2009年,所以这只是以后参考的一条注记。) - michael
谢谢(哇,这么多年后回应得好快)。我现在明白这个帖子是“呼吁”一个简单的解决方案,而不是我最初误读的“提供”一个简单的解决方案。 - michael
@michael_n:扩展...嗯,现在我看到这篇帖子并不完全是关于所请求的解决方案,而是关于问题本身(假设是XY问题的情况)。 - Jakub Narębski
嗯,使用分离的头进行查看应该更加容易,特别是它应该显示有用的状态信息,并允许通过一些反馈快速推进工作区(例如拉取的提交)。然后它将成为只读本地分支的替代品。 - eckes
“无本地dev”工作流程的更详细说明:如何在没有任何分支的本地副本的情况下处理自己的分支 - idbrii

10

这里有很多可接受的答案,但对于初学者来说,其中一些技术细节可能比较晦涩难懂。以下是一个更简单的示例,可以轻松地进行定制:

$ cat ~/bin/git/git-update-all
#!/bin/bash
# Update all local branches, checking out each branch in succession.
# Eventually returns to the original branch. Use "-n" for dry-run.
git_update_all() {
  local run br
  br=$(git name-rev --name-only HEAD 2>/dev/null)
  [ "$1" = "-n" ] && shift && run=echo

  for x in $( git branch | cut -c3- ) ; do
     $run git checkout $x && $run git pull --ff-only || return 2
  done

  [ ${#br} -gt 0 ] && $run git checkout "$br"
}

git_update_all "$@"

如果你将~/bin/git加入你的PATH(假设文件是~/bin/git/git-update-all),你只需要运行以下命令:

$ git update-all

1
谢谢,我在Windows下尝试了Git-Bash,但是git抱怨说“git:'update-all'不是一个git命令。请参阅'git --help'。”有人知道在Git-Bash下注册扩展的位置吗? - mrswadge
根据https://www.atlassian.com/git/articles/extending-git,任何路径都可以。我创建了~/bin并将其复制到那里,现在它似乎可以工作了,所以谢谢 :) - mrswadge

7

我遇到了这个问题的相同情况...

对此我感到很好奇,于是在我的 .bashrc 文件中编写了一个小的别名函数:

gitPullAll() {
    for branch in `git branch | sed -E 's/^\*/ /' | awk '{print $1}'`; do
        git checkout $branch
        git pull -p
        printf "\n"
    done
    echo "Done"
}

对我很有用(:)

7

以下是一份好的答案:如何获取所有git分支

for remote in `git branch -r`; do git branch --track $remote; done
git pull --all

为什么建议使用 git fetchgit pull,而不是只用 git pull - syntagma
谢谢。看起来 pull 命令会从所有远程仓库获取所有分支。我已经修改了它。 - milkovsky
9
这将获取所有远程分支,但只会合并当前分支。如果您有10个远程分支,您需要手动检出每个分支并进行合并。 - mpoisot
1
执行此操作将使用 origin/ 前缀在本地创建所有远程分支。 - Yassine ElBadaoui

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