4

I work on several projects with some having main and some master as default branch. I can't rename all the projects with one or other name because not all are managed by me. I know the .gitconfig alias system is very wide so I'm wondering if there is a way to accept both names for every command where there are involved. So far I only found this but it involves modify each individual command and using !sh -c which is not very 'pretty'. The ideal would be to also testing if both branches actually exist in the repo to avoid mistakes.
I tried adding master = main at the beginning of .gitconfig [alias] section but it don't works in either way.

Nonoreve
  • 115
  • 1
  • 11
  • If these are your own checkouts, you can use whatever branch names you like. You can have a local branch named `master` that tracks a remote branch named `main`. When you clone a repository, the initial branch may be named `main`, but you can rename it to `master` (or vice versa). – chepner Jan 17 '21 at 16:57
  • Thanks, this is a good thing to solve local problems. However I would want this to also work for origin/main and origin/master. – Nonoreve Jan 17 '21 at 17:00
  • Does this answer your question? [git - how to get default branch?](https://stackoverflow.com/questions/28666357/git-how-to-get-default-branch) – kelvin Nov 13 '21 at 00:30

5 Answers5

4

To find the default branch of a remote repository, regardless of name, you can look at .git/refs/remote/<remote name>/HEAD.

For instance:

$ cat .git/refs/remotes/origin/HEAD
ref: refs/remotes/origin/master
$ awk -F "/" '{print $NF}' .git/refs/remotes/origin/HEAD
master

Then you can switch to the local branch of the same name:

git switch $(awk -F "/" '{print $NF}' .git/refs/remotes/origin/HEAD)
IMSoP
  • 89,526
  • 13
  • 117
  • 169
  • 1
    In my repo there is no `.git/refs/remotes/origin/HEAD` I'm on git 2.31.1 – cowlicks Jun 23 '21 at 15:47
  • @cowlicks: The remote might be named differently, for example `upstream`. Just as `master`/`main`, the default remote names are only a convention. – Katrin Leinweber Nov 11 '21 at 16:34
  • @cowlicks That may also happen if a remote is added after `git init` (or after cloning the repository). I have one such repository that has multiple remotes, but only `origin` has a `HEAD` file in `.git/refs/remotes`. If that's the case, running `git remote set-head origin --auto` should fix it; see https://stackoverflow.com/a/66895556/10095231 – kelvin Nov 13 '21 at 05:53
  • `sed 's|.*/||' .git/refs/remotes/origin/HEAD` is an alternative to the awk statement – pyrocrasty Dec 24 '21 at 22:46
  • 1
    A version based on git command instead of git files (which will work also in subdirectories): `git rev-parse --abbrev-ref origin/HEAD` (taken from https://stackoverflow.com/a/62397081/ ) – jakub.g Feb 15 '22 at 11:13
2

I don't think there's a practical way to do that. You could approximate that by renaming the branch locally; this doesn't affect the remote or any other user, so it doesn't matter whether it's "your" project to manage.

git checkout master
git branch -m main

But I don't really recommend it. The downside to this (or to your original "alias" idea if you could get it to work) is that you will likely reinforce your lack of knowledge about which repos use which name. For those cases when you need the remote tracking ref name (origin/master or origin/main), and for those cases where you're communicating with other users of the repo, this is likely to increase confusion.

Mark Adelsberger
  • 42,148
  • 4
  • 35
  • 52
  • Yes I see. I didn't think about communication problems – Nonoreve Jan 17 '21 at 17:06
  • "You could approximate that by renaming the branch locally; this doesn't affect the remote or any other user" It could if you ever merge locally (or pull from other branches, as is done on the Linux kernel workflow) and push the result, as the branch name is part of the auto-generated merge commit message. Besides the cited communication problems (of when mentioning the branches I assume), I'd find it rather confusing (and inconsistent) if the history were to sometimes contain merges of non-existent remote branches. – kelvin Nov 12 '21 at 23:17
1

A more robust version of IMSoP's answer is

awk -F "/" '{print $NF}' \
  $(find ./.git/refs/remotes/ -type f -name 'HEAD' | head -1)

It finds the HEAD first, and thus the branch name, regardless of the name of the remote(s, or their number).


Edit: My below, initial answer is more brittle than the above.

testing if both branches actually exist in the repo to avoid mistakes.

Specifically for switching to the default branch, I use this bash function during this… transition period.

gsm () {  # Switch to main or master branch
  git branch --list | grep ' master$'
  if   [[ $? -eq 0 ]]; then git switch master
  elif [[ $? -ne 0 ]]; then git branch --list | grep ' main$'
    if [[ $? -eq 0 ]]; then git switch main; fi
  else  # neither exists
    git switch -c main
  fi
}

I haven't tested this for each combination of master and/or main being present and/or absent, though.

Katrin Leinweber
  • 1,316
  • 13
  • 33
  • I guess `git switch` is an alias for checkout ? – Nonoreve Apr 27 '21 at 13:41
  • 1
    @Nonoreve "git checkout" did rather too many different things, so "git switch" is [the new command for switching branches](https://git-scm.com/docs/git-switch), while "git restore" is taking over things like "undo local changes to this file" – IMSoP Apr 27 '21 at 13:48
  • thanks I didn't knew this new feature. Also I think you answering made this question active again witch made someone give an answer even closer to what I thought of. – Nonoreve Apr 27 '21 at 14:16
  • 1
    "`find ./.git/refs/remotes/* -type f -name 'HEAD'`" Note that if there are multiple remotes with a HEAD reference, this will print multiple values. Speaking of robustness, since `find` is already being used, I suppose that `*` can be dropped, as if it expands to funny characters I think that they could cause issues; see https://mywiki.wooledge.org/BashFAQ/004. Suggestion: `find ./.git/refs/remotes/ -type f -name 'HEAD' -exec awk -F "/" '{print $NF}' '{}' +` – kelvin Nov 13 '21 at 01:10
1

Here's a simple way to do it in an alias in your .gitconfig:

[alias]
    com="!f() { git checkout master 2>/dev/null || git checkout main; }; f"

com stands for "checkout master/main". Just easier to type git com.
Note: only works if the main branch names in the repos you work on are either master or main.
Why anyone would make different main branch names I have no idea since there really isn't any programmatic advantage.

Cro-Magnon
  • 11
  • 3
0

From https://stackoverflow.com/a/62397081/10095231:

git rev-parse --abbrev-ref origin/HEAD will print origin/<default-branch-name>.

To strip the remote part (i.e.: "origin") from it and only print what is to the right of (the last) /:

basename "$(git rev-parse --abbrev-ref origin/HEAD)"

To support multiple remotes, something like the following could be done (inside a script or a function, for example):

test -n "$1" && r="$1" || r=origin
basename "$(git rev-parse --abbrev-ref "$r"/HEAD)"

Now inside a git alias, called "defbranch":

[alias]
    defbranch = "!f() { test -n \"$1\" && r=\"$1\" || r=origin; \
      basename \"$(git rev-parse --abbrev-ref \"$r\"/HEAD)\"; }; f"

It should be portable enough to work on any POSIX-compatible shell.

Add the above code to your git config file (e.g.: ~/.gitconfig) and use it like so:

$ git defbranch
master

To print the default branch of another remote, just specify its name as the first argument:

$ git defbranch origin2
main

Examples of using the alias with other git commands:

git checkout "$(git defbranch)"
git merge "$(git defbranch)"

For increased ergonomics, you could also store the default branch name in a variable and then use just the variable:

db="$(git defbranch)"
git checkout "$db"
git merge "$db"
kelvin
  • 1,421
  • 13
  • 28