Git Prom! My Favorite Git Alias
Published: 2024-01-05 23:22:30

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](https://github.com/fermyon/spin):

```
[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 **P**ull **R**ebase **O**rigin **M**ain. 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`:

```console
$ 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](https://www.hanselman.com/blog/easily-rename-your-git-default-branch-from-master-to-main) `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:
1. [EMPTY]
2. [EMPTY]
3. `HEAD`
4. `branch:`
5. `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:

```console
$ 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."