可以“git pull –all”更新我所有的本地分支机构吗?


Can “git pull --all” update all my local branches?

我通常至少有3个远程分支:主分支、分段分支和生产分支。我有3个本地分支跟踪这些远程分支。

更新我所有的本地分支非常繁琐:

1
2
3
4
5
6
git fetch --all
git rebase origin/master
git checkout staging
git rebase origin/staging
git checkout production
git rebase origin/production

我很想能做一个"吉特拉全部",但我还没能让它工作。它似乎执行"全部获取",然后更新(快进或合并)当前工作分支,但不更新其他本地分支。

我仍然坚持手动切换到每个本地分支并进行更新。


我使用hub的sync子命令来实现自动化。我的.bash_profile中有alias git=hub,所以我输入的命令是:

1
git sync

这将更新具有匹配上游分支的所有本地分支。从手册页:

  • If the local branch is outdated, fast-forward it;
  • If the local branch contains unpushed work, warn about it;
  • If the branch seems merged and its upstream branch was deleted, delete it.

它还处理当前分支上的隐藏/取消隐藏未提交的更改。

我曾经使用过一个类似的工具,叫做git up,但是它不再被维护,并且git sync做了几乎完全相同的事情。


您为pull --all描述的行为与预期完全相同,但不一定有用。该选项被传递给git fetch,然后从所有远程设备获取所有引用,而不仅仅是所需的引用;pull然后合并(或者在您的情况下,重新设置)相应的单个分支。

如果你想签出其他分行,你就必须签出它们。是的,合并(和重新平衡)绝对需要一个工作树,所以如果不签出其他分支,就无法完成它们。如果您愿意的话,您可以将所描述的步骤总结成脚本/别名,不过我建议您将命令与&&结合起来,这样一来,如果其中一个命令失败,它就不会继续工作。


我知道这个问题已经快3岁了,但我问自己同样的问题,没有找到任何现成的解决方案。所以,我自己创建了一个自定义的git命令shell脚本。

