How to iterate through all git branches using bash script

Viewed 54856

How can I iterate through all the local branches in my repository using bash script. I need to iterate and check is there any difference between the branch and some remote branches. Ex

for branch in $(git branch); 
do
    git log --oneline $branch ^remotes/origin/master;
done

I need to do something like given above, but the issue I'm facing is $(git branch) gives me the folders inside the repository folder along with the branches present in the repository.

Is this the correct way to solve this issue? Or is there another way to do it?

Thank you

17 Answers

The bash builtin, mapfile, is built for this

all git branches: git branch --all --format='%(refname:short)'

all local git branches: git branch --format='%(refname:short)'

all remote git branches: git branch --remotes --format='%(refname:short)'

iterate through all git branches: mapfile -t -C my_callback -c 1 < <( get_branches )

example:

my_callback () {
  INDEX=${1}
  BRANCH=${2}
  echo "${INDEX} ${BRANCH}"
}
get_branches () {
  git branch --all --format='%(refname:short)'
}
# mapfile -t -C my_callback -c 1 BRANCHES < <( get_branches ) # if you want the branches that were sent to mapfile in a new array as well
# echo "${BRANCHES[@]}"
mapfile -t -C my_callback -c 1 < <( get_branches )

for the OP's specific situation:

#!/usr/bin/env bash


_map () {
  ARRAY=${1?}
  CALLBACK=${2?}
  mapfile -t -C "${CALLBACK}" -c 1 <<< "${ARRAY[@]}"
}


get_history_differences () {
  REF1=${1?}
  REF2=${2?}
  shift
  shift
  git log --oneline "${REF1}" ^"${REF2}" "${@}"
}


has_different_history () {
  REF1=${1?}
  REF2=${2?}
  HIST_DIFF=$( get_history_differences "${REF1}" "${REF2}" )
  return $( test -n "${HIST_DIFF}" )
}


print_different_branches () {
  read -r -a ARGS <<< "${@}"
  LOCAL=${ARGS[-1]?}
  for REMOTE in "${SOME_REMOTE_BRANCHES[@]}"; do
    if has_different_history "${LOCAL}" "${REMOTE}"; then
      # { echo; echo; get_history_differences "${LOCAL}" "${REMOTE}" --color=always; } # show differences
      echo local branch "${LOCAL}" is different than remote branch "${REMOTE}";
    fi
  done
}


get_local_branches () {
  git branch --format='%(refname:short)'
}


get_different_branches () {
  _map "$( get_local_branches )" print_different_branches
}


# read -r -a SOME_REMOTE_BRANCHES <<< "${@}" # use this instead for command line input
declare -a SOME_REMOTE_BRANCHES
SOME_REMOTE_BRANCHES=( origin/master remotes/origin/another-branch another-remote/another-interesting-branch )
DIFFERENT_BRANCHES=$( get_different_branches )

echo "${DIFFERENT_BRANCHES}"

source: List all local git branches without an asterisk

for branch in $(git for-each-ref --format='%(refname:short)' refs/heads); do
    ...
done

This uses git plumbing commands, which are designed for scripting. It's also simple and standard.

Reference: Git's Bash completion

Keep it simple

The simple way of getting branch name in loop using bash script.

#!/bin/bash

for branch in $(git for-each-ref --format='%(refname)' refs/heads/); do
    echo "${branch/'refs/heads/'/''}" 
done

Output:

master
other

What I ended up doing, applied to your question (& inspired by ccpizza mentioning tr):

git branch | tr -d ' *' | while IFS='' read -r line; do git log --oneline "$line" ^remotes/origin/master; done

(I use while loops a lot. While for particular things you'd definitely want to use a pointed variable name ["branch", for example], most of the time I am only concerned with doing something with each line of input. Using 'line' here instead of 'branch' is a nod to reusability/muscle memory/efficiency.)

Googlian's answer, but without using for

git for-each-ref --format='%(refname:lstrip=-1)' refs/heads/

If you're at this state:

git branch -a

* master

  remotes/origin/HEAD -> origin/master

  remotes/origin/branch1

  remotes/origin/branch2

  remotes/origin/branch3

  remotes/origin/master

And you run this code:

git branch -a | grep remotes/origin/*

for BRANCH in `git branch -a | grep remotes/origin/*` ;

do
    A="$(cut -d'/' -f3 <<<"$BRANCH")"
    echo $A

done        

You'll get this result:

branch1

branch2

branch3

master

The correct way to simply iterate over local branch names is to use for-each-ref over refs/heads/. For example:

for branch in $(git for-each-ref --format='%(refname:short)' refs/heads/); do
  echo branch="${branch}"
done

This works with standard/default values for IFS because spaces are illegal in branch names in git.

Extending on from @finn's answer (thank you!), the following will let you iterate over the branches without creating an intervening shell script. It's robust enough, as long as there's no newlines in the branch name :)

git for-each-ref --format='%(refname)' refs/heads  | while read x ; do echo === $x === ; done

The while loop runs in a subshell, which is usually fine unless you're setting shell variables that you want to access in the current shell. In that case you use process substitution to reverse the pipe:

while read x ; do echo === $x === ; done < <( git for-each-ref --format='%(refname)' refs/heads )

List heads (branches) in the local repository

git show-ref --heads

This will list the heads something like

682e47c01dc8d0f4e4102f183190a48aaf34a3f0 refs/heads/main
....

so if you're only interested in the name, you can use something like sed to obtain the output you want

git show-ref --heads | sed 's/.*refs\/heads\///'

Iterate through the branches

With this output you can easily iterate through it, say using a bash loop, xargs, whatever floats your boat

for SHA in $(git show-ref --heads | awk '{ print $1 }'); do
 echo "magic! $SHA"
done
  • git show-ref --heads get the branches as per above
  • awk '{ print $1 }' obtain the SHA
  • echo "magic! $SHA" <- this is where you would do your magic
#/bin/bash
for branch in $(git branch -r); 
do
    echo $branch
done

Of course in theory one should use a special interface that Git indeed has for use when scripting. But often you want something simpler — handy for a oneliner. Something that doesn't urge you remember stuff like git for-each-ref --format … refs … amen. And it's UNIX anyways finally. Then it goes like that:

  1. There's an utility widely known for its obscure but a terse way to print the last column.
  2. git branch puts asterisk before the branch name. Meaning we're seemingly always interested in the last column exactly.

Resulting:

git branch | awk '{print $NF}'
Related