Robert Cranston authored on 19/04/2026 03:01:45
Showing 2 changed files

... ...
@@ -1,15 +1,46 @@
1
+[user]
2
+	; Don't try to guess `user.name` and `user.email`.
3
+	useConfigOnly = true
4
+
1 5
 [include]
2 6
 	path = ~/.gitconfig-user
3 7
 
4 8
 [core]
5
-	; Git sets `$LESS` to `FRX` if it's not already set. `X` disables
6
-	; sending termcap initialization and deinitialization strings to the
7
-	; terminal, which leaves (potentially log) text on the screen and
8
-	; disables mouse scrolling.
9
-	pager = less -+X
9
+	; If the `LESS` environment variable is unset, Git (naively) prepends
10
+	; `LESS=FRX` to the shell expression given in `pager` (or it's
11
+	; default). `X` disables sending termcap (alternate screen)
12
+	; initialization and deinitialization strings to the terminal, which
13
+	; disables mouse scrolling and makes long text scroll past previous
14
+	; context.
15
+	; pager = LESS=FR less
16
+	pager = diff-highlight | LESS=FR less
17
+	; TODO: E.g. `attributesFile` defaults to
18
+	; `$XDG_CONFIG_HOME/git/attributes`. Maybe this is a better place to
19
+	; put these?
10 20
 	hooksPath      = ~/.githooks
11 21
 	excludesFile   = ~/.gitignore
12 22
 	attributesFile = ~/.gitattributes
23
+	; Print paths verbatim (e.g. UTF-8).
24
+	quotePath = false
25
+	; Disable newline conversion (mangling). This relies on editors *also*
26
+	; not doing any conversions or else you will get huge useless diffs.
27
+	; Also be aware of text files copied verbatim from somewhere else such
28
+	; as the internet. The Git default of having only LF in commits (autocrlf = false), but sometimes .
29
+	; autocrlf = input
30
+	; If we re-enable conversion (see above), reject on failed roundtrip.
31
+	safecrlf = true
32
+
33
+; [pager]
34
+; 	; As suggested in `contrib/diff-highlight/README`, except as explained
35
+; 	; in `core.pager` above.
36
+; 	log  = diff-highlight | less -+X
37
+; 	show = diff-highlight | less -+X
38
+; 	diff = diff-highlight | less -+X
39
+
40
+[interactive]
41
+	; TODO: It is untested if this is needed or happens by virtue of
42
+	; `core.pager`. Also untested if it has any ill effects.
43
+	diffFilter = diff-highlight
13 44
 
14 45
 [init]
15 46
 	templateDir = ~/.gittemplate
... ...
@@ -20,8 +51,12 @@
20 51
 	pushDefault = origin
21 52
 
22 53
 [push]
23
-	default = current
54
+	default = current ; This obviates `git push --set-upstream`.
55
+	; In the past, `current` was commented out and we used `upstream`
56
+	; instead, unsure why.
57
+	; default = upstream
24 58
 	recurseSubmodules = check
59
+	autoSetupRemote = true ; Git 2.37
25 60
 
26 61
 [branch]
27 62
 	autoSetupMerge  = always
... ...
@@ -32,10 +67,12 @@
32 67
 	pruneTags = true
33 68
 
34 69
 [rebase]
35
-	autoSquash = true
36
-	autoStash  = true
37
-	updateRefs = true
70
+	autoSquash   = true
71
+	autoStash    = true
72
+	rebaseMerges = true
73
+	updateRefs   = true
38 74
 	missingCommitsCheck = error
75
+	instructionFormat = %s%nexec GIT_COMMITTER_DATE='%ci' GIT_AUTHOR_DATE='%ai' git commit --amend --no-edit --reset-author
39 76
 
40 77
 [pull]
41 78
 	rebase = merges
... ...
@@ -44,6 +81,8 @@
44 81
 	ff = false
45 82
 	autoStash = true
46 83
 	conflictStyle = diff3
84
+	; TODO: `zdiff3` removes edge common lines but is too new?
85
+	; conflictStyle = zdiff3
47 86
 
48 87
 [am]
49 88
 	threeWay = true
... ...
@@ -96,7 +135,7 @@
96 135
 	submoduleSummary = true
