I work on a lot of GitHub repositories. In most of these, I make a fork of the repository, do my own coding, and then open PRs against the main repository.
Like any avid Git user, over time I have accumulated a number of Git aliases. But my favorite (and one of the most frequently used) is one I call git prom
.
Since Git setups tend to differ, let me give a quick high-level about how I set up my clones and why.
How I Set Up My Local Clones
Typically, when working on an upstream repository at GitHub, I start with the main repo, clone it, and work on a clone. Then I open PRs to suggest merging my changes back into upstream.
So we're tracking two repos: the upstream and my own clone. I use the following naming convention:
- My own repo is always called
my
(git clone my URL
) - Upstream is always
origin
(git remote add origin URL
)
For example, here's a snippet of my Git config for the spin repository:
[remote "origin"]
url = git@github.com:fermyon/spin.git
fetch = +refs/heads/*:refs/remotes/origin/*
[remote "my"]
url = git@github.com:technosophos/spin.git
fetch = +refs/heads/*:refs/remotes/my/*
I find this really useful because then origin
is always the "real" project, and my
is always my copy. Easy to remember.
Some of my friends call upstream upstream
and their copy origin
. I find that use of origin
confusing, but if it works for you, it's totally fine. I'll point out where you need to make changes in the alias.
The Problem: Updating my branch to the last from upstream
For a longer-running PR, it is often the case that I need to periodically update my branch with the latest from the upstream repository.
The best way to do this is to:
- Pull the upstream repo
- Rebase my own work on top of it
I wanted a fast way to do this that didn't involve typing lots of flags (and hence introducing typo opportunities).
The Solution: The git prom
alias
I wrote a quick Git alias that is in my main .gitconfig
file (and is therefore global):
[alias]
prom = "!git pull --rebase origin $(git remote show origin | grep 'HEAD branch' | cut -d' ' -f5)"
First, let me explain what it does. Then I'll go into the specific command a little more.
The name gives it away; prom
is shorthand for Pull Rebase Origin Main. So it pull
s and rebase
s origin
's (or upstream's) main
branch.
Say I'm on a feature branch called feat/prom-example
, and I've been working away on it. In the meantime, upstream's main
branch has been merging new PRs. I need to re-sync. I'll use git prom
to rebase my current branch on the latest main
:
$ git prom
remote: Enumerating objects: 1180, done.
remote: Counting objects: 100% (327/327), done.
remote: Total 1180 (delta 327), reused 327 (delta 327), pack-reused 853
Receiving objects: 100% (1180/1180), 536.46 KiB | 2.87 MiB/s, done.
Resolving deltas: 100% (691/691), completed with 124 local objects.
From github.com:fermyon/spin
* branch main -> FETCH_HEAD
0405da75..74cd215a main -> origin/main
Updating 16dd1339..74cd215a
Fast-forward
.devcontainer/Dockerfile | 3 -
.devcontainer/devcontainer.json | 1 -
.envrc | 1 +
# and more changes
At the end, I have the latest version of the upstream's main
branch pulled, and then my own changes rebased on top of that.
This is a very common part of workflow, so it's nice to have a nice mnemonic alias that is more typo-resistent than the usual full command.
How The Command Works
Even though the workflow is very common, the alias is surprisingly complex. Let's take a quick look at what it's doing.
prom = "!git pull --rebase origin $(git remote show origin | grep 'HEAD branch' | cut -d' ' -f5)"
In essence, when I am on a branch feat/prom-example
and I want to pull and rebase, the basic command is git pull --rebase origin main
. And in most cases, the prom
alias could have just been prom = "!git pull --rebase origin main"
.
But... it is not always the case that the upstream main branch is named main
. Some still use the antiquated master
. Others use their own systems for what the main branch is. (I worked on one that set the main branch name to whatever the current major release number was, e.g. v2
.)
So to get around this limitation, we need to run a bit of ad hoc shell processing: $(git remote show origin | grep 'HEAD branch' | cut -d' ' -f5)
This does the following:
- Run
git remote show origin
to see the branches that are upstream (remember: for me,origin
is the name of the upstream repository. If you use a different convention, adapt accordingly). -
grep 'HEAD branch'
gets us the name of the branch that upstream currently considers to be the "HEAD". For GitHub, this is the branch that is the default main branch. - And
cut -d' ' -f5
says "split the returned line based on ' ' (space) characters and return me the 5th (-f5
) field.
So git remote show origin | grep HEAD
returns
HEAD branch: main
Note the leading whitespace. The cut
command returns five fields:
- [EMPTY]
- [EMPTY]
HEAD
branch:
main
And we just want field 5. If we were to run just this subcommand in a terminal, here's what the output would look like:
$ git remote show origin | grep HEAD | cut -d' ' -f5
main
A Quick Note for Windows (non-WSL) Users
The alias I use works fine on macOS, Linux, WSL, and probably most UNIXy OSes. But it will not work on Windows outside of WSL. I don't happen to know how to do this in Powershell, so your best bet may be to just hard code in the default branch name with !git pull --rebase origin main
.
If you know a Windows solution, please post it in the comments below. I'd love to know this.
Conclusion
My git prom
alias abstracts away the specifics of git pull --rebase origin main
. It still works with esoteric or unconventional upstream repos as long as I follow my origin
and my
conventions.
If you use a different convention (like upstream
instead of origin
), then all you need to do is change occurrences of origin
to whatever convention you use.
Cover image generated using Bing's version of DALL-E using the prompt: "Using a blueprint illustration style, draw a terminal window used by a software developer."
Top comments (1)
I should have noted in the article that I also have these two aliases for other cases where naming warranted it: