Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I easily update all local git branches from remote branches simultaneously? [duplicate]

Tags:

git

I often have at least 3 remote branches: master, staging and production. I have 3 local branches that track those remote branches.

Updating all my local branches is tedious:

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

I'd love to be able to just do a "git pull -all", but I haven't been able to get it to work. It seems to do a "fetch --all", then updates (fast forward or merges) the current working branch, but not the other local branches.

I'm still stuck manually switching to each local branch and updating.

like image 907
mpoisot Avatar asked Nov 30 '10 20:11

mpoisot


People also ask

Does git pull update all local branches?

git fetch. On its own, git fetch updates all the remote tracking branches in local repository. No changes are actually reflected on any of the local working branches.

How do two git branches work simultaneously?

Git offers a feature referred to as a worktree, and what it does is allow you to have multiple branches running at the same time. It does this by creating a new directory for you with a copy of your git repository that is synced between the two directories where they are stored.

How do I clone a repository with all branches?

Use the git clone Command to Clone All Branches in Git Clone your repository with the git clone command. Then navigate to the directory where your project is located. Use the git branch command to view local branches. This command will only show you local branches.


12 Answers

I use the sync subcommand of hub to automate this. I have alias git=hub in my .bash_profile, so the command I type is:

git sync

This updates all local branches that have a matching upstream branch. From the man page:

  • 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.

It also handles stashing/unstashing uncommitted changes on the current branch.

I used to use a similar tool called git-up, but it's no longer maintained, and git sync does almost exactly the same thing.

like image 63
John Avatar answered Sep 29 '22 19:09

John


The behavior you describe for pull --all is exactly as expected, though not necessarily useful. The option is passed along to git fetch, which then fetches all refs from all remotes, instead of just the needed one; pull then merges (or in your case, rebases) the appropriate single branch.

If you want to check out other branches, you're going to have to check them out. And yes, merging (and rebasing) absolutely require a work tree, so they cannot be done without checking out the other branches. You could wrap up your described steps into a script/alias if you like, though I'd suggest joining the commands with && so that should one of them fail, it won't try to plow on.

like image 26
Cascabel Avatar answered Sep 29 '22 20:09

Cascabel


I know this question is almost 3 years old, but I asked myself the very same question and did not found any ready made solution. So, I created a custom git command shell script my self.

Here it goes, the git-ffwd-update script does the following...

  1. it issues a git remote update to fetch the lates revs
  2. then uses git remote show to get a list of local branches that track a remote branch (e.g. branches that can be used with git pull)
  3. then it checks with git rev-list --count <REMOTE_BRANCH>..<LOCAL_BRANCH> how many commit the local branch is behind the remote (and ahead vice versa)
  4. if the local branch is 1 or more commits ahead, it can NOT be fast-forwarded and needs to be merged or rebased by hand
  5. if the local branch is 0 commits ahead and 1 or more commits behind, it can be fast-forwarded by git branch -f <LOCAL_BRANCH> -t <REMOTE_BRANCH>

the script can be called like:

$ 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

The full script, should be saved as git-ffwd-update and needs to be on the 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 $@
like image 34
muhqu Avatar answered Sep 29 '22 19:09

muhqu


It's not so hard to automate:

#!/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
like image 20
Fred Foo Avatar answered Sep 29 '22 21:09

Fred Foo


This still isn't automatic, as I wish there was an option for - and there should be some checking to make sure that this can only happen for fast-forward updates (which is why manually doing a pull is far safer!!), but caveats aside you can:

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

to update the position of your local branch without having to check it out.

Note: you will be losing your current branch position and moving it to where the origin's branch is, which means that if you need to merge you will lose data!

like image 37
Matt Connolly Avatar answered Sep 29 '22 21:09

Matt Connolly


There are a lot of answers here but none that use git-fetch to update the local ref directly, which is a lot simpler than checking out branches, and safer than git-update-ref.

Here we use git-fetch to update non-current branches and git pull --ff-only for the current branch. It:

  • Doesn't require checking out branches
  • Updates branches only if they can be fast-forwarded
  • Will report when it can't fast-forward

and here it is:

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

From the manpage for 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.

By specifying git fetch <remote> <ref>:<ref> (without any +) we get a fetch that updates the local ref only when it can be fast-forwarded.

Note: this assumes the local and remote branches are named the same (and that you want to track all branches), it should really use information about which local branches you have and what they are set up to track.

like image 29
quornian Avatar answered Sep 29 '22 21:09

quornian


This issue is not solved (yet), at least not easily / without scripting: see this post on git mailing list by Junio C Hamano explaining situation and providing call for a simple solution.

The major reasoning is that you shouldn't need this:

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

The call for a solution was for an option or external script to prune local branches that follow now remote-tracking branches, rather than to keep them up-to-date by fast-forwarding, like original poster requested.

So how about "git branch --prune --remote=<upstream>" 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 <upstream>; and
(3) it does not have any commits on its own;

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

Note: as of git 2.10 no such solution exists. Note that the git remote prune subcommand, and git fetch --prune are about removing remote-tracking branch for branch that no longer exists on remote, not about removing local branch that tracks remote-tracking branch (for which remote-tracking branch is upstream branch).

like image 31
3 revs Avatar answered Sep 29 '22 19:09

3 revs


There are plenty of acceptable answers here, but some of the plumbing may be be a little opaque to the uninitiated. Here's a much simpler example that can easily be customized:

$ 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 "$@"

If you add ~/bin/git to your PATH (assuming the file is ~/bin/git/git-update-all), you can just run:

$ git update-all
like image 24
michael Avatar answered Sep 29 '22 20:09

michael


I came across the same issue of this question...

Wondering myself about it, I did a small alias function inside my .bashrc file:

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

Worked for me (:

like image 25
Francisco Neto Avatar answered Sep 29 '22 20:09

Francisco Neto


Add this script to .profile on Mac OS X:

# 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};
};
like image 28
Mike Avatar answered Sep 29 '22 19:09

Mike


Here is a good answer: How to fetch all git branches

for remote in `git branch -r`; do git branch --track $remote; done
git pull --all
like image 37
milkovsky Avatar answered Sep 29 '22 19:09

milkovsky


A script I wrote for my GitBash. Accomplishes the following:

  • By default pulls from origin for all branches that are setup to track origin, allows you to specify a different remote if desired.
  • If your current branch is in a dirty state then it stashes your changes and will attempt to restore these changes at the end.
  • For each local branch that is set up to track a remote branch will:
    • git checkout branch
    • git pull origin
  • Finally, will return you to your original branch and restore state.

** I use this but have not tested thoroughly, use at own risk. See an example of this script in a .bash_alias file here.

    # 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
    }
like image 26
philosowaffle Avatar answered Sep 29 '22 20:09

philosowaffle