git configuration


git config

This file generates my git global config.

User config

Only define my name, and not my email. And instruct git to avoid trying to guess a default for the email. This will enforce me to set the email for each git repo.

[user]
        useConfigOnly = true
        name = Toon Claes

Core config

[core]

Global ignore file

Use a global ignore file.

I’m using the default ~/.config/git/ignore so no need to set this explicitly.

excludesfile = ~/.config/git/ignore

Commit comments

I tend to use markdown in my commit messages, and markdown uses the # symbol to define headings. So instead of the default symbol # used to define comments in commit messages, use ;.

commentchar = ";"

CR/LF

Don’t do output conversion on end-of-line.

autocrlf = input

Help

[help]

Auto-correct

Auto-correct mistyped commands after 0.1 seconds.

autoCorrect = 1

Init

[init]

Default branch

When creating a new repo, set the default branch to main.

defaultBranch = main

Commit

[commit]

Template

Specify a git commit template that will help me to write good commit messages.

Here we start with an empty block to force org-tangle to start the file with an empty line. The padline header argument, defaulting to yes, causes empty lines between the above and the below codeblock.


; Why is this change needed?
;Prior to this change,

; How does it address the issue?
;Let's ...

; Provide links to any relevant tickets, articles or other resources


;Label: bug::performance
;Label: bug::availability
;Label: bug::vulnerability
;Label: bug::mobile
;Label: bug::functional
;Label: bug::ux
;Label: bug::transient

;Label: feature::addition
;Label: feature::enhancement
;Label: feature::consolidation

;Label: maintenance::refactor
;Label: maintenance::removal
;Label: maintenance::dependency
;Label: maintenance::scalability
;Label: maintenance::usability
;Label: maintenance::test-gap
;Label: maintenance::pipelines
;Label: maintenance::workflow
;Label: maintenance::performance
;Label: maintenance::release

;Changelog: added
;Changelog: fixed
;Changelog: changed
;Changelog: deprecated
;Changelog: removed
;Changelog: security
;Changelog: performance
;Changelog: other

;EE: true

And configure git to use this template.

template = ~/.config/git/commit_msg.txt

Status

[status]

Untracked files

Show also individual files in untracked directories.

showUntrackedFiles = all

Submodule summary

Enable submodule summary showing the summary of commits for modified submodules.

submoduleSummary = true

Diff

[diff]

Moved code

Show moved lines in a different color.

colorMoved = zebra

Merge

[merge]

Mergiraf

Use mergiraf for better automatic merge conflict resolving.

name = mergiraf
driver = mergiraf merge --git %O %A %B -s %S -x %X -y %Y -p %P

Fetching

[fetch]

Prune

Prune the local tracking branches and tags when fetching from remote.

prune = true
pruneTags = true

Pull

[pull]

Fast-forward only

When pulling only update the current branch by fast-forwarding.

ff = only

Transfer

[transfer]

Bundle-URI

Enable the use of Bundle-URI when the server advertises it.

bundleURI = true

Aliases

Define a set of aliases.

[alias]

Scrub

This alias scrubs away all local branches that are merged.

scrub = !git branch --merged | grep --extended-regexp --invert-match '(^\\*|master|main)' | xargs --no-run-if-empty git branch --delete

Work-in-progress

I don’t like git stashes. A git stash is where code goes to die. So instead of stashes, create a WIP commit on the current branch. This is much better to keep context of the work-in-progress code, allows me to push the code to a remote and have backup, and maybe also give me some insights on the test results of CI.

I’m also providing an alias that undoes the last commit if it is a WIP commit.

Improved unwip alias by ZJ. Improved wip from Aziz Nal, who took the idea from ohmyzsh, which might have gotten it from @haacked.

wip = !git add --all && git commit --no-verify --no-gpg-sign --message 'WIP [skip ci]'
unwip = !git log --max-count=1 --format=format:"%H" --invert-grep --grep '^WIP' | xargs git reset --soft

Rebase on remote master

I commonly rebase my working branch on master on the remote. That remote can be origin or upstream. So this alias allows me to fetch master from the <remote> and rebase current branch on <remote>/master.

It expects one argument:

  • the name of the remote
remaster = !sh -c 'git fetch $1 master && git rebase $1/master' -

