24

With the variety of Default Branch choices in git, how can I write a script/alias which references the default branch no matter the name?

My specific example is my alias which rebases onto the newest remote master:

### version for `master`
#   rebaseOn = "!f() { BRANCH=$(git symbolic-ref --short HEAD) && git fetch && git pull --rebase; git rebase origin/${1:-master}; }; f"
### version for `main`
    rebaseOn = "!f() { BRANCH=$(git symbolic-ref --short HEAD) && git fetch && git pull --rebase; git rebase origin/${1:-main}; }; f"

usage: git rebaseOn or git rebaseOn newParentBranch (because its defaulting the input ${1:-main})

I would like that command to work for either version there, because various repositories I work with have main or master or default or trunk


So far I've found https://stackoverflow.com/a/50056710/356218: git remote show upstream | grep "HEAD branch" | sed 's/.*: //' but that is quite long for inserting into an alias.

Is there a better option than that?


Best answer so far

Restating the current best answer in my own words:

@matt suggesting of using git branch --set-upstream-to:

  • I suppose you set git branch --set-upstream-to <main/master> when you initially clone a repo, and be very consistent about running that command with every repo you touch
  • So then you can ignore what github or such says is the default master, and know that on this setup of yours, you always use your preference locally
    • As opposed to pair programming or anything where you must fall back to standard behavior
  • Then before rebasing onto default, you must pull your choice of name (script this) so that any new commits are included before the rebase
    • So if you're using a branch to work in parallel with working on something else on default you can't really do that ever with this method, always need a separate branch for commits which aren't ready to push yet
  • Then you can merge into default and push without extra work

Merging works mildly better probably (as is always the case with git)

Note: the real frustration is when a repo has both a master and main branch, and its difficult to remember/know/discover which is the primary via CLI (have to git log and look at dates, remember fairly obscure commands, or check github. All those take more than 2 seconds and a context change, to give an idea of the level of the frustration)

Thymine
  • 8,775
  • 2
  • 35
  • 47
  • 2
    don't use alias, just create `git-rebaseOn` bash script and you can call it with `git rebaseOn` – Ôrel Feb 16 '21 at 23:51
  • 3
    Could you be overthinking this? There is nothing special about the name of the branch so why not just change it to `master` or `main` or whatever consistent name you like, _locally_, while leaving the _remote_ tracking branch hooked to the correct branch on the remote? There is no law saying your local name must match the remote name, you know. – matt Feb 17 '21 at 03:36
  • @matt, yeah there is nothing that says that, but I feel like you're just pushing the difficulty to a different place, so instead of needing to know when I rebase or otherwise, I need to know that I need to set that up on initial clone? I want the problem to be automated away, since that should be easy to do, not just some other manual action which helps it for the common cases – Thymine Feb 17 '21 at 18:39
  • "I need to know that I need to set that up on initial clone" You don't need to know anything. There is really no such thing as a "default branch". If you do a clone and you don't like the name of whatever is considered the primary branch, just change it to what you do like, locally. That part does require thinking. But now the problem is automated away because you know the name of the primary branch: it's the name you always use. :) – matt Feb 17 '21 at 18:41

3 Answers3

18

Continuing from great @torek's comment, you can set your aliases like that:

main-branch = !git symbolic-ref refs/remotes/origin/HEAD | cut -d'/' -f4
remotesh = remote set-head origin --auto
com = "!f(){ git checkout $(git main-branch) $@;}; f"
upm = "!f(){ git pull --rebase --autostash origin $(git main-branch) $@;}; f"
rebasem = "!f(){ git rebase -i --autosquash origin/$(git main-branch) --no-verify $@;}; f"

git main-branch will return the name of the current "main" branch (main/master).

In other aliases use $(git main-branch) instead of hardcoded master branch name. Just remember that it needs to be a shell script (which starts with !) so that the $(git main-branch) gets actually interpolated.

If any of the aliases errors with fatal: ref refs/remotes/origin/HEAD is not a symbolic ref, then simply run git remotesh .

jtompl
  • 1,034
  • 13
  • 16
  • What is `git remotsh`? It does not work on my system and I cannot find any documentation online. – Martin J.H. Mar 27 '22 at 19:51
  • An alias that is added by the configuration mentioned in the answer. – jtompl Mar 28 '22 at 14:11
  • Just what I needed to create a single `checkout-and-update-main` alias. Thanks for the clever and comprehensive answer! – Nick K9 Nov 23 '22 at 14:38
  • This is great! Can you explain why you need to put the com one inside a bash function `f(){...` and also why does `$@;` need to be inserted at the end? – rasen58 Feb 23 '23 at 01:51
2

You have numerous options here. I would, however, recommend that you use none of the ones I list here, because your branch names are yours. There's no reason you need to use the same names as some other repository. That's what matt suggests in a comment as well.

