The letter A styled as Alchemists logo. lchemists
Published February 1, 2023 Updated August 21, 2023
Cover
Git Configuration

I’ve written about Git several times in the past but haven’t spent much time talking about how to properly configure your global configuration. Maintaining a global configuration allows your local configurations to inherit common defaults while improving consistency across all managed repositories. In this article, we’ll focus on a Git Rebase workflow while leveraging XDG to manage our global Git configuration. OK, let’s jump in!

Table of Contents

Overview

Below is my current global configuration with a few edits made for security reasons:

[advice]
  detachedHead = false
[branch]
  autoSetupRebase = always
[commit]
  gpgSign = true
  template = ~/.config/git/commit_message.txt
[core]
  abbrev = 12
  editor = "$EDITOR --wait"
  fsmonitor = false
  hooksPath = ~/.config/git/hooks
  pager = delta
  quotePath = false
  untrackedCache = true
  whitespace = fix,-indent-with-non-tab,trailing-space,cr-at-eol
[color]
  pager = true
  ui = true
[color "branch"]
  current = yellow reverse
  local = yellow
  remote = green
[color "diff"]
  commit = 227 bold
  frag = magenta bold
  meta = 227
  new = green bold
  old = red bold
  whitespace = red reverse
[color "diff-highlight"]
  newHighlight = green bold 22
  newNormal = green bold
  oldHighlight = red bold 52
  oldNormal = red bold
[color "status"]
  added = yellow
  changed = green
  untracked = cyan
[credential]
  helper = cache --timeout=3600
[delta]
  commit-decoration-style = bold yellow box ul
  file-style = bold yellow ul
  hunk-header-decoration-style = yellow box
  line-numbers = true
  minus-color = "#340001"
  plus-color = "#012800"
  side-by-side = true
  whitespace-error-style = 22 reverse
[diff]
  algorithm = histogram
  colorMoved = default
  indentHeuristic = true
  mnemonicPrefix = true
  renames = copies
  tool = Kaleidoscope
[diff "exif"]
  textconv = exiftool
[difftool]
  prompt = false
[difftool "Kaleidoscope"]
  cmd = ksdiff --partial-changeset --relative-path \"$MERGED\" -- \"$LOCAL\" \"$REMOTE\"
[feature]
  experimental = true
[fetch]
  prune = true
  pruneTags = true
  writeCommitGraph = true
[grep]
  column = true
  fullname = true
  lineNumber = true
[init]
  defaultBranch = main
  templateDir = ~/.config/git/template
[interactive]
  diffFilter = delta --color-only
  singleKey = true
[maintenance]
  auto = false
  strategy = incremental
[merge]
  conflictStyle = zdiff3
  ff = only
  tool = Kaleidoscope
[mergetool]
  prompt = false
[mergetool "Kaleidoscope"]
  cmd = ksdiff --merge --output \"$MERGED\" --base \"$BASE\" -- \"$LOCAL\" --snapshot \"$REMOTE\" --snapshot
  trustExitCode = true
[notes]
  rewriteRef = refs/notes/commits
[pack]
  useBitmapBoundaryTraversal = true
[pull]
  rebase = merges
[push]
  autoSetupRemote = true
  default = simple
  followTags = true
  useForceIfIncludes = true
[rebase]
  abbreviateCommands = true
  autoSquash = true
  autoStash = true
  updateRefs = true
[remote "origin"]
  fetch = +refs/pull/*/head:refs/remotes/pull_requests/*
[rerere]
  autoUpdate = true
  enabled = true
[revert]
  reference = true
[status]
  showUntrackedFiles = all
[tag]
  gpgSign = true
  sort = version:refname
[transfer]
  fsckObjects = true
[url "https://github.com/bkuhlmann/"]
  insteadOf = bk:
[url "https://github.com/dry-rb/dry-"]
  insteadOf = dry:
[url "https://github.com/"]
  insteadOf = gh:
[url "https://github.com/hanami/"]
  insteadOf = hanami:
[url "https://git.heroku.com"]
  insteadOf = heroku:
[url "https://github.com/rom-rb/rom-"]
  insteadOf = rom:
[user]
  email = <redacted>
  name = <redacted>
  signingKey = <redacted>
  useConfigOnly = true
[includeIf "gitdir:~/Engineering/Companies/Example/"]
  path = ~/.config/git/profiles/example

As future versions of Git are released and new edits to my global configuration are made, this configuration might grow stale from the time of this writing. For a more up-to-date view of my current configuration, feel free to check out my Dotfiles project for details.

With the above in mind, we’ll spend the rest of this article detailing what each configuration does and why each is valuable so you can let Git do the heavy lifting for you.

Details

The first thing you might notice with the above configuration is that each section — denoted by the use of brackets (i.e. []) — is alphabetically sorted. Only the includeIf section is listed last due to order of precedence but we’ll get to that later. For simplicity, I’m going to document each key in the configuration using Git’s namespace dot notation which means:

[advice]
  detachedHead = false

…​becomes advice.detachedHead = false. This is roughly the same syntax you’d use if wanting to immediately adjust your global configuration via the command line:

git config --global advice.detachedHead false

With the above in mind, let’s walk through each setting so you can learn how they work and determine if you’d like to apply these changes to your own configuration. Also, keep in mind that the official documentation for all of these settings can be found via Git’s own documentation. Anyway, here’s the breakdown:

  • advice.detachedHead = false: When set to true (default), this will print help text each time you use git switch or git checkout to move into a detached HEAD state. This can quickly become annoying when doing this often so I’ve permanently disabled it to reduce verbosity.

  • branch.autoSetupRebase = always: Ensures new branches are automatically configured to use a Git Rebase workflow.

  • commit.gpgSign = true: Ensures all commits are signed for improved security and author verification. See the Milestoner documentation for further details on how to setup and configure GPG if you haven’t already.

  • commit.template = ~/.config/git/commit_message.txt: References my Dotfiles template in a XDG structure which provides helpful text and a reminder checklist when creating a new commit message.

  • core.abbrev = 12: Uses only the first 12 characters of a commit SHA instead of the full 40 characters which saves screen real-estate while still being uniquely identifiable.

  • core.editor = "$EDITOR --wait": Instructs my default editor to wait for me to edit and save my commit message. Without this being set, I’d never be able to finish a commit message, rebase, etc. I use EDITOR as a global environment variable which provides the path to my Sublime Text IDE.

  • core.fsmonitor = false: Enables Git’s built-in file system monitor daemon which will watch all projects for changes and speed up commands that use the index. Useful for large repositories but there is a significant performance hit for small projects especially when checking the status of all projects because if the daemon is not running it will have to be started first.

  • core.hooksPath = ~/.config/git/hooks: Tells Git where to look for my Dotfiles managed Git Hooks XDG configuration. This is one of the most powerful aspects of Git because it allows you to wire in additional behavior to automate your workflow and improve your performance.

  • core.pager = delta: Tells Git to use the Delta program for pagination purposes as installed via my macOS Configuration.

  • core.quotePath = false: Ensures path output is not quoted and escaped by default.

  • core.untrackedCache = true: Ensures any untracked cache is added to your index so you don’t lose information.

  • core.whitespace = fix,-indent-with-non-tab,trailing-space,cr-at-eol: Defines how to deal with whitespace problems in your diff. You generally want this comma delimited list but I’ll refer you to the Git documentation since it details each value.

  • color.pager = true: Ensures output is colorized for improved readability.

  • color.ui = true: Enables color for all commands who support colorized output.

  • color.branch.current = yellow reverse: Shows current branch using a yellow background with foreground text in a reverse color for improved readability contrast.

  • color.branch.local = yellow: Shows local branches in yellow foreground text.

  • color.branch.remote = green: Shows remote branches in green foreground text.

  • color.diff.*: I won’t individually list these but know this is how you customize the color for diffs. In my case, all colors are defined with the assumption of reading in a black console.

  • color.diff-highlight.*: Similar to the above, these are the colors used for diff highlights.

  • color.status.added = yellow: Shows added status in yellow color.

  • color.status.changed = green: Shows changed status in green color.

  • color.status.untracked = cyan: Shows untracked status in cyan color.

  • credential.helper = cache --timeout = 3600: Instructs your credential helper for username/password information to cache for 1 hour to reduce strain when needing to resupply credentials.

  • delta.*: I won’t walk through each Delta setting used in my configuration but refer you to Delta’s documentation instead since that is where I sourced these settings to begin with and you might want to take as is or customize further.

  • diff.algorithm = histogram: Defines the histogram diffing algorithm which further enhances the patience algorithm to support low occurrence common elements for better diffing analysis that helps speed up your reading time.

  • diff.colorMoved = default: Determines the color of moved lines in a diff. In this case, the defaults are used.

  • diff.indentHeuristic = true: Ensures the heuristics for determining diff hunk boundaries are easier to read.

  • diff.mnemonicPrefix = true: Ensures the the default a/ and b/ diff prefixes are dropped in favor of using i/ (index) and w/ (working copy) which are more informative than alphabetic letters.

  • diff.renames = copies: Ensures Git not only detects file renames but also file copies where the code is the same but the file has been moved to a different location for improved diff reporting.

  • diff.tool = Kaleidoscope: Defines your favorite external diff tool. In my case, I use Kaleidoscope as found via my macOS Configuration project.

  • diff.exif.textConv = exiftool: Defines which tool to use when converting binary files, like images, so you can see metadata information. I use the ExifTool as found via my macOS Configuration project.

  • difftool.prompt = false: Disables the annoying confirmation prompt when launching your custom diff tool.

  • difftool.Kaleidoscope.cmd = ksdiff --partial-changeset --relative-path "$MERGED" — "$LOCAL" * ``"$REMOTE": Defines how to execute Kaleidoscope. See the Kaleidoscope CLI documentation for further information.

  • feature.experimental = true: Enables config options that are experimental/new but not fully supported or might change in the future. I like to live on the edge by using the latest and greatest Git has to offer. 😅

  • fetch.prune = true: Ensures each fetch prunes remote branches that no longer exist for a clean branch history you don’t have to manually maintain.

  • fetch.pruneTags = true: Ensures each fetch prunes remote tags that no longer exist for a clean tag history you don’t have to manually maintain.

  • fetch.writeCommitGraph = true: Informs git gc to update the commit graph after each fetch for improved performance of several Git commands (for example: git log). This is less demanding than a full GC operation for non-trivial garbage collection.

  • grep.column = true: Ensures column information is printed when searching for improved readability of output.

  • grep.fullName = true: Ensures paths, relative to the top of your repository, are used instead of being relative to the current directory you are in for more actionable output.

  • grep.lineNumber = true: Ensures line numbers are printed when searching.

  • init.defaultBranch = main: Ensures main is always defined as the default branch.

  • init.templateDir = ~/.config/git/template: Defines where my XDG configuration template is located for initializing new Git repositories with custom settings as provided by the Dotfiles project.

  • interactive.diffFilter = delta --color-only: Ensures Delta is used to handle the diff filter for interactive commands.

  • interactive.singleKey = true: Ensures only a single key is used without having to hit enter for interactive commands.

  • maintenance.auto = false: Disables some Git commands from executing maintenance after completing their work. This allows me to control when maintenance mode is executed via my Dotfiles aliases/functions.

  • maintenance.strategy = incremental: Ensures the maintenance strategy is incremental which optimizes for performing small activities only.

  • merge.conflictStyle = zdiff3: Specifies the diff conflict style used. In this case, I’m able to see what is on HEAD, what is common, and what is local for improved diffing.

  • merge.ff = only: Ensures no extra commit is made when merging. This helps ensure a clean Git history while also ensuring the merge can only be performed when there are no errors.

  • merge.tool = Kaleidoscope: Defines Kaleidoscope as my default merge tool.

  • mergetool.prompt = false: Ensures there is no prompt when using a custom merge tool.

  • mergetool.Kaleidoscope.cmd = ksdiff --merge --output "$MERGED" --base "$BASE" — "$LOCAL" --* ``snapshot "$REMOTE" --snapshot: Informs Git how to use Kaleidoscope for displaying merges.

  • mergetool.Kaleidoscope.trustexitcode = true: Ensures the merge tool properly exits when given a non-zero exit code.

  • notes.rewriteRef = refs/notes/commits: Ensures notes are not lost when rebasing.

  • pack.useBitmapBoundaryTraversal = true: Ensures bitmaps are used for improved index traversal performance.

  • pull.rebase = merges: Enforces a Git Rebase workflow instead of the default merge workflow when pulling new content from the remote repository. By using merges instead of true, you also gain the ability to rebase merge bubbles when working in a team environment that is unfortunately a mix of merging and rebasing.

  • push.autoSetupRemote = true: When enabled, assumes that the current branch is set upstream (i.e. same name as branch) where remote tracking is enabled by default so you don’t have to type the current branch name each time you are pushing.

  • push.default = simple: Works in conjunction with the above by using the same branch name locally and remotely.

  • push.followTags = true: Ensures, when pushing, any tags not on the remote are pushed as well for reduced manual labor.

  • push.useForceIfIncludes = true: Ensures you can’t force push changes to remote branch if commit history doesn’t have same parent. This is extremely handy to ensure you don’t obliterate remote changes if the histories don’t align especially in rare cases your local branch managed to get misaligned with the remote branch. See my Git Rebase article for details.

  • rebase.abbreviateCommands = true: Ensures rebase editor commands are appreviated to a single letter for less typing. See my Git Rebase article for details.

  • rebase.autoSquash = true: Ensures each rebase automatically moves fixup!, squash!, and amend! commits to their corresponding commit for reduced manual labor. See my Git Rebase article for details.

  • rebase.autoStash = true: Ensures your untracked, modified, or staged files are automatically pushed onto the stash before a rebase and auto-popped after the rebase completes so you don’t have to manage this yourself. See my Git Rebase article for details.

  • rebase.updateRefs = true: Ensures any branches, which references the same commits, are automatically rebased along with the current branch being rebased. Reduces the maintenance burden when working on multiple branches in parallel or chained together.

  • remote.origin.fetch = +refs/pull//head:refs/remotes/pull_requests/: Fetches pull request information which can be used via a Dotfiles function for interacting with pull request information locally via an interactive prompt.

  • rerere.autoUpdate = true: Ensures the rerere cache is automatically updated when resolving a rebase conflict for improved rebase speed. See my Git Rebase article for details.

  • rerere.enabled = true: Ensures rerere is enabled for an improved rebase workflow so you can reduce the number of times you have to resolve the same conflict when rebasing multiple times. See my Git Rebase article for details.

  • revert.reference = true: Disables the verbose commit revert message and uses a formatted SHA for reference instead which you can describe with more useful information.

  • status.showUntrackedFiles = all: Ensures all untracked files in all directories are shown so you have a better view.

  • tag.gpgSign = true: Ensures tags are always cryptographically signed as done for commits (shown earlier).

  • tag.sort = version:refname: Ensures repository tags are sorted by version. This is especially useful when using Semantic Versioning.

  • transfer.fsckObjects = true: Ensures fetch will abort when receiving a malformed object. This is great to have for repository integrity and when cloning new repositories that have been poorly maintained.

  • url.https://github.com/.insteadOf = gh:: Allows you to use the gh prefix when cloning Github repositories for less typing. Example: git clone gh:hanami/hanami.

  • url.https://github.com/bkuhlmann/.insteadOf = bk:: Allows you to use the bk prefix when cloning my GitHub repositories for less typing. Example: git clone bk:xdg.

  • url.https://git.heroku.com.insteadOf = heroku:: Allows you to use the heroku prefix when cloning Heroku repositories for less typing. Example: git clone heroku:demo.

  • user.email = <redacted>: Defines your global/default email address.

  • user.name = <redacted>: Defines your global/default name.

  • user.signingKey = <redacted>: Defines your GPG signing key (never share this publicly).

  • user.useConfigOnly = true: Ensures Git never attempts to guess user email and name from the local system but uses your configuration instead.

  • includeif.gitdir:/Engineering/Companies/Example/.path=/.config/git/profiles/example: This is an example of a powerful feature which allows you to specify a global configuration override based on the root directory you are currently in. You always want to define this at the bottom of your configuration because it is meant to override settings defined above it. See my Git Config Include screencast for details.

Conclusion

As you can see, there is a lot you can configure with Git. Hopefully, this has been educational in terms of what is possible for upgrading your own configuration. By improving and dialing in your configuration, you allow Git to automate more of your workflow for you which, in turn, makes you a more efficient engineer. Enjoy!