97 136
 
98 137
 [log]
99
-	abbrevCommit = true
138
+	; abbrevCommit = true
100 139
 	decorate = short
101 140
 
102 141
 [log]
... ...
@@ -115,74 +154,96 @@
115 154
 	  BrightCyan
116 155
 
117 156
 [diff]
118
-	colorMoved   = zebra
119
-	colorMovedWS = ignore-space-change
157
+	wsErrorHighlight = all
158
+	colorMoved       = zebra
159
+	colorMovedWS     = ignore-space-change
120 160
 
121 161
 [color "diff"]
162
+	; TODO: Harmonize with `vimdiff`?
163
+	; TODO: Use `dim` and remove `Bright`? Terminal support?
164
+	commit                    = BrightYellow
122 165
 	meta                      = BrightBlue
123 166
 	frag                      =       Blue
124 167
 	func                      =       Blue
125
-	commit                    = BrightYellow
126
-	whitespace                = BrightRed
127
-	plain                     =       Yellow
128 168
 	context                   = BrightWhite
129
-	contextBold               = BrightWhite
130
-	contextDimmed             =       White
169
+	contextBold               = BrightWhite   reverse
170
+	contextDimmed             =       White   reverse
131 171
 	old                       = BrightRed
132
-	oldBold                   = BrightRed
133
-	oldDimmed                 =       Red
172
+	oldBold                   = BrightRed     reverse
173
+	oldDimmed                 =       Red     reverse
134 174
 	new                       = BrightGreen
135
-	newBold                   = BrightGreen
136
-	newDimmed                 =       Green
175
+	newBold                   = BrightGreen   reverse
176
+	newDimmed                 =       Green   reverse
137 177
 	oldMoved                  = BrightMagenta
138
-	oldMovedDimmed            =       Magenta
178
+	oldMovedDimmed            = BrightMagenta
139 179
 	oldMovedAlternative       =       Magenta
140 180
 	oldMovedAlternativeDimmed =       Magenta
141 181
 	newMoved                  = BrightCyan
142
-	newMovedDimmed            =       Cyan
182
+	newMovedDimmed            = BrightCyan
143 183
 	newMovedAlternative       =       Cyan
144 184
 	newMovedAlternativeDimmed =       Cyan
185
+	whitespace                = BrightRed     reverse
186
+
187
+[color "diff-highlight"]
188
+	; TODO: Harmonize with `vimdiff`?
189
+	oldNormal    = BrightRed
190
+	newNormal    = BrightGreen
191
+	oldHighlight = BrightRed   Red
192
+	newHighlight = BrightGreen Green
145 193
 
146 194
 [blame]
147
-	coloring = repeatedLines
148
-	; coloring = highlightRecent
195
+	; coloring = repeatedLines
196
+	coloring = highlightRecent
149 197
 
150 198
 [color "blame"]
151
-	repeatedLines = White
199
+	; repeatedLines = White
152 200
 	highlightRecent = \
153
-	  245, 1  years ago, \
154
-	  246, 6 months ago, \
155
-	  247, 5 months ago, \
156
-	  248, 4 months ago, \
157
-	  249, 3 months ago, \
158
-	  250, 2 months ago, \
159
-	  251, 4  weeks ago, \
160
-	  252, 3  weeks ago, \
161
-	  253, 2  weeks ago, \
162
-	  254, 1  weeks ago, \
201
+	  245, 2  years ago, \
202
+	  246, 1   year ago, \
203
+	  247, 6 months ago, \
204
+	  248, 3 months ago, \
205
+	  249, 1 months ago, \
206
+	  250, 2  weeks ago, \
207
+	  251, 1  weeks ago, \
208
+	  252, 5   days ago, \
209
+	  253, 2   days ago, \
210
+	  254, 1   days ago, \
163 211
 	  255
164 212
 
165 213
 [absorb]
166 214
 	; https://github.com/tummychow/git-absorb
167 215
 	maxStack = 100
168 216
 
217
+; [instaweb]
218
+; 	; TODO: There seems to be bugs with both `local` and `python` in
219
+; 	; `git-instaweb`! Look at, fix, and sumbit patches for them.
220
+; 	local = true
221
+; 	httpd = python
222
+
169 223
 [pretty]
