@jaspermayone's dotfiles
1{
2 lib,
3 config,
4 pkgs,
5 isDarwin,
6 ...
7}:
8with lib;
9{
10 options.jsp.git = {
11 enable = mkEnableOption "Git configuration";
12 };
13
14 config = mkIf config.jsp.git.enable {
15 programs.git = {
16 enable = true;
17 lfs.enable = true;
18
19 # Conditional includes for different work contexts
20 includes = [
21 {
22 path = pkgs.writeText "gitconfig-phishdir" ''
23 [user]
24 email = jasper.mayone@phish.directory
25 '';
26 condition = "gitdir/i:~/dev/projects/phishdirectory/**";
27 }
28 {
29 path = pkgs.writeText "gitconfig-singlefeather" ''
30 [user]
31 email = jasper.mayone@singlefeather.com
32 '';
33 condition = "gitdir/i:~/dev/work/singlefeather/**";
34 }
35 {
36 path = pkgs.writeText "gitconfig-mlh" ''
37 [user]
38 email = jasper.mayone@majorleaguehacking.com
39 '';
40 condition = "gitdir/i:~/dev/work/mlh/eng/**";
41 }
42 {
43 path = pkgs.writeText "gitconfig-patchwork" ''
44 [user]
45 email = jasper@patchworklabs.org
46 '';
47 condition = "gitdir/i:~/dev/patchwork/**";
48 }
49 {
50 path = pkgs.writeText "gitconfig-school" ''
51 [user]
52 email = mayonej@wit.edu
53 '';
54 condition = "gitdir/i:~/dev/school/**";
55 }
56 {
57 path = pkgs.writeText "gitconfig-personal" ''
58 [user]
59 email = me@jaspermayone.com
60 '';
61 condition = "gitdir/i:~/dev/personal/**";
62 }
63 ];
64
65 # Global git ignore
66 ignores = [
67 # Compiled source
68 "*.com"
69 "*.class"
70 "*.dll"
71 "*.exe"
72 "*.o"
73 "*.so"
74
75 # Packages
76 "*.7z"
77 "*.dmg"
78 "*.gz"
79 "*.iso"
80 "*.jar"
81 "*.rar"
82 "*.tar"
83 "*.zip"
84
85 # Logs
86 "*.log"
87
88 # OS generated files
89 ".DS_Store"
90 ".DS_Store?"
91 "*/.DS_Store"
92 "**/.DS_Store"
93 "._*"
94 ".Spotlight-V100"
95 ".Trashes"
96 "ehthumbs.db"
97 "Thumbs.db"
98
99 # Claude Code
100 ".llm-orc/*"
101 "CLAUDE.local.md"
102 ];
103
104 settings = {
105 alias = {
106 # Quick shortcuts
107 co = "checkout";
108 br = "branch";
109 ci = "commit";
110 st = "status";
111 unstage = "reset HEAD --";
112 last = "log -1 HEAD";
113 pushfwl = "push --force-with-lease --force-if-includes";
114
115 # View abbreviated SHA, description, and history graph of the latest 20 commits
116 l = "log --pretty=oneline -n 20 --graph --abbrev-commit";
117 lg = "log --oneline --graph --decorate";
118
119 # View the current working tree status using the short format
120 s = "status -s";
121
122 # Show the diff between the latest commit and the current state
123 d = "!git diff-index --quiet HEAD -- || clear; git --no-pager diff --patch-with-stat";
124
125 # `git di $number` shows the diff between the state `$number` revisions ago and the current state
126 di = "!d() { git diff --patch-with-stat HEAD~$1; }; git diff-index --quiet HEAD -- || clear; d";
127
128 # Pull in remote changes for the current repository and all its submodules
129 p = "pull --recurse-submodules";
130
131 # Clone a repository including all submodules
132 c = "clone --recursive";
133
134 # Commit all changes
135 ca = "!git add ':(exclude,attr:builtin_objectmode=160000)' && git commit -av";
136
137 # Switch to a branch, creating it if necessary
138 go = "!f() { git checkout -b \"$1\" 2> /dev/null || git checkout \"$1\"; }; f";
139
140 # Show verbose output about tags, branches or remotes
141 tags = "tag -l";
142 branches = "branch --all";
143 remotes = "remote --verbose";
144
145 # List aliases
146 aliases = "config --get-regexp alias";
147
148 # Amend the currently staged files to the latest commit
149 amend = "commit --amend --reuse-message=HEAD";
150
151 # Credit an author on the latest commit
152 credit = "!f() { git commit --amend --author \"$1 <$2>\" -C HEAD; }; f";
153
154 # Interactive rebase with the given number of latest commits
155 reb = "!r() { git rebase -i HEAD~$1; }; r";
156
157 # Remove the old tag with this name and tag the latest commit with it
158 retag = "!r() { git tag -d $1 && git push origin :refs/tags/$1 && git tag $1; }; r";
159
160 # Find branches containing commit
161 fb = "!f() { git branch -a --contains $1; }; f";
162
163 # Find tags containing commit
164 ft = "!f() { git describe --always --contains $1; }; f";
165
166 # Find commits by source code
167 fc = "!f() { git log --pretty=format:'%C(yellow)%h %Cblue%ad %Creset%s%Cgreen [%cn] %Cred%d' --decorate --date=short -S$1; }; f";
168
169 # Find commits by commit message
170 fm = "!f() { git log --pretty=format:'%C(yellow)%h %Cblue%ad %Creset%s%Cgreen [%cn] %Cred%d' --decorate --date=short --grep=$1; }; f";
171
172 # Remove branches that have already been merged with main (a.k.a. 'delete merged')
173 dm = "!git branch --merged | grep -v '\\\\*' | xargs -n 1 git branch -d";
174
175 # List contributors with number of commits
176 contributors = "shortlog --summary --numbered";
177
178 # Show the user email for the current repository
179 whoami = "config user.email";
180 };
181
182 user = {
183 name = "Jasper Mayone";
184 email = "me@jaspermayone.com";
185 signingKey = "14D0D45A1DADAAFA";
186 };
187
188 init.defaultBranch = "main";
189
190 apply.whitespace = "fix";
191
192 core = {
193 editor = "code --wait";
194 pager = "less";
195 # Treat spaces before tabs and trailing whitespace as errors
196 whitespace = "space-before-tab,-indent-with-non-tab,trailing-space";
197 # Make `git rebase` safer on macOS
198 trustctime = false;
199 # Prevent showing files with non-ASCII names as unversioned
200 precomposeunicode = false;
201 # Speed up commands involving untracked files
202 untrackedCache = true;
203 };
204
205 color = {
206 ui = "auto";
207 branch = {
208 current = "yellow reverse";
209 local = "yellow";
210 remote = "green";
211 };
212 };
213
214 diff = {
215 algorithm = "histogram";
216 tool = "windsurf";
217 renames = "copies"; # Detect copies as well as renames
218 };
219
220 "difftool \"windsurf\"".cmd = "windsurf --diff $LOCAL $REMOTE";
221
222 # Binary file diff using hexdump
223 "diff \"bin\"".textconv = "hexdump -v -C";
224
225 # Bun lockfile diff
226 "diff \"lockb\"" = {
227 textconv = "bun";
228 binary = true;
229 };
230
231 # Include summaries of merged commits in merge commit messages
232 merge.log = true;
233
234 pull.rebase = true;
235
236 push = {
237 default = "simple";
238 followTags = true;
239 autoSetupRemote = true;
240 };
241
242 rebase.autoStash = true;
243
244 status = {
245 submoduleSummary = true;
246 showUntrackedFiles = "all";
247 };
248
249 tag = {
250 sort = "version:refname";
251 forceSignAnnotated = true;
252 gpgsign = true;
253 };
254
255 versionsort = {
256 prereleaseSuffix = [
257 "-pre"
258 ".pre"
259 "-beta"
260 ".beta"
261 "-rc"
262 ".rc"
263 ];
264 };
265
266 commit.gpgSign = true;
267
268 gpg = {
269 program = if isDarwin then "/opt/homebrew/bin/gpg" else "gpg";
270 format = "openpgp";
271 };
272
273 help.autocorrect = 1;
274
275 # URL shorthands
276 "url \"git@github.com:\"" = {
277 insteadOf = "gh:";
278 pushInsteadOf = "https://github.com/";
279 };
280 "url \"git://github.com/\"".insteadOf = "github:";
281 "url \"git@gist.github.com:\"".insteadOf = "gst:";
282 "url \"git://gist.github.com/\"".insteadOf = "gist:";
283
284 sequence.editor = "code --wait";
285
286 branch.sort = "-committerdate";
287 column.ui = "auto";
288 } // (if isDarwin then {
289 # macOS specific
290 credential = {
291 helper = "osxkeychain";
292 };
293 "credential \"https://dev.azure.com\"".useHttpPath = true;
294 } else { });
295 };
296
297 # Delta for better diffs
298 programs.delta = {
299 enable = true;
300 options = {
301 navigate = true;
302 light = false;
303 line-numbers = true;
304 };
305 };
306
307 # GitHub CLI
308 programs.gh = {
309 enable = true;
310 settings = {
311 git_protocol = "ssh";
312 };
313 };
314
315 # Lazygit
316 programs.lazygit = {
317 enable = true;
318 settings = {
319 gui.theme = {
320 lightTheme = false;
321 activeBorderColor = [
322 "blue"
323 "bold"
324 ];
325 inactiveBorderColor = [ "black" ];
326 selectedLineBgColor = [ "default" ];
327 };
328 };
329 };
330
331 # GitHub Dashboard
332 programs.gh-dash = {
333 enable = true;
334 settings = {
335 prSections = [
336 {
337 title = "Mine";
338 filters = "is:open author:@me updated:>={{ nowModify \"-3w\" }} sort:updated-desc archived:false";
339 layout.author.hidden = true;
340 }
341 {
342 title = "Review";
343 filters = "sort:updated-desc is:pr is:open review-requested:jaspermayone archived:false";
344 }
345 {
346 title = "All";
347 filters = "sort:updated-desc is:pr is:open user:@me archived:false";
348 }
349 ];
350 issuesSections = [
351 {
352 title = "Assigned";
353 filters = "is:issue state:open archived:false assignee:@me sort:updated-desc";
354 }
355 {
356 title = "Created";
357 filters = "author:@me is:open archived:false";
358 }
359 {
360 title = "All";
361 filters = "is:issue involves:@me archived:false sort:updated-desc is:open";
362 }
363 ];
364 defaults = {
365 view = "prs";
366 refetchIntervalMinutes = 5;
367 layout.prs = {
368 repoName = {
369 grow = true;
370 width = 10;
371 hidden = false;
372 };
373 base.hidden = true;
374 };
375 preview = {
376 open = true;
377 width = 84;
378 };
379 prsLimit = 20;
380 issuesLimit = 20;
381 };
382 repoPaths = {
383 "jaspermayone/*" = "~/dev/personal/*";
384 "phishdirectory/*" = "~/dev/projects/phishdirectory/*";
385 };
386 keybindings = {
387 universal = [
388 {
389 key = "g";
390 name = "lazygit";
391 command = "cd {{.RepoPath}} && lazygit";
392 }
393 ];
394 prs = [
395 {
396 key = "O";
397 builtin = "checkout";
398 }
399 {
400 key = "m";
401 command = "gh pr merge --admin --repo {{.RepoName}} {{.PrNumber}}";
402 }
403 {
404 key = "a";
405 name = "lazygit add";
406 command = "cd {{.RepoPath}} && git add -A && lazygit";
407 }
408 {
409 key = "v";
410 name = "approve";
411 command = "gh pr review --repo {{.RepoName}} --approve --body \"$(gum input --prompt='Approval Comment: ')\" {{.PrNumber}}";
412 }
413 ];
414 };
415 theme = {
416 ui = {
417 sectionsShowCount = true;
418 table.compact = false;
419 };
420 };
421 };
422 };
423 };
424}