在这里,git-ffwd-update脚本执行以下操作…

  • 它发出一个git remote update来获取最新的版本。
  • 然后使用git remote show获取跟踪远程分支的本地分支的列表(例如,可以与git pull一起使用的分支)
  • 然后,它与git rev-list --count ..检查本地分支在远程服务器后面有多少提交(反之亦然)
  • 如果本地分支机构提前提交了一个或多个提交,则不能快速转发,需要手动合并或重新调整
  • 如果本地分支机构提前提交0个,而后面提交1个或多个,则可以由git branch -f -t 快速转发。
  • 脚本的调用方式如下:

    1
    2
    3
    4
    5
    $ 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上。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    #!/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
    2
    3
    4
    5
    6
    7
    8
    9
    #!/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


    这仍然不是自动的,因为我希望有一个选项-和应该有一些检查,以确保这只能发生在快进更新(这就是为什么手动拉是更安全的!)!),但除此之外,您还可以:

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

    更新本地分支机构的职位而不必签出它。

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


    这个问题还没有解决,至少不容易/不需要脚本:请参阅Junio C Hamano在Git邮件列表上的这篇文章,解释情况并提供一个简单的解决方案。

    主要的理由是你不应该需要这个:

    With git that is not ancient (i.e. v1.5.0 or newer), there is no reason to
    have local"dev" that purely track the remote anymore. If you only want
    to go-look-and-see, you can check out the remote tracking branch directly
    on a detached HEAD with"git checkout origin/dev".

    Which means that the only cases we need to make it convenient for users
    are to handle these local branches that"track" remote ones when you do
    have local changes, or when you plan to have some.

    If you do have local changes on"dev" that is marked to track the remove
    "dev", and if you are on a branch different from"dev", then we should not
    do anything after"git fetch" updates the remote tracking"dev". It
    won't fast forward anyway

    对解决方案的要求是使用一个选项或外部脚本来修剪跟随现在的远程跟踪分支的本地分支,而不是像最初的海报所要求的那样通过快速转发使它们保持最新。

    So how about"git branch --prune --remote=" that iterates over
    local branches, and if

    (1) it is not the current branch; and
    (2) it is marked to track some branch taken from the ; and
    (3) it does not have any commits on its own;

    then remove that branch? "git remote --prune-local-forks " is
    also fine; I do not care about which command implements the feature that
    much.

    注:自Git 2.10起,不存在此类解决方案。注意,git remote prune子命令和git fetch --prune是关于删除远程上不再存在的分支的远程跟踪分支,而不是关于删除跟踪远程跟踪分支的本地分支(远程跟踪分支是上游分支)。


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

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

    • 不需要签出分支
    • 仅当分支可以快速转发时才更新分支
    • 无法快速前进时将报告

    这里是:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    #!/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的手册页:

    1
    2
    3
    4
    5
    6
    7
       <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 :(没有任何+),我们得到一个仅在本地引用可以快速转发时才更新的fetch。

    注意:这假定本地和远程分支的名称相同(并且您希望跟踪所有分支),它应该真正使用有关您拥有哪些本地分支以及要跟踪哪些本地分支的信息。


    这里有很多可以接受的答案,但有些管道可能对未经培训的人来说有点不透明。下面是一个更简单的例子,可以很容易地定制:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    $ 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文件),则可以运行:

    1
    $ git update-all

    这里有一个很好的答案:如何获取所有git分支

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


    将此脚本添加到Mac OS X上的.profile中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    # Usage:
    #   `git-pull-all` to pull all your local branches from origin
    #   `git-pull-all remote` to pull all your local branches from a named remote

    function git-pull-all() {
        START=$(git symbolic-ref --short -q HEAD);
        for branch in $(git branch | sed 's/^.//'); do
            git checkout $branch;
            git pull ${1:-origin} $branch || break;
        done;
        git checkout $START;
    };

    function git-push-all() {
        git push --all ${1:-origin};
    };


    我为我的Gitbash写的剧本。完成以下任务:

    • 默认情况下,为所有设置为跟踪原点的分支从原点提取,允许您根据需要指定不同的远程。
    • 如果当前分支处于脏状态,那么它将保存您的更改,并在最后尝试恢复这些更改。
    • 对于为跟踪远程分支而设置的每个本地分支,将:
      • 埃多克斯1〔14〕
      • 埃多克斯1〔15〕
    • 最后,将返回到原始分支并恢复状态。

    **我使用此产品,但未进行彻底测试,使用风险自负。请在.bash_别名文件中查看此脚本的示例。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
        # Do a pull on all branches that are tracking a remote branches, will from origin by default.
        # If current branch is dirty, will stash changes and reply after pull.
        # Usage: pullall [remoteName]
        alias pullall=pullAll
        function pullAll (){
         # if -h then show help
         if [[ $1 == '-h' ]]
        then
          echo"Description: Pulls new changes from upstream on all branches that are tracking remotes."
          echo
          echo"Usage:"
          echo"- Default: pullall"
          echo"- Specify upstream to pull from: pullall [upstreamName]"
          echo"- Help: pull-all -h"
        else

         # default remote to origin
         remote="origin"
         if [ $1 !="" ]
         then
           remote=$1
         fi

         # list all branches that are tracking remote
         # git branch -vv : list branches with their upstreams
         # grep origin : keep only items that have upstream of origin
         # sed"s/^.."... : remove leading *
         # sed"s/^"..... : remove leading white spaces
         # cut -d""..... : cut on spaces, take first item
         # cut -d splits on space, -f1 grabs first item
         branches=($(git branch -vv | grep $remote | sed"s/^[ *]*//" | sed"s/^[ /t]*//" | cut -d"" -f1))

         # get starting branch name
         startingBranch=$(git rev-parse --abbrev-ref HEAD)

         # get starting stash size
         startingStashSize=$(git stash list | wc -l)

         echo"Saving starting branch state: $startingBranch"
         git stash

         # get the new stash size
         newStashSize=$(git stash list | wc -l)

         # for each branch in the array of remote tracking branches
         for branch in ${branches[*]}
         do
           echo"Switching to $branch"
           git checkout $branch

           echo"Pulling $remote"
           git pull $remote

         done

         echo"Switching back to $startingBranch"
         git checkout $startingBranch

         # compare before and after stash size to see if anything was stashed
         if ["$startingStashSize" -lt"$newStashSize" ]
         then
           echo"Restoring branch state"
           git stash pop
         fi
        fi
        }


    如果您在Windows上,可以使用pygitup,它是用于python的git-up的克隆。您可以使用带pip install --user git-up的PIP或通过使用scoop install git-up的勺安装它。

    [MGXY1(0]


    只是发布一个更新的答案。git-up不再维护,如果您阅读文档,它们会提到现在Git中提供的功能。

    As of Git 2.9, git pull --rebase --autostash does basically the same thing.

    Accordingly, if you update to Git 2.9 or later, you can use this alias instead of installing git-up:

    git config --global alias.up 'pull --rebase --autostash'

    你也可以为每台2.9吉特的git pull设置这个(谢谢@vonc,请在这里看到他的答案)

    1
    2
    git config --global pull.rebase true
    git config --global rebase.autoStash true


    要完成Matt Connolly的回答,这是一种更新本地分支引用的更安全的方法,可以快速转发这些引用,而无需签出分支。它不会更新无法快速转发的分支(即已分叉的分支),也不会更新当前签出的分支(因为这样工作副本也应该更新)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    git fetch

    head="$(git symbolic-ref HEAD)"
    git for-each-ref --format="%(refname) %(upstream)" refs/heads | while read ref up; do
        if [ -n"$up" -a"$ref" !="$head" ]; then
            mine="$(git rev-parse"$ref")"
            theirs="$(git rev-parse"$up")"
            base="$(git merge-base"$ref""$up")"
            if ["$mine" !="$theirs" -a"$mine" =="$base" ]; then
                git update-ref"$ref""$theirs"
            fi
        fi
    done


    如果refs/heads/master可以快速转发到refs/remotes/foo/master,则

    1
    git merge-base refs/heads/master refs/remotes/foo/master

    应返回引用/heads/master指向的sha1 id。通过这一点,您可以组合一个脚本,自动更新所有没有应用转移提交的本地分支。

    这个小小的shell脚本(我称之为git can ff)说明了它是如何完成的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    #!/bin/sh

    set -x

    usage() {
        echo"usage: $(basename $0) <from-ref> <to-ref>">&2
        exit 2
    }

    [ $# -ne 2 ] && usage

    FROM_REF=$1
    TO_REF=$2

    FROM_HASH=$(git show-ref --hash $FROM_REF)
    TO_HASH=$(git show-ref --hash $TO_REF)
    BASE_HASH=$(git merge-base $FROM_REF $TO_REF)

    if ["$BASE_HASH" ="$FROM_HASH" -o \
        "$BASE_HASH" ="$FROM_REF" ]; then
        exit 0
    else
        exit 1
    fi


    我也遇到了这个问题…

    出于对它的好奇,我在我的.bashrc文件中做了一个小的别名函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    gitPullAll() {
        for branch in `git branch | sed -E 's/^\*/ /' | awk '{print $1}'`; do
            git checkout $branch
            git pull -p
            printf"
    "
        done
        echo"Done"
    }

    为我工作(:


    一个稍有不同的脚本,它只对名称与上游分支匹配的分支进行快速转发。如果可能,它还将更新当前分支。

    通过运行git branch -vv确保所有分支的上游分支设置正确。用git branch -u origin/yourbanchname设置上游分支

    复制粘贴到文件中,chmod 755:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    #!/bin/sh

    curbranch=$(git rev-parse --abbrev-ref HEAD)

    for branch in $(git for-each-ref refs/heads --format="%(refname:short)"); do
            upbranch=$(git config --get branch.$branch.merge | sed 's:refs/heads/::');
            if ["$branch" ="$upbranch" ]; then
                    if ["$branch" ="$curbranch" ]; then
                            echo Fast forwarding current branch $curbranch
                            git merge --ff-only origin/$upbranch
                    else
                            echo Fast forwarding $branch with origin/$upbranch
                            git fetch . origin/$upbranch:$branch
                    fi
            fi
    done;


    看起来很多其他人都提供了类似的解决方案,但我想我会分享我的想法,并邀请其他人提供帮助。这个解决方案有一个漂亮的彩色输出,可以很好地处理当前的工作目录,而且速度很快,因为它不执行任何签出操作,并在tact中离开工作目录。另外,它只是一个没有依赖性的shell脚本,而不是git。(目前仅在OSX上测试)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    #!/usr/bin/env bash

    gitup(){    
    RED='\033[33;31m'
    YELLO='\033[33;33m'
    GREEN='\033[33;32m'
    NC='\033[0m' # No Color

    HEAD=$(git rev-parse HEAD)
    CHANGED=$(git status --porcelain | wc -l)

    echo"Fetching..."
    git fetch --all --prune &>/dev/null
    for branch in `git for-each-ref --format='%(refname:short)' refs/heads`; do

        LOCAL=$(git rev-parse --quiet --verify $branch)
        if ["$HEAD" ="$LOCAL" ] && [ $CHANGED -gt 0 ]; then
            echo -e"${YELLO}WORKING${NC}\t\t$branch"
        elif git rev-parse --verify --quiet $branch@{u}&>/dev/null; then
            REMOTE=$(git rev-parse --quiet --verify $branch@{u})
            BASE=$(git merge-base $branch $branch@{u})

            if ["$LOCAL" ="$REMOTE" ]; then
               echo -e"${GREEN}OK${NC}\t\t$branch"
            elif ["$LOCAL" ="$BASE" ]; then
                if ["$HEAD" ="$LOCAL" ]; then
                    git merge $REMOTE&>/dev/null
                else
                    git branch -f $branch $REMOTE
                fi
                echo -e"${GREEN}UPDATED${NC}\t\t$branch"
            elif ["$REMOTE" ="$BASE" ]; then
                echo -e"${RED}AHEAD${NC}\t\t$branch"
            else
                echo -e"${RED}DIVERGED${NC}\t\t$branch"
            fi
        else
            echo -e"${RED}NO REMOTE${NC}\t$branch"
        fi
    done
    }

    https://github.com/davestimpert/gitup

    抱歉,我似乎也想到了与上面的其他工具相同的名称。


    可以用下面的脚本来完成…它将首先获取所有分支并逐个签出并自行更新。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #!/bin/bash
    git branch -r | grep -v '\->' | while read remote; do git branch --track
    "${remote#origin/}""$remote"; done

    set -x
    CURRENT=`git rev-parse --abbrev-ref HEAD`
    git fetch --all
    branch_name=$(git branch | awk '{print $1""}' | grep -v '*' | xargs)
    for branch in $branch_name; do
       git checkout"$branch" || exit 1
       git rebase"origin/$branch" || exit 1
       git pull origin $branch|| exit 1
    done
    git checkout"$CURRENT" || exit 1
    git pull || exit 1


    @larsmans的脚本有点改进:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #!/bin/sh

    set -x
    CURRENT=`git rev-parse --abbrev-ref HEAD`
    git fetch --all
    for branch in"$@"; do
      if ["$branch" -ne"$CURRENT"]; then
        git checkout"$branch" || exit 1
        git rebase"origin/$branch" || exit 1
      fi
    done
    git checkout"$CURRENT" || exit 1
    git rebase"origin/$CURRENT" || exit 1

    完成后,工作副本将从调用脚本之前的同一分支签出。

    git pull版本:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #!/bin/sh

    set -x
    CURRENT=`git rev-parse --abbrev-ref HEAD`
    git fetch --all
    for branch in"$@"; do
      if ["$branch" -ne"$CURRENT"]; then
        git checkout"$branch" || exit 1
        git pull || exit 1
      fi
    done
    git checkout"$CURRENT" || exit 1
    git pull || exit 1


    如果可能,下面的一行程序将快速转发具有上游分支的所有分支,否则将打印错误:

    1
    2
    3
    git branch \
      --format"%(if)%(upstream:short)%(then)git push . %(upstream:short):%(refname:short)%(end)" |
      sh

    号它是如何工作的?

    它使用带有git branch命令的自定义格式。对于每个具有上游分支的分支,它将按以下模式打印一条线:

    1
    git push . <remote-ref>:<branch>

    这可以直接通过管道传输到sh(假设分支名称的格式良好)。省略| sh,看看它在做什么。

    注意事项

    一行程序不会联系到你的遥控器。在运行之前,发出git fetchgit fetch --all

    当前已签出的分支将不会用以下消息更新

    1
    ! [remote rejected] origin/master -> master (branch is currently checked out)

    为此,您可以使用常规的git pull --ff-only

    别名

    将以下内容添加到您的.gitconfig中,以便git fft执行此命令:

    1
    2
    [alias]
            fft = !sh -c 'git branch --format "%(if)%(upstream:short)%(then)git push . %(upstream:short):%(refname:short)%(end)" | sh' -

    另见我的.gitconfig。别名是"快进跟踪(分支)"的简写。


    从Git 2.9开始:

    埃多克斯1〔19〕

    请参阅https://git-scm.com/docs/git-rebase

    Automatically create a temporary stash before the operation begins,
    and apply it after the operation ends. This means that you can run
    rebase on a dirty worktree. However, use with care: the final stash
    application after a successful rebase might result in non-trivial
    conflicts.