My git tips for a more efficent workflow

I have read several articles over the years around git workflows, this article is a summary of where my current git work flow is at.

Configuration


git has three levels of configuration.

  • System: Applies to all users on the system (set with --system).
  • Global: Applies to the current user across all repositories.
  • Local: Applies only to the current repository.

Most of the time, I configure git at the global level to streamline my workflow across projects. However, I use local configuration when working with clients who require a dedicated work email. So far, I haven’t had the need to apply system-level git configuration in my career.

git uses the following order of precedence for configuration (from highest to lowest)

  1. Local (<repo>/.git/config)
  2. Global (~/.gitconfig)
  3. System (/etc/gitconfig)

There are two ways that I know of configuring git. You can do it via the command line with the git config command specifying a configuration level with --global, --local, or --system (If you dont specify a level it will be local by default), for example;
git config --global user.name "Your Name"
git config --global user.email "your.email@example.com"

Or you can config it by opening up the ~/.gitconfig file (or one of the other aforementioned files) and edit it yourself. This is the main way that I find myself configuring git. The git config file is in the INI file format, in this format you have sections denoted with [] and then key value pairs, for example;

[user]
  name = Your Name
  email = your.email@example.com

One of the first things I configure is the template that git will use when writing commits. This will be used instead of the standard message and is a great way to remind yourself to write consistent and well worded git commits.
[commit]
  template = ~/.gitmessage

You then have to add the ~/.gitmessage file. In mine I have the following, which is extracted from this article on writing good commit messages
# Title: Summary, imperative, start upper case, don't end with a period
# No more than 50 chars. #### 50 chars is here:  #

# Remember blank line between title and body.

# Body: Explain *what* and *why* (not *how*). Include task ID (Jira issue).
# Wrap at 72 chars. ################################## which is here:  #

# At the end: Include Co-authored-by for all contributors. 
# Include at least one empty line before it. Format: 
# Co-authored-by: name <user@users.noreply.github.com>
#
# How to Write a Git Commit Message:
# https://chris.beams.io/posts/git-commit/
#
# 1. Separate subject from body with a blank line
# 2. Limit the subject line to 50 characters
# 3. Capitalize the subject line
# 4. Do not end the subject line with a period
# 5. Use the imperative mood in the subject line
# 6. Wrap the body at 72 characters
# 7. Use the body to explain what and why vs. how

Quality of life
These are a couple of configurations that work for me, but they fit in with how I like my git workflow to be.

When pushing and pulling my remote branches always match my local branches and I don't want to have to explicitly set them up so I have the following configuration.
[push]
  default = current
[pull]
  default = current
For pushing, that means, you can push the current branch to the remote branch of the same name (creating it if it doesn’t exist) without needing to set the upstream branch explicitly.
For pulling, it doesn't have as big an impact as the remote relationship needs to be established. However, with push.current set you can then just pull.

Aliases


I only have one shell alias defined for git, and that is to alias git to g. I use the git command probably ten's (if not hundreds) of times a day and this really does save me alot of keystrokes (and if you use oh-my-zsh and have the git plugin this is already defined for you).

That is the only alias I would put in my shell config as git has its own alias system. This is what I use for defining git aliases.

Once again when defining aliases you have two options you can use the git config command, for example;

git config --global alias.st status

Or you can open up the ~/.gitconfig (or one of the other aforementioned files) and define the alias with the key value pair in the [alias] section of the config file

You can check on what aliases you already have defined with the following command;

git config --get-regexp alias

Simple aliases

Most of my aliases that I define are similar to the g alias, they mainly just shorten existing git commands. Here is a list of some aliases that I have defined.

[alias]
  pl = pull
  cl = clone
  ft = fetch
  ...
These aliases just help me to save keystrokes, and don't really help improve my git workflow that much.

Tweaking the defaults

Some of my aliases that I have defined tweak the defaults. These are the aliases that have a bigger impact on improving my workflow as they make git work for me, for example;
[alias]
  st = status -sb

This makes it so that when I run status I get the short output -s and the -b shows the branch information. So for example the difference in the output looks like the below.

## main...origin/main
 M file1.txt
A file2.txt
 D file3.txt

Instead of the default, which would look like the below;

On branch main
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  modified: file1.txt

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
  new file: file2.txt

Deleted files:
  (use "git restore <file>..." to restore it)
  deleted: file3.txt

Another example of an alias I added to tweak the default would be the diff command below.

[alias]
  diff = diff -w --patience
 
This aliases uses two options
  1. -w This makes diff ignore white space that don't affect the actual content allowing me to focus on actual code changes
  2. --patience means that git will use the patience diff algorithm. This algorithm tries to minimize the noise (like when moving functions).

Complex aliases

I have some aliases that are more complex as I haven't been able to just tweak the defaults to give me the command that I am looking for. Luckily git allows aliases that are shell commands by prefixing the command with a !. You either can define it without quotes or with quotes if your command contains white spaces. I'll show you two examples I have in my git config.

Firstly its the ls command which is probably one of the commands I use the most during a day. The alias looks like the following;
[alias]
  ls = !"f() { git --no-pager log --oneline -n ${1:-10}; }; f"

This command shows the previous n commits with no pager and in the one line format (which I much prefer when just checking the previous commits). However, I wanted to be able to specify the number of commits that were output or keep it to the default of 10. You cant just pass arguments like that to aliases, you can define it as a shell command that in turn defines a shell function. Then you can pass it arguments like you can a normal shell function. In this commands case we have ${1:-10} this will either use the first argument given or default to 10. An example usage of this would be.

$ g ls
dc4ede6 (HEAD -> master, origin/master) Example commit #1
e328b4b Example commit #2
fe69015 Example commit #3
2d5c911 Example commit #4
3cb585e Example commit #5
ddb74cc Example commit #6
ddec719 Example commit #7
6ced65d Example commit #8
aaf9c5a Example commit #9
96b16f1 Example commit #10

or

$ g ls 5
dc4ede6 (HEAD -> master, origin/master) Example commit #1
e328b4b Example commit #2
fe69015 Example commit #3
2d5c911 Example commit #4
3cb585e Example commit #5






Another example would be the command I have that removes branches that have been merged and deleted but still exist locally
[alias]
  rm-br = !"git fetch -p && for branch in $(git branch -vv | grep ': gone]' | awk '{print $1}'); do git branch -D $branch; done"

I wont explain this one in detail, you can work it out for yourself, but I have hopefully shown you that the git aliasing system is super extensible and that you should always be looking for ways to improve your workflow.

Thanks for reading