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

更新我所有的本地分支是乏味的:

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”,然后更新(快进或合并)当前工作的分支,但不包括其他本地分支。

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


当前回答

你不能只使用一个git命令,但是你可以用一个bash行自动化它。

为了用一行安全地更新所有分支,我是这样做的:

git fetch --all && for branch in $(git branch | sed '/*/{$q;h;d};$G' | tr -d '*') ; do git checkout $branch && git merge --ff-only || break ; done

如果它不能快进一个分支或遇到错误,它将停止并将您留在该分支中,以便您可以收回控制权并手动合并。 如果所有分支都可以快进,它将以您当前所在的分支结束,使您停留在更新之前的位置。

解释:

为了更好的可读性,可以将它分成几行:

git fetch --all && \
for branch in $(git branch | sed '/*/{$q;h;d};$G' | tr -d '*')
    do git checkout $branch && \
    git merge --ff-only || break
done

git fetch --all && ... => Fetches all refs from all remotes and continue with the next command if there has been no error. git branch | sed '/*/{$q;h;d};$G' | tr -d '*' => From the output of git branch, sed take the line with a * and move it to the end (so that the current branch will be updated last). Then tr simply remove the *. for branch in $(...) ; do git checkout $branch && git merge --ff-only || break ; done = > For each branch name obtained from the previous command, checkout this branch and try to merge with a fast-forward. If it fails, break is called and the command stops here.

当然,如果你想要的话,你可以用git rebase替换git merge——ff-only。

最后,你可以把它作为一个别名放在bashrc中:

alias git-pull-all='git fetch --all && for branch in $(git branch | sed '\''/*/{$q;h;d};$G'\'' | tr -d "*") ; do git checkout $branch && git merge --ff-only || break ; done'

或者如果你害怕混淆' and ',或者你只是喜欢在编辑器中保持语法的可读性,你可以将它声明为一个函数:

git-pull-all()
{
    git fetch --all && for branch in $(git branch | sed '/*/{$q;h;d};$G' | tr -d '*') ; do git checkout $branch && git merge --ff-only || break ; done
}

奖金:

对于那些想要sed '/*/{$q;h;d};$G'部分的解释:

/*/ =>查找带*的行。 {$q =>如果它在最后一行,退出(我们不需要做任何事情,因为当前分支已经是列表中的最后一个)。 d} =>否则,将该行保存在保持缓冲区中,并将其删除在当前列表位置。 $G =>当它到达最后一行时,追加保持缓冲区的内容。

其他回答

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

git sync

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

如果本地分支已经过时,快进; 如果本地分支包含未推送的工作,请对此发出警告; 如果分支显示为已合并,且上游分支已被删除,则删除该分支。

它还处理在当前分支上存储/解存储未提交的更改。

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

我为我的giitbash写的脚本。完成以下任务:

默认情况下,为跟踪原点而设置的所有分支从原点提取,允许您根据需要指定不同的远程。 如果您的当前分支处于脏状态,那么它将存储您的更改,并在结束时尝试恢复这些更改。 对于每个用于跟踪远程分支的本地分支,将: Git结帐分支 Git拉源 最后,将您返回到原始分支并恢复状态。

**我使用这个,但没有彻底测试,使用自负风险。在.bash_alias文件中可以看到该脚本的示例。

    # 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
    }

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

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

这里有很多答案,但没有一个是使用git-fetch直接更新本地ref,这比检查分支简单得多,也比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的manpage:

   <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>(没有任何+),我们得到一个只在本地ref可以快进时更新它的fetch。

注意,这假设本地分支和远程分支的名称相同(并且您希望跟踪所有分支),它实际上应该使用关于您拥有哪些本地分支以及它们被设置为跟踪的内容的信息。

以上答案都没有考虑存在多个工作树的可能性。使用git update-ref或git branch -f更新当前在其他工作树中签出的分支会产生意想不到的副作用。

考虑一下我处理工作树的解决方案:

#! /usr/bin/env bash
set -euo pipefail

# Read the default remote from config, defaulting to "origin".
DEFAULT_REMOTE=$(git config --default origin --get clone.defaultRemoteName)

# Use first argument as remote name, fallback to default.
REMOTE=${1:-$DEFAULT_REMOTE}

# Resolve the rev that HEAD points at, so that we can give it
# a special treatment.
HEAD_REV=$(git rev-parse HEAD)

# Format that allows us to easily grep for local branches that are behind,
# and have an upstream at $REMOTE.
FORMAT="%(upstream:trackshort)%(upstream:remotename)|%(refname:short)"

# Get a list of revs that are checked out. We don't want to
# update refs that are pointing at them.
set +e
WORKTREE_REVS=$(git worktree list --porcelain | grep -Po "HEAD \K(.+)" | grep -v "$HEAD_REV")
set -e

git fetch $REMOTE

for BRANCH in $(git for-each-ref refs/heads --format="$FORMAT" | grep -Po "<$REMOTE\|\K(.+)")
do
    BRANCH_REV=$(git rev-parse $BRANCH)
    if [ "$BRANCH_REV" = "$HEAD_REV" ]
    then
        # This branch is currently checked out "here". Forward it carefully.
        set +e
        git merge --no-autostash --ff-only $BRANCH@{u}
        set -e
    elif grep -q "$BRANCH_REV" <<< "$WORKTREE_REVS"
    then
        # This branch is currently checked out by another. Leave it alone.
        echo "$BRANCH skipped, because it is checked out in another worktree. Use 'git worktree list' to diagnose."
    else
        # This branch is not checked out. Just update it!
        git update-ref refs/heads/$BRANCH $BRANCH@{u}
        echo "$BRANCH forwarded"
        fi
done