Resync with remote

When the remote branch was rebased and you want to resynchronize your local branch to the remote state, use this reremote alias.

It expects one argument:

  • the name of the remote
reremote = !sh -c 'git rev-parse --abbrev-ref HEAD | xargs git fetch $1 && git reset --hard FETCH_HEAD' -

Fetch and checkout

Fetch the branch from the remote, create a tracking branch for it and check it out now 🎶the funk soul brother🎶.

It expects two arguments:

  • the name of the remote
  • the name of the branch
cofetch = !sh -c 'git fetch $1 $2:remotes/$1/$2 && git switch -c $2 remotes/$1/$2' -

Politely force push current branch

When force pushing it’s safer to use Git’s --force-with-lease as this ensures the latest changes were fetched before overwriting changes on the remote.

plush = !sh -c 'git push --force-with-lease $1 HEAD' -

Loglog

Fancy shortlog.

loglog = log --graph --oneline --all

Rerere

git-rerere stands for: Reuse recorded resolution of conflicted merges. It is an awesome feature that helps you to resolve the same conflicts over and over again.

[rerere]
        enabled = true

Markdown diffing

Below is an attempt to improve diffing markdown files. But I never got it working and I just left it here.

[diff "markdown"]
        tool = lowdown

[difftool "lowdown"]
        cmd = "lowdown-diff -s -Tman $LOCAL $REMOTE | groff -Tascii -man | less"

Emacs diff tool

Use emacs(client) as a difftool.

[difftool "ediffclient"]
        cmd = emacsclient --eval \"(ediff-files \\\"$LOCAL\\\" \\\"$REMOTE\\\")\"

LFS

These settings involving LFS are generated by git itself.

[filter "lfs"]
        smudge = git-lfs smudge -- %f
        process = git-lfs filter-process
        required = true
        clean = git-lfs clean -- %f

Protocol

Enable git protocol version 2.

Read about it.

[protocol]
        version = 2

Sendmail

Configure git to send mails directly from git.

Instructions came from git-send-email.io.

I have msmtp configured to send mails through Emacs, so I can just use that tool here too.

[sendemail]
    smtpserver = /usr/bin/msmtp

B4

I’m using b4 to send patches to the Git mailing list. This can use some configuration.

[b4]

Signing

Don’t sign mails with patatt.

send-no-patatt-sign = yes

Skip auto-CC

On the Git mailing list there isn’t an auto-CC script, so skip that step.

prep-pre-flight-checks = disable-needs-auto-to-cc

Global Gitignore

Ruby vendoring

Ignore gems installed in the local vendor directory.

**/vendor/bundle

GNU Global

For a while I used GNU Global for tagging code, so ignore the TAGS files from being committed.

GPATH
GRTAGS
GTAGS
TAGS

CCLS LSP

For C code I’m using CCLS with LSP in emacs. Ignore the files this creates.

.ccls-cache/

Emacs

Dir locals

Most project don’t like Emacs .dir-locals.el.

.dir-locals.el

Auto-save files

Emacs creates auto-save files named by appending # to the front and rear of the visited file name.

\#*\#

GitLab & GitHub

Do not ignore .gitlab and .gitlab-ci.yml, and be explicit about it. This will also make rg(1) search these. And do the same for GitHub’s files.

!.gitlab
!.gitlab-ci.yml
!.github

Compile flags

For LSP in Emacs you might need to specify extra compiler flags, but not all projects have it, so ignore files setting them.

compile_flags.txt
.ccls

There is also compile_commands.json, which needs to be generated. You can either use Bear to generate it. This is done by prefixing the make command with bear --, or tools like Meson generate it automatically.

compile_commands.json

Byebug

Byebug creates a history file in the current working directory, and that’s not something you want to check in.

.byebug_history

Git attributes

Diff

Improve diff output for various file types.

From: https://tekin.co.uk/2020/10/better-git-diff-output-for-ruby-python-elixir-and-more