Still, your options include:

  • Read origin/HEAD in your own repository.

    repo-A$ git symbolic-ref refs/remotes/origin/HEAD
    refs/remotes/origin/master
    
    repo-B$ git symbolic-ref refs/remotes/origin/HEAD
    refs/remotes/origin/main
    

    where repo-A is a clone of an older repository that uses master and repo-B is a clone of a newer repository that uses main, for instance.

    Note that you may occasionally need to run git remote set-head --auto to get your Git to notice that their Git has renamed master to main and hence to update your origin/HEAD to match. Your origin/HEAD is established when you run git clone and not updated after that, unless you use git remote set-head --auto. You could of course put this git remote set-head operation into your alias.

  • Use git remote show, just as you showed. It's in an alias: why do you care how long the alias is?

  • Use the upstream setting (git branch --set-upstream-to) and then just run git rebase, so as to pick the upstream of the current branch. Of course that will mostly be origin/name for branch name; presumably, you're using this alias when you want to update your name before a forced push to update the branch named name over on origin. If so, this option is a bad one.

  • Write a script. Put the script in your $PATH somewhere, naming it git-rebase-on-default-branch for instance. Running git rebase-on-default-branch will run your script. Now that it's not an alias, but rather a script—in whatever language you like—you can put as much code as you like in it, making it as clever and fancy as you want. (This, I see, is also Ôrel's suggestion.)

There are no doubt more, but this should cover at least some of the bases.

torek
  • 448,244
  • 59
  • 642
  • 775
  • Thanks for a well thought out answer. I care about the length of the part of the alias which I would need to copy/paste in other aliases using the logic, so the executable script is better (albeit will again only work on my OS and setup). I really don't get the `git` mindset here, do y'all not communicate with other people closely, or you're just fine with the layer of abstraction which is totally unique to your env and that is just fine with the mental gymnastics to know that _on my machine_ its `master` still. Its minor, but comes up dozens of times per day – Thymine Feb 17 '21 at 18:46
  • 1
    @Thymine: once you start doing complicated stuff, like using three or four remotes, you run out of the option of naming your branch the same as their branch, because there are three or four different remote-named foo/master-s. So you get used to not having the names match any more. (For simpler cases, I do tend to name mine the same as theirs, but for this particular case, I just don't bother with a `master` / `main` at all, and use `origin/whatever` directly. It hasn't been a problem yet.) – torek Feb 18 '21 at 02:13
  • I do forget about forks and stuff, since only ever have the company enterprise github as the single remote, so that is much more understandable. My thought is GUI for the win (_see_ the full context you're choosing from), but git lacks in full-power GUIs much of the time too :) Thanks for the edification of your workflow! – Thymine Feb 18 '21 at 18:57
0

I solved it with a "git" wrapper which rewrites "main" to "master" (or vice versa, depending on the default branch name) in the command arguments before invoking git.

Add this to your PATH somewhere before the actual git command, for example /usr/local/bin, ~/bin, ~/.local/bin, or whatever makes sense on your system. Remember to make it executable too.

Note that:

  • You will need zsh (not as the default shell, just installed); I'm sure it can be done in bash too but it's more difficult and I can't be bothered to port it.

  • Git is assumed to be in /usr/bin/git; we need the full path because otherwise it'll keep calling itself. Could parse it from PATH, but that's more complex.

  • It works everywhere on the commandline (this is a feature): if you type something like git commit -am main then this will get rewritten too if the branch name is master.

  • You could convert it to a shell function, which is slightly faster. Then it will only work from your shell though (which may or may not be a good thing, but I like it because e.g. :!git from Vim will work identical).

The upshot of all of this is that you don't need special aliases for every command, and can just continue using git as before.

#!/usr/bin/env zsh
#
# Frob with git so that "main" gets rewritten to "master" if that's the default
# branch, or vice versa.
#
# The "real" git is assumed to be in /usr/bin/git.
[ "${ZSH_VERSION:-}" = "" ] && echo >&2 "Only works with zsh" && exit 1
setopt err_exit no_unset no_clobber pipefail

def=${$(/usr/bin/git symbolic-ref refs/remotes/origin/HEAD >&/dev/null ||:):t}
if [[ $def = 'master' && $argv[(i)main] -le $#argv ]] then
    print >&2 "Using 'main' instead of 'master'"
    argv[$argv[(i)main]]=master
elif [[ $def = 'main' && $argv[(i)master] -le $#argv ]] then
    print >&2 "Using 'master' instead of 'main'"
    argv[$argv[(i)master]]=main
fi

exec /usr/bin/git $argv
Martin Tournoij
  • 26,737
  • 24
  • 105
  • 146