Reputation: 16065
I'm working on a Bash script that does some Git repository maintenance. Some of repositories use 'master'
as the main branch while others use 'main'
. What Git command can I use to return the first branch of these which exists?
P.S. I want to check local branches of a repo, since there will always be a local 'master'
or 'main'
branch.
Upvotes: 14
Views: 5170
Reputation: 15246
What Git command can I use to return the first branch of these which exists?
git branch -r
returns a list of remotes.
grep origin/ma
will match main
and master
.
If you need to be pickier, use grep -E 'origin/(main|master)'
If you want to be a little more careful, especially for any automation -
for n in main master; do
if [[ -n "$(git branch -r -l "origin/$n")" ]]; then
git checkout "$n" && break # keep the first success
fi
done
Thanks to xmedeko for asking a question that made me look again.
Original bad code is further below, but first...
To answer xmedeko's question in the comments:
What about git branch -r | grep 'origin/HEAD' ?
This is usually a quick, simple, and effective way to find out, but has a few pitfalls. For one, it isn't guaranteed to be true.
$: git remote set-head origin test
$: git branch -r | grep 'origin/HEAD'
origin/HEAD -> origin/test
In this case I think the OP specifically wants whatever the default branch is, but I work with some teams who make develop
the default branch from which they copy to make new feature branches...
For manual checks it's fine, but if you're automating you still have to check that it IS the default, and then convert to whichever you found it on or this is likely to happen, which might not be what you want -
$: git checkout origin/HEAD
Note: switching to 'origin/HEAD'.
You are in 'detached HEAD' state. You can look around, make
experimental changes and commit them, and you can discard any
commits you make in this state without impacting any branches
by switching back to a branch.
If you want to create a new branch to retain commits you create,
you may do so (now or later) by using -c with the switch command.
Example:
git switch -c <new-branch-name>
Or undo this operation with:
git switch -
Turn off this advice by setting config variable advice.detachedHead to false
HEAD is now at 28a1f02 Update some-file
Easy enough to switch with something like git branch -r|grep HEAD|sed 's,^.* origin/,,'
if you know it's one or the other...
Assigning the list returned to an array should work,
I had a warning about spaces, but git branch names can't have spaces. More importantly, I had this code, which doesn't work.
b=( $(git branch -r | grep origin/ma ) ) # all matches
git checkout "${b[0]##*/}" # first hit
That checkout
is just wrong.
$: git branch -r | grep origin/ma
origin/HEAD -> origin/main
origin/main
origin/master
$: b=( $(git branch -r | grep origin/ma ) ) # all matches
$: printf '[%s] ' "${b[@]}"; echo
[origin/HEAD] [->] [origin/main] [origin/main] [origin/master]
$: echo "${b[0]}"
origin/HEAD
$: echo "${b[0]##*/}"
HEAD
Doing a checkout of just HEAD is just going to grab whichever local branch you are already on.
$: for b in test main master; do git checkout $b; git checkout HEAD; done
Switched to branch 'test'
Your branch is up to date with 'origin/test'.
Your branch is up to date with 'origin/test'.
Switched to branch 'main'
Your branch is up to date with 'origin/main'.
Your branch is up to date with 'origin/main'.
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
Your branch is up to date with 'origin/master'.
I considered suggesting
$: git remote set-head origin develop # for when this is true
$: git branch -r | grep -Eo 'origin/ma(in|ster)$'
origin/main
origin/master
This will give you whichever exists, or likely main
if there's both, as it sorts above.
If HEAD
is in fact either of these, then it solves even that.
$: git remote set-head origin master
$: git branch -r | grep -Eo 'origin/ma(in|ster)$'
origin/master
origin/main
origin/master
$: b=( $( git branch -r | grep -Eo 'origin/ma(in|ster)$' ) )
$: echo "${b[0]}"
origin/master
But I think I'd either use the loop above, or one of these:
First, a multiple grep
and head
(this hurts me...)
$: git remote set-head origin test
$: git branch -r | grep -E 'origin/(HEAD|main|master)$' | grep -Eo 'ma(in|ster)$' | head -1
main
$: git remote set-head origin master
$: git branch -r | grep -E 'origin/(HEAD|main|master)$' | grep -Eo 'ma(in|ster)$' | head -1
master
$: git remote set-head origin main
$: git branch -r | grep -E 'origin/(HEAD|main|master)$' | grep -Eo 'ma(in|ster)$' | head -1
main
Or second, the same thing with just one sed
.
$: git remote set-head origin test
$: git branch -r | sed -En '/origin\/ma(in|ster)$/{s/^.* origin\///; p; q;}'
main
$: git remote set-head origin main
$: git branch -r | sed -En '/origin\/ma(in|ster)$/{s/^.* origin\///; p; q;}'
main
$: git remote set-head origin master
$: git branch -r | sed -En '/origin\/ma(in|ster)$/{s/^.* origin\///; p; q;}'
master
$: git checkout $(git branch -r | sed -En '/origin\/ma(in|ster)$/{s/^.* origin\///; p; q;}')
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
Upvotes: 4
Reputation: 149726
To find out which of the two local branches exists, you can use git branch
with the -l
/--list
option:
git branch -l master main # outputs 'master' or 'main', provided only one exists
Git also marks the current branch with an asterisk, so you could add --format
to prevent that:
git branch -l main master --format '%(refname:short)'
Upvotes: 12
Reputation: 60235
I want to check local branches of a repo, since there will always be a local
master
ormain
branch
Taking that as a reliable description of your intended use-case environment, since in general use it's often enough not true,
What Git command can I use to return the first branch of these which exists?
A concise way of doing it is
if git cat-file -e refs/heads/master 2>&-; then echo master
elif git cat-file -e refs/heads/main 2>&-; then echo main
fi
but for anything even a little more elaborate you're likely to want for-each-ref,
{ git for-each-ref --format='%(refname)' refs/{heads,remotes/*}/master;
git for-each-ref --format='%(refname)' refs/{heads,remotes/*}/main;
} | sed -E 's,(.*)/,\1 ,' | sort -usk1,1
which gives the answer for local branches and all tracked remotes without any foofaraw.
In my git
repo this reports refs/remotes/origin master
, for instance, and the Git project keeps both branch names in lockstep. But as someone else pointed out, some projects keep master
around as a record of where it was when they switched away, and as you might have noticed I don't have a master
or main
branch. My make
branch tracks origin/next
, that's the one I run my deployments from, and there's a couple others for little projects.
To do just local branches do
{ git for-each-ref --format='%(refname:short)' refs/heads/master;
git for-each-ref --format='%(refname:short)' refs/heads/main;
} | head -1
See the for-each-ref
examples, it was built for this.
Upvotes: 1
Reputation: 15697
I wanted a script that worked with multiple remotes (origin fork or canonical upstream) and repositories that used main or master. Based on Paul Hodges answer I ended up with:
#Update local/fork default to match remote. Can also delete current local branch using -D (to clean up a finished feature branch)
gum(){
REMOTES=( $(git branch -r | grep -Eo " (origin|upstream)/(master|main)") )
RNAME=${REMOTES[-1]%%/*} # Prefix of last one in the list (prefer fetching from upstream over origin)
RBRANCH=${REMOTES[-1]##*/} # Suffix of last matching remote
LBRANCH="$(git rev-parse --abbrev-ref HEAD)" # Name of current checked out branch
if [[ "$LBRANCH" != "$RBRANCH" && "$1" != "-D" ]]; then
echo "error current branch is '$LBRANCH' not $RBRANCH and -D not specified to delete current branch"
elif [[ ! -z $(git status -s) ]]; then
echo "error pending changes on local branch"
else
# Switch to local default, delete current if requested
if [[ "$LBRANCH" != "$RBRANCH" && "$1" == "-D" ]]; then
git checkout $RBRANCH
git branch -D $LBRANCH
fi
# fetch all and prune, then update local default
git fetch --all -p && git merge $RNAME/$RBRANCH && if [[ "$RNAME" != "origin" ]]; then
git push ${REMOTES[0]%%/*} $RBRANCH # also update default branch on fork
fi
fi
}
Upvotes: 0
Reputation: 487735
To find out what some other Git repository's HEAD
is, use git ls-remote
:
$ git ls-remote --symref origin HEAD
ref: refs/heads/master HEAD
670b81a890388c60b7032a4f5b879f2ece8c4558 HEAD
This assumes both your Git, and the remote's Git, are not so old that they do not support --symref
(it must be supported on both sides). Note that this command can be run outside any repository by using a URL directly, instead of a remote name like origin
.
To find out what branch names exist in some other Git repository, either clone it and inspect the resulting remote-tracking names, or use git ls-remote
. Note that you can specify just refs/heads
to limit the output to branch names only, or omit it entirely to get everything (all branch and tag names and any other names they choose to expose).
Upvotes: 5
Reputation: 36327
Well, "default branch" has no meaning locally; it only makes sense if your repo is a remote to someone else, or in terms of a remote that someone else hosts. (Also, in terms of "what branch do I pull by default when I pull from this local branch, but if you knew that, you wouldn't be asking this.)
git fetch
git branch -r --list 'origin/HEAD' | grep '>'
Upvotes: 3