170
-	patch    =  format:%C(BrightYellow)commit %h%C(auto)%d%n%C(BrightYellow)Author: %aN <%aE>%n%C(BrightYellow)Date:   %ad%n%n%C(Yellow)%s%n
171
-	compact  = tformat:%C(BrightYellow)%h%x20%C(BrightMagenta)%>(13,trunc)%ar%x20%C(BrightBlue)%<(15,trunc)%aN%x20%C(BrightWhite)%s%C(auto)%d
172
-	compactt = tformat:%C(BrightYellow)%h%x09%C(BrightMagenta)%>(1       )%ar%x09%C(BrightBlue)%<(1       )%aN%x09%C(BrightWhite)%s%C(auto)%d
224
+	compact   = tformat:%C(BrightYellow)%h%C(auto) %C(BrightWhite)%s%C(auto)%d
225
+	columnars = tformat:%C(BrightYellow)%h%x20%C(BrightMagenta)%>(13,trunc)%ar%x20%C(BrightBlue)%<(15,trunc)%aN%x20%C(BrightWhite)%s%C(auto)%d
226
+	columnart = tformat:%C(BrightYellow)%h%x09%C(BrightMagenta)%>(1       )%ar%x09%C(BrightBlue)%<(1       )%aN%x09%C(BrightWhite)%s%C(auto)%d
227
+	patch     =  format:%C(BrightYellow)commit %h%C(auto)%d%n%C(BrightYellow)Author: %aN <%aE>%n%C(BrightYellow)Date:   %ad%n%n%C(Yellow)%s%n
173 228
 
174 229
 [alias]
175 230
 	; All.
231
+	; TODO: `eval` the supplied command (if any), so that one can use pipes
232
+	; and other shell features? That would require one to type `git all git
233
+	; <git-command>` (i.e. repeat `git` twice) for Git commands though.
234
+	; Actually, borrow an idea from git aliases themselves? If the command
235
+	; starts with `!` then strip it and do `eval "$@"`, otherwise do `git
236
+	; "$@"`?
176 237
 	all = "!f() { : ; \
177
-	  [ -t 1 ] && terminal=y; \
238
+	  [ -t 1 ] && tty=true; \
178 239
 	  git config --get-colorbool color.all && color_all=always; \
179 240
 	  git config --get-colorbool color.ui  && color_ui=always; \
180
-	  find . -type d -name '*.git' -prune \
181
-	  | sed 's#^\\./\\.git$\\|^\\./\\|/\\.git$##g' \
182
-	  | sort \
241
+	  find -L . -type d -name '*.git' -prune \
242
+	  | sed 's#^\\./\\|/\\.git$##g' \
243
+	  | LC_ALL=C sort \
183 244
 	  | awk '{ \
184 245
 	    for (parent in parents) \