*.c     diff=cpp
*.h     diff=cpp
*.c++   diff=cpp
*.h++   diff=cpp
*.cpp   diff=cpp
*.hpp   diff=cpp
*.cc    diff=cpp
*.hh    diff=cpp
*.cs    diff=csharp
*.css   diff=css
*.html  diff=html
*.xhtml diff=html
*.ex    diff=elixir
*.exs   diff=elixir
*.go    diff=golang
*.php   diff=php
*.pl    diff=perl
*.py    diff=python
*.md    diff=markdown
*.rb    diff=ruby
*.rake  diff=ruby
*.rs    diff=rust
*.lisp  diff=lisp
*.el    diff=lisp

Merge

Use mergiraf for automatically conflict resolution.

mergiraf languages --gitattributes
nil

Hooks

User-wide hooks.

Pre-push

When a Gemfile is found, and rubocop is enabled, run Rubocop on the files modified compared to the last merge commit. And since in our codebase everything is applied to master with a merge commit, this can be considered the upstream commit.

We also could have used @{upstream}, but that requires each branch to set it’s upstream branch, and that is not always the case.

if [[ -f Gemfile.lock && -x $(bundle exec which rubocop) ]]; then
  echo "> Running Rubocop.."
  git diff --name-only --diff-filter=d $(git log --merges -1 --pretty=format:%H) | xargs bundle exec rubocop
else
  exit 0
fi

Standalone commands

A section of small, standalone git command scripts

Abort

A short command to abort any action that is ongoing. That could be:

  • cherry-pick
  • rebase
  • merge
git_status=$(git status)

if [[ $git_status =~ 'currently cherry-picking' ]]; then
  git cherry-pick --abort
  echo "Ongoing cherry-pick aborted."
  exit 0
fi

if [[ $git_status =~ $(echo 'currently (editing a commit while )?rebasing') ]]; then
  git rebase --abort
  echo "Ongoing rebase aborted."
  exit 0
fi

if [[ $git_status =~ 'you are still merging' ]]; then
  git merge --abort
  echo "Ongoing merge aborted."
  exit 0
fi

echo >&2 "Nothing found to abort."
exit 1

Continue

A short command to continue any action that is ongoing. That could be:

  • cherry-pick
  • rebase
  • merge
git_status=$(git status)

if [[ $git_status =~ 'currently cherry-picking' ]]; then
  echo "Continuing ongoing cherry-pick."
  git cherry-pick --continue
  exit 0
fi

if [[ $git_status =~ $(echo 'currently (editing a commit while )?rebasing') ]]; then
  git rebase --continue
  echo "Continuing ongoing rebase."
  exit 0
fi

if [[ $git_status =~ 'you are still merging' ]]; then
  echo "Continuing ongoing merge."
  git commit
  exit 0
fi

echo >&2 "Nothing found to continue."
exit 1

Modified

Get all files that are modified.

There was some discussion on this on team chat.

git diff --name-only --diff-filter=d

Unlock

Sometimes your git can get locked, .git/index.lock in particular.

rm .git/index.lock

MR push

Push the current branch and create an MR. This uses the trailers from the commit template to add tags to the MR.

# Look up branch description first
branch="$(git rev-parse --abbrev-ref $commit)"
msg="$(git config branch.$branch.description || true)"

# Next try to find a commit that can be used as cover letter
commit="${COMMIT}"
if [ -z "${commit}" ]
then
  commit="$(git log --grep '--- b4-submit-tracking ---' --format=%H)"
fi
if [ -z "${commit}" ]
then
  commit="HEAD".
fi
if [ -z "${msg}" ]
then
  msg="$(git log -1 --format=%B ${commit})"
fi

title="$(echo "${msg}" | head -1)"
desc="$(echo "${msg}" | tail -n+3 | sed -z 's/\n/\\n/g')"
milestone="${MILESTONE:-$(git log -1 --format='%(trailers:key=Milestone,valueonly=true)' ${commit})}"
labels="$(git log -1 --format='%(trailers:key=Label,valueonly=true)' ${commit})"

opts=""

if [ -n "${milestone}" ]
then
  opts+=" -o merge_request.milestone=${milestone}"
fi

for l in ${labels}
do
  opts+=" -o merge_request.label=${l}"
done

opts+=" -o merge_request.label=group::git"

set -x

git push --set-upstream \
    -o merge_request.create \
    -o merge_request.title="${title}" \
    -o merge_request.description="${desc}" \
    $opts \
    ${@}