Git provides a rich set of options for customising its behaviour via the .gitconfig file. I present here some of the more interesting options.

For more comprehensive information on each and every option, you had better peruse the official documentation.

Basics

Depending on your preferences, the global gitconfig file is either located at ~/.gitconfig, $XDG_CONFIG_HOME/git/config, or perhaps ~/.config/git/configthe documentation provides all options.

If you have no existing gitconfig, go ahead and create one in your preferred location. I'm using ~/.config/git/config.

Includes

You can include other files into your gitignore with the include directive. This can be useful for colour theme files. Note that files are relative to the current (config) file unless an absolute path is given.

[include]
  path = some-file
  path = /some/other/file

Per-folder settings / includes

It's also possible to use conditional includes, which lets us enable settings for repos under a particular directory. Suppose, for example, that you use your personal e-mail address for open source work, but want to use your work e-mail for commits to work repositories — or, all repositories under ~/work.

For this we can use includeIf:

# ~/.config/git/config
[user]
  email = me@mine.com
[includeIf "gitdir:~/work/"]
  path = config-work
# ~/.config/git/config-work
[user]
  email = me@work.com
# ...

See the docs on conditional includes for more.

Global gitignore

There are some things that you want to make sure never make it into any of your git repos, regardless of how they're each set up, unless you very explicitly force them in. That's why I like to use a global .gitignore file.

This file defaults to $XDG_CONFIG_HOME/git/ignore or ~/.config/git/ignore, but it might make sense to use something under $HOME for consistency if using ~/.gitconfig:

# ~/.gitconfig
[core]
  excludesfile = ~/.gitignore
# ~/.gitignore
.secrets
*.gpg
.in

# ... etc ...

Parallelization

Some operations can be parallelized, which aren't by default. This might speed up your workflow when working with larger repos, but may also slow you down with smaller repos. YMMV.

[checkout]
  workers = -1 ; use all cores to checkout
[fetch]
  parallel = 0; reasonable level of parallelization

Conditional tooling

I might work on some machines where one diff or merge tool is available and another isn't. For this reason I like a set of fallbacks to pick from. This can be achieved with simple scripts.

[difftool "difftool"]
  cmd = difftool "$LOCAL" "$REMOTE" ; script which selects the right difftool
[diff]
  tool = difftool
[mergetool "mergetool"]
  cmd = mergetool "$BASE" "$LOCAL" "$REMOTE" "$MERGED" ; script which selects the right mergetool
[merge]
  tool = mergetool

Then, somewhere in your path, place the scripts. You'll want to change these depending on your preferred fallback order / installed software:

#!/usr/bin/env bash
#
# difftool
#
# Wraps difftool for git use. Conditionally determined appropriate difftool.
LOCAL="$1"
REMOTE="$2"

if [[ $(command -v "smerge") ]]; then
  smerge diff $LOCAL $REMOTE
elif [[ $(command -v "meld") ]]; then
  meld "$LOCAL" "$REMOTE"
elif [[ $(command -v "vimdiff") ]]; then
  vimdiff "$LOCAL" "$REMOTE"
else
  echo "No difftool found."
fi
#!/usr/bin/env bash
#
# mergetool
#
# Wraps mergetool for git use. Conditionally determined appropriate mergetool.
BASE="$1"
LOCAL="$2"
REMOTE="$3"
MERGED="$4"

if [[ $(command -v "smerge") ]]; then
  smerge "$BASE" "$LOCAL" "$REMOTE" -o "$MERGED"
if [[ $(command -v "meld") ]]; then
  meld --diff "$LOCAL" "$BASE" "$REMOTE" --output "$MERGED"
elif [[ $(command -v "vimdiff") ]]; then
  vimdiff "$LOCAL" "$MERGED" "$REMOTE"
else
  echo "No mergetool found."
fi

Aliases

Finally, what's usually the largest section of one's gitconfig is the aliases. Much like shell aliases, these let us get common operations done with fewer keystrokes and ease the need to remember every switch.

Here's an example:

[alias]
  s = status

Now we can use git s and git will run git status for us.

Since we all have our preferences, I'll just include my current aliases here for inspiration. I suggest borrowing a couple at a time, lest you end up with a gitconfig full of aliases you've forgotten you have.

[alias]
  # add
  a  = add
  aa = add -A
  ac = !git add -A && git commit
  acm = !git add -A && git commit -m

  # branch
  b = branch ; list branches
  bd = branch -d ; delete
  bD = branch -D ; delete (remote)
  bmv = branch -m ; rename
  ; detailed branch list
  bl = for-each-ref --sort=committerdate refs/heads/ --format='%(HEAD) %(color:yellow)%(refname:short)%(color:reset) %(contents:subject) %(color:green)(%(committerdate:relative))%(color:reset) %(color:blue)<%(authorname)>%(color:reset) %(color:yellow)%(objectname:short)%(color:reset)'

  # commit
  c  = commit
  cm  = commit -m ; inline message
  ca  = commit --amend ; amend previous commit and edit the message
  can  = commit --amend --no-edit ; amend previous commit without editing the message
  cempty = commit --allow-empty --allow-empty-message -m '' ; make an empty commit with an empty message

  # clone
  cl = clone

  # checkout
  co = checkout ; switch branch
  cob = checkout -b ; create a new branch
  com = !git switch master 2>/dev/null || git switch main ; switch to main or master branch, whichever is present

  # cherry-pick
  cp = cherry-pick

  # diff
  d = diff
  dc = diff --cached ; diff against staged changes
  dh = diff HEAD ; diff against the current head
  d1 = diff HEAD^ ; diff against the previous head
  d2 = diff HEAD^^ ; diff against the previous head -1
  dt = difftool ; open difftool
  dta = difftool --dir-diff ; compare directories

  # log
  ls = log --pretty=doneline ; simple history
  lh = log --pretty=doneline --abbrev-commit --stat ; simple history with file changes
  ld = log -u ; include diffs
  lg = log --graph --pretty=doneline --all ; detailed log with graph
  lfs = !git log --pretty=format: --name-only --diff-filter=A | sort -u | grep -v '^$' | fzf -i ; search for files historically

  # merge
  mt = mergetool
  mc = merge --continue
  ma = merge --abort

  # push
  pf = push -u --force-with-lease
  pu = push -u

  # fetch
  prune = fetch --prune

  # status
  s = status -sbu
  staged = diff --name-only --cached ; list of staged files

  # stash
  sta = stash save --include-untracked ; stash everything; not just staged files.
  spop = stash pop

  # rebase
  r = rebase
  rc = rebase --continue
  ra = rebase --abort

  # rev-parse
  sha = rev-parse HEAD ; get current SHA

  # reset
  rs   = reset
  rs1  = reset HEAD^
  rs2  = reset HEAD^^
  rsh  = reset --hard
  rsh1 = reset HEAD^ --hard
  rsh2 = reset HEAD^^ --hard
  uncommit = reset --soft HEAD^

  # tag
  tagd = tag --delete
  tagdr = push --delete origin ; remote delete
  tags = tag -n --sort=authordate ; list of tags with most recent at the bottom

  # etc
  cat = show

Bonus

Some extra stuff you might want to consider to improve your git workflow: