As I was updating some gitflow config today, I noticed that my .gitconfig is a little messy. Like many things, it's grown organically and I've just never thought about tidying it up. It doesn't have to be organized, but neat and tidy files always make things more fun.

Sorting .gitconfig was a bit more challenging than I was expecting. A naive sort mixes sections and values.

$ cat ~/.gitconfig | sort
autocorrect = 1
autocrlf = input
clean = git-lfs clean %f
[core]
default = matching
editor = nano
[filter "lfs"]
[help]
[push]
required = true
smudge = git-lfs smudge %f
[user]

A more complicated sort might be able to get the job done, but I wasn't able to figure out a good way to set up the fields and keys properly. If you have a pure sort solution, I'd love to see it.

I spent a good chunk of time glossing over awk array sorts and block sorting techniques trying to find a solution I thought I'd read once. Then it hit me. Rather than trying to pipe several clever awk and sort combos, I could just add more information, sort, and remove the extra information.

First thing to do is sanitize the leading spaces. I typically replace tabs with spaces but git likes tabbed files.

$ cat ~/.gitconfig \
| sed 's/ +/\t/g'
[filter "lfs"]
clean = git-lfs clean %f
smudge = git-lfs smudge %f
required = true
[help]
autocorrect = 1
[core]
autocrlf = input
editor = nano
[push]
default = matching

With a guaranteed tab, we can abuse the field separator in awk to access the section headings.

$ cat ~/.gitconfig \
| sed 's/ +/\t/g' \
| awk -F '\t' '$1 { print $1 }'
[filter "lfs"]
[user]
[help]
[core]
[push]

By the same token, we can also access the config values.

$ cat ~/.gitconfig \
| sed 's/ +/\t/g' \
| awk -F '\t' '$2 { print $2 }'
clean = git-lfs clean %f
smudge = git-lfs smudge %f
required = true
autocorrect = 1
autocrlf = input
editor = nano
default = matching

By putting the two together, we make a more verbose line perfect for sorting.

$ cat ~/.gitconfig \
| sed 's/ +/\t/g' \
| awk -F '\t' '$1 { current = $1; print current } $2 { print current "\t" $2}'
[filter "lfs"]
[filter "lfs"] clean = git-lfs clean %f
[filter "lfs"] smudge = git-lfs smudge %f
[filter "lfs"] required = true
[help]
[help] autocorrect = 1
[core]
[core] autocrlf = input
[core] editor = nano
[push]
[push] default = matching
$ cat ~/.gitconfig \
| sed 's/ +/\t/g' \
| awk -F '\t' '$1 { current = $1; print current } $2 { print current "\t" $2}' \
| sort
[core]
[core] autocrlf = input
[core] editor = nano
[filter "lfs"]
[filter "lfs"] clean = git-lfs clean %f
[filter "lfs"] required = true
[filter "lfs"] smudge = git-lfs smudge %f
[help]
[help] autocorrect = 1
[push]
[push] default = matching

We can regain the headings by looking for entries without a second field.

$ cat ~/.gitconfig \
| sed 's/ +/\t/g' \
| awk -F '\t' '$1 { current = $1; print current } $2 { print current "\t" $2}' \
| sort \
| awk -F '\t' '!$2 {print $1}'
[core]
[filter "lfs"]
[help]
[push]
[user]

Similarly, the values can be pulled out from rows with both a first and second field.

$ cat ~/.gitconfig \
| sed 's/ +/\t/g' \
| awk -F '\t' '$1 { current = $1; print current } $2 { print current "\t" $2}' \
| sort \
| awk -F '\t' '$1 && $2 { print $2 }'
autocrlf = input
editor = nano
clean = git-lfs clean %f
required = true
smudge = git-lfs smudge %f
autocorrect = 1
default = matching

Now it's just a simple matter of putting everything back together.

$ cat ~/.gitconfig \
| sed 's/ +/\t/g' \
| awk -F '\t' '$1 { current = $1; print current } $2 { print current "\t" $2}' \
| sort \
| awk -F '\t' '!$2 {print $1} $1 && $2 { print "\t" $2 }'
[core]
autocrlf = input
editor = nano
[filter "lfs"]
clean = git-lfs clean %f
required = true
smudge = git-lfs smudge %f
[help]
autocorrect = 1
[push]
default = matching