185
-	      if ($0 ~ \"^\" parent) \
246
+	      if (index($0, parent \"/\") == 1) \
186 247
 	        next; \
187 248
 	    parents[$0]; \
188 249
 	    print; \
... ...
@@ -190,9 +251,9 @@
190 251
 	  | { \
191 252
 	    if [ $# -eq 0 ]; \
192 253
 	    then \
193
-	      if [ $terminal ]; \
254
+	      if [ \"$tty\" ]; \
194 255
 	      then \
195
-	        tree -n ${color_all+-C} --fromfile; \
256
+	        tree -n --fromfile; \
196 257
 	      else \
197 258
 	        cat; \
198 259
 	      fi; \
... ...
@@ -201,13 +262,13 @@
201 262
 	      then \
202 263
 	        get_color() { git -C \"$path\" config --get-color \"$@\"; }; \
203 264
 	        color_reset=$(get_color '' reset); \
204
-	        color_all_header=$(get_color color.all.header yellow); \
265
+	        color_all_header=$(get_color color.all.header \"BrightWhite White\"); \
205 266
 	      fi; \
206 267
 	      while read -r path; \
207 268
 	      do \
208 269
 	        output=$(git -C \"$path\" -c color.ui=$color_ui \"$@\" 2>&1); \
209 270
 	        [ \"$output\" ] || continue; \
210
-	        printf '%s%s%s\\n%s\\n\\n' \
271
+	        printf '%s# %s%s\\n%s\\n\\n' \
211 272
 	          \"$color_all_header\" \
212 273
 	          \"$path\" \
213 274
 	          \"$color_reset\" \
... ...
@@ -219,29 +280,87 @@
219 280
 	}; f"
220 281
 
221 282
 	; Tree.
283
+	; This could have been basically `git ls-files | tree --fromfile` but
284
+	; `--fromfile` doesn't look at the filesystem at all and misses
285
+	; permission bits for coloring. Instead, we use `tree`'s `-P` and `-I`
286
+	; even though they can only match on file basenames, so this solution
287
+	; is quite brittle. Also, `git ls-files` quotes "unusual" charactes,
288
+	; even with `git -c core.quotePath=false`. The only way around that is
289
+	; to pass `-z` but of course that uses null separators, which shell
290
+	; command substitution doesn't handle. So to convert the nulls to
291
+	; newlines we have to thread the return code out through extra file
292
+	; descriptors as a string.. Unix is elegant, right? Yes, I know about
293
+	; `-o pipefail`, that POSIX is too new for me :) It mostly works.
294
+	; Actually, fuck it, it turns out `tree`'s `-P` and `-I` are not
295
+	; anchored, even though they support `*` but no anchoring mechanism, so
296
+	; we get even more false positives than expected, so we do without the
297
+	; absolutely correct coloring for now.
222 298
 	tree = "!f() { : git ls-files ; \
223 299
 	  git config --get-colorbool color.tree && color_tree=always; \
224
-	  git ls-files \"$@\" | tree -n ${color_tree+-C} --fromfile; \
300
+	  files=\"$( \
301
+	    ((( \
302
+	      (git ls-files -z \"$@\"; echo $? >&3) | sed 's/\\x00/\\n/g' >&4 \
303
+	    ) 3>&1) | exit $(cat)) 4>&1 \
304
+	  )\" || return; \
305
+	  printf '%s\\n' \"$files\" \
306
+	  | tree -a -n ${color_tree+-C} --fromfile; \
307
+	  return; \
308
+	  dirs=\"$( \
309
+	    printf '%s\\n' \"$files\" \
310
+	    | sed -n 's#\\([^/]\\+\\)/.*#\\1#p' \
311
+	    | sort -u \
312
+	    | paste -sd '|' \
313
+	  )\"; \
314
+	  pattern=\"$( \
315
+	    printf '%s\\n' \"$files\" \
316
+	    | sed 's#/#\\n#g' \
317
+	    | sort -u \
318
+	    | paste -sd '|' \
319
+	  )\"; \
320
+	  ignore=\"$( \
321
+	    find . \\! -name '.' -type d -prune \
322
+	    | sed 's#^./##' \
323
+	    | grep -Ev \"$dirs\" \
324
+	    | paste -sd '|' \
325
+	  )\"; \
326
+	  tree -a -n ${color_tree+-C} \
327
+	    --matchdirs \
328
+	    --prune \
329
+	    -P \"$pattern\" \
330
+	    -I \"$ignore\"; \
331
+	}; f"
332
+
333
+	; Clone user repo
334
+	; `git clone-user-repo */$user/$repo` =>
335
+	; `git clone           */$user/$repo $user/repo`
336
+	clone-user-repo ="!f() { : git clone ; \
337
+	  url() { shift $(($#-1)); echo \"$1\"; }; \
338
+	  url=\"$(url \"$@\")\"; \
339
+	  url=\"${url%/}\"; \
340
+	  repo=\"${url##*/}\"; \
341
+	  user=\"${url%/*}\"; \
342
+	  user=\"${user##*/}\"; \
343
+	  git clone \"$@\" \"$user/$repo\"; \
225 344
 	}; f"
226 345
 
227 346
 	; Users.
228 347
 	users = "!f() { : git log ; \
229 348
 	  git log --pretty=full \"$@\" \
230 349
 	  | sed -n 's/^\\(Author\\|Commit\\): \\(.*\\)/\\2/p' \
231
-	  | sort -u; \
350
+	  | LC_ALL=C sort -u; \
232 351
 	}; f"
233 352
 
234 353
 	; Browse.
235 354
 	browse ="!f() { : ; \
236
-	  set --; \
237
-	  urls=\"$(git remote get-url ${@:-$(git remote)})\"; \
238
-	  for url in $urls; \
355
+	  [ $# != 0 ] || set -- $(git remote); \
356
+	  for remote in \"$@\"; \
239 357
 	  do \
240
-	    echo $url; \
358
+	    url=\"$(git remote get-url \"$remote\")\"; \
359
+	    printf '%s: %s\\n' \"$remote\" \"$url\"; \
241 360
 	    { [ $(command -v xdg-open) ] && xdg-open \"$url\"; } || \
242 361
 	    { [ $(command -v open)     ] && open     \"$url\"; } || \
243 362
 	    { [ $(command -v start)    ] && start    \"$url\"; } || \
244
-	    echo \"$@\"; \
363
+	    true; \
245 364
 	  done; \
246 365
 	}; f"
247 366
 
... ...
@@ -260,37 +379,57 @@
260 379
 	  | eval \"LESS=${LESS-FRX} $(git var GIT_PAGER)\"; \
261 380
 	}; f"
262 381
 
263
-	; Filter meta.
264
-	filter-meta = "!f() { : git filter-branch ; \
382
+	; Reset user.
383
+	reset-user = "!f() { : git filter-branch ; \
265 384
 	  name=\"$1\";  shift; \
266 385
 	  email=\"$1\"; shift; \
267
-	  git filter-branch \"$@\" --env-filter \" \
386
+	  git filter-branch --env-filter \" \
268 387
 	    GIT_AUTHOR_NAME=\\\"$name\\\"; \
269 388
 	    GIT_AUTHOR_EMAIL=\\\"$email\\\"; \
270 389
 	    GIT_COMMITTER_NAME=\\\"$name\\\"; \
271 390
 	    GIT_COMMITTER_EMAIL=\\\"$email\\\"; \
272 391
 	    GIT_COMMITTER_DATE=\\\"\\$GIT_AUTHOR_DATE\\\"; \
273
-	  \" ; \
392
+	  \" \"$@\" ; \
393
+	}; f"
394
+
395
+	; Reset date.
396
+	reset-date = "!f() { : git filter-branch ; \
397
+	  git filter-branch --env-filter ' \
398
+	    sleep 1; \
399
+	    date=\"$(date --iso-8601=s)\"; \
400
+	    GIT_AUTHOR_DATE=\"$date\"; \
401
+	    GIT_COMMITTER_DATE=\"$date\"; \
402
+	  ' \"$@\" ; \
274 403
 	}; f"
275 404
 
276 405
 	; Log.
406
+	; TODO: Add `--no-merges` and `--merges` (or `--first-parent`) aliases?
407
+	; They fit well with the workflow of my personal projects.
408
+	; TODO: If would be cool to add like a `=> ${u}` to the decoration to
409
+	; also show the tracking branch if any.
410
+	; TODO: Add `--shortstat` info?
411
+	; <https://til.codeinthehole.com/posts/how-to-add-commit-sizes-to-git-log-output/>
412
+	; TODO: Can we do nicer (Unicode) graphs?
413
+	; - <https://github.com/rbong/vim-flog/issues/49>
277 414
 	l = "!f() { : git log ; \
278 415
 	  git config --get-colorbool color.diff && color_diff=always; \
279
-	  git -c color.diff=$color_diff log --graph --pretty=compactt \"$@\" \
416
+	  git -c color.diff=$color_diff log --graph --pretty=columnart \"$@\" \
280 417
 	  | sed -E 's/^([^\\t]+\\t[^\\t,]+)(,.+)? ago(.*\\t.*)$/\\1\\3/' \
281 418
 	  | sed -E 's/^(.*) +(.*[0-9a-f]{7}.*\\t)/\\1\\t\\2/' \
282 419
 	  | sed -E 's/ *$//' \
283 420
 	  | column -t -o ' ' -s \"$(printf '\\t')\" \
284 421
 	  | eval \"LESS=${LESS-FRX} $(git var GIT_PAGER)\"; \
285 422
 	}; f"
286
-	ls = log --oneline --decorate=no
287
-	lf = log --first-parent
288
-	lp = log --patch --pretty=patch
423
+	lb = log --topo-order --graph --pretty=compact --simplify-by-decoration ; `git-show-tree` from `git-extras` is similar.
424
+	ls = log --topo-order --graph --pretty=compact
425
+	ll = log --topo-order --graph --pretty=columnars
426
+	lp = log --topo-order --patch --pretty=patch --find-copies-harder --irreversible-delete
289 427
 	lpw  = lp --color-words='[_[:alnum:]]+|[^[:space:]]'
290 428
 	lpww = lp --color-words='.'
291 429
 	la    = l    --all
430
+	lba   = lb   --all
292 431
 	lsa   = ls   --all
293
-	lfa   = lf   --all
432
+	lla   = ll   --all
294 433
 	lpa   = lp   --all
295 434
 	lpwa  = lpw  --all
296 435
 	lpwwa = lpww --all
... ...
@@ -304,9 +443,9 @@
304 443
 	sba = sb --ignored
305 444
 
306 445
 	; Diff.
307
-	d  = diff
308
-	du = diff @{upstream}
309
-	dp = diff @{push}
446
+	d  = diff --find-copies-harder --irreversible-delete
447
+	du = diff --find-copies-harder --irreversible-delete @{upstream}
448
+	dp = diff --find-copies-harder --irreversible-delete @{push}
310 449
 	dc  = d  --cached
311 450
 	duc = du --cached
312 451
 	dpc = dp --cached
... ...
@@ -332,8 +471,8 @@
332 471
 	ca = commit --amend
333 472
 
334 473
 	; Rebase.
335
-	ri = rebase -i
336
-	ro = rebase -i --root
474
+	ri = rebase -i --rebase-merges
475
+	ro = rebase -i --rebase-merges --root
337 476
 	rr = rebase --continue
338 477
 
339 478
 	; Fetch.
... ...
@@ -347,5 +486,7 @@
347 486
 	x = clean -Xd
348 487
 
349 488
 	; Contains.
489
+	; contains = name-rev --name-only
350 490
 	contains = tag --contains
491
+	; contains = branch --contains
351 492
 	; contains = describe --contains
... ...
@@ -1,15 +1,32 @@
1 1
 #!/bin/sh
2 2
 set -euC
3 3
 
4
-# Where `diff-highlight` is located and whether it's executable varies between
5
-# distros, so try a few common places and run the shebang manually.
4
+# Distros (or upstream git, I'm not sure) make it very annoying to actually use
5
+# the shipped `diff-highlight`. The following things vary depending on distro
6
+# and version:
7
+# - Where it is located
8
+# - If it has a file extension
9
+# - Whether it's executable
10
+# - Whether has a shebang
11
+# Therefore, we are left with this shim which does its best to actually run the
12
+# thing.
6 13
 
7 14
 for file in \
8
-  '/usr/share/doc/git/contrib/diff-highlight/diff-highlight' \
9
-  '/usr/share/git-core/contrib/diff-highlight' \
10
-  '/usr/share/git/diff-highlight/diff-highlight'
15
+  '/usr/share/doc/git/contrib/diff-highlight/diff-highlight'* \
16
+  '/usr/share/git-core/contrib/diff-highlight'* \
17
+  '/usr/share/git/diff-highlight/diff-highlight'*
11 18
 do
12
-  [ -e "$file" ] && break
19
+  # Does it exist?
20
+  [ -r "$file" ] || continue
21
+  # Is it executable?
22
+  [ -x "$file" ] && exec "$file" || true
23
+  # Does it have a shebang?
24
+  interp="$(sed -n '1s/^#!//p' "$file")"
25
+  [ "$interp" ] && exec $interp "$file" "$@" || true
26
+  # Does it have a file extension which matches a command?
27
+  ext="${file##*.}"
28
+  [ "$(command -v "$ext")" ] && exec "$ext" "$file" "$@" || true
13 29
 done
14
-prog="$(sed -n '1s/^#!//p' "$file")"
15
-exec $prog "$file" "$@"
30
+
31
+# Fallback.
32
+exec cat