treewide: implement auto importing for modules

Reduce maintenance burden and increase efficiency by automatically
importing modules following a specific convention.

Co-authored-by: awwpotato <awwpotato@voidq.com>
Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
This commit is contained in:
Austin Horstman 2025-06-20 14:39:55 -05:00
parent fefeb0e928
commit 4fca600cb1
461 changed files with 72 additions and 474 deletions

View file

@ -0,0 +1,936 @@
{
config,
lib,
pkgs,
...
}:
let
inherit (lib)
concatStringsSep
literalExpression
mkDefault
mkEnableOption
mkIf
mkOption
mkOptionDefault
mkPackageOption
types
;
cfg = config.programs.git;
gitIniType =
with types;
let
primitiveType = either str (either bool int);
multipleType = either primitiveType (listOf primitiveType);
sectionType = attrsOf multipleType;
supersectionType = attrsOf (either multipleType sectionType);
in
attrsOf supersectionType;
includeModule = types.submodule (
{ config, ... }:
{
options = {
condition = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
Include this configuration only when {var}`condition`
matches. Allowed conditions are described in
{manpage}`git-config(1)`.
'';
};
path = mkOption {
type = with types; either str path;
description = "Path of the configuration file to include.";
};
contents = mkOption {
type = types.attrsOf types.anything;
default = { };
example = literalExpression ''
{
user = {
email = "bob@work.example.com";
name = "Bob Work";
signingKey = "1A2B3C4D5E6F7G8H";
};
commit = {
gpgSign = true;
};
};
'';
description = ''
Configuration to include. If empty then a path must be given.
This follows the configuration structure as described in
{manpage}`git-config(1)`.
'';
};
contentSuffix = mkOption {
type = types.str;
default = "gitconfig";
description = ''
Nix store name for the git configuration text file,
when generating the configuration text from nix options.
'';
};
};
config.path = mkIf (config.contents != { }) (
mkDefault (
pkgs.writeText (lib.hm.strings.storeFileName config.contentSuffix) (
lib.generators.toGitINI config.contents
)
)
);
}
);
in
{
meta.maintainers = with lib.maintainers; [
khaneliman
rycee
];
options = {
programs.git = {
enable = mkEnableOption "Git";
package = lib.mkPackageOption pkgs "git" {
example = "pkgs.gitFull";
extraDescription = ''
Use {var}`pkgs.gitFull`
to gain access to {command}`git send-email` for instance.
'';
};
userName = mkOption {
type = types.nullOr types.str;
default = null;
description = "Default user name to use.";
};
userEmail = mkOption {
type = types.nullOr types.str;
default = null;
description = "Default user email to use.";
};
aliases = mkOption {
type = types.attrsOf types.str;
default = { };
example = {
co = "checkout";
};
description = "Git aliases to define.";
};
signing = {
key = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
The default signing key fingerprint.
Set to `null` to let the signer decide what signing key
to use depending on commits author.
'';
};
format = mkOption {
type = types.nullOr (
types.enum [
"openpgp"
"ssh"
"x509"
]
);
defaultText = literalExpression ''
"openpgp" for state version < 25.05,
undefined for state version 25.05
'';
description = ''
The signing method to use when signing commits and tags.
Valid values are `openpgp` (OpenPGP/GnuPG), `ssh` (SSH), and `x509` (X.509 certificates).
'';
};
signByDefault = mkOption {
type = types.nullOr types.bool;
default = null;
description = "Whether commits and tags should be signed by default.";
};
signer = mkOption {
type = types.nullOr types.str;
description = "Path to signer binary to use.";
};
};
extraConfig = mkOption {
type = types.either types.lines gitIniType;
default = { };
example = {
core = {
whitespace = "trailing-space,space-before-tab";
};
url."ssh://git@host".insteadOf = "otherhost";
};
description = ''
Additional configuration to add. The use of string values is
deprecated and will be removed in the future.
'';
};
hooks = mkOption {
type = types.attrsOf types.path;
default = { };
example = literalExpression ''
{
pre-commit = ./pre-commit-script;
}
'';
description = ''
Configuration helper for Git hooks.
See <https://git-scm.com/docs/githooks>
for reference.
'';
};
iniContent = mkOption {
type = gitIniType;
internal = true;
};
ignores = mkOption {
type = types.listOf types.str;
default = [ ];
example = [
"*~"
"*.swp"
];
description = "List of paths that should be globally ignored.";
};
attributes = mkOption {
type = types.listOf types.str;
default = [ ];
example = [ "*.pdf diff=pdf" ];
description = "List of defining attributes set globally.";
};
includes = mkOption {
type = types.listOf includeModule;
default = [ ];
example = literalExpression ''
[
{ path = "~/path/to/config.inc"; }
{
path = "~/path/to/conditional.inc";
condition = "gitdir:~/src/dir";
}
]
'';
description = "List of configuration files to include.";
};
lfs = {
enable = mkEnableOption "Git Large File Storage";
skipSmudge = mkOption {
type = types.bool;
default = false;
description = ''
Skip automatic downloading of objects on clone or pull.
This requires a manual {command}`git lfs pull`
every time a new commit is checked out on your repository.
'';
};
};
maintenance = {
enable = mkEnableOption "" // {
description = ''
Enable the automatic {command}`git maintenance`.
If you have SSH remotes, set {option}`programs.git.package` to a
git version with SSH support (eg: `pkgs.gitFull`).
See <https://git-scm.com/docs/git-maintenance>.
'';
};
repositories = mkOption {
type = with types; listOf str;
default = [ ];
description = ''
Repositories on which {command}`git maintenance` should run.
Should be a list of absolute paths.
'';
};
timers = mkOption {
type = types.attrsOf types.str;
default = {
hourly = "*-*-* 1..23:53:00";
daily = "Tue..Sun *-*-* 0:53:00";
weekly = "Mon 0:53:00";
};
description = ''
Systemd timers to create for scheduled {command}`git maintenance`.
Key is passed to `--schedule` argument in {command}`git maintenance run`
and value is passed to `Timer.OnCalendar` in `systemd.user.timers`.
'';
};
};
diff-highlight = {
enable = mkEnableOption "" // {
description = ''
Enable the contrib {command}`diff-highlight` syntax highlighter.
See <https://github.com/git/git/blob/master/contrib/diff-highlight/README>,
'';
};
pagerOpts = mkOption {
type = types.listOf types.str;
default = [ ];
example = [
"--tabs=4"
"-RFX"
];
description = ''
Arguments to be passed to {command}`less`.
'';
};
};
difftastic = {
enable = mkEnableOption "" // {
description = ''
Enable the {command}`difftastic` syntax highlighter.
See <https://github.com/Wilfred/difftastic>.
'';
};
package = mkPackageOption pkgs "difftastic" { };
enableAsDifftool = mkEnableOption "" // {
description = ''
Enable the {command}`difftastic` syntax highlighter as a git difftool.
See <https://github.com/Wilfred/difftastic>.
'';
};
background = mkOption {
type = types.enum [
"light"
"dark"
];
default = "light";
example = "dark";
description = ''
Determines whether difftastic should use the lighter or darker colors
for syntax highlighting.
'';
};
color = mkOption {
type = types.enum [
"always"
"auto"
"never"
];
default = "auto";
example = "always";
description = ''
Determines when difftastic should color its output.
'';
};
display = mkOption {
type = types.enum [
"side-by-side"
"side-by-side-show-both"
"inline"
];
default = "side-by-side";
example = "inline";
description = ''
Determines how the output displays - in one column or two columns.
'';
};
};
delta = {
enable = mkEnableOption "" // {
description = ''
Whether to enable the {command}`delta` syntax highlighter.
See <https://github.com/dandavison/delta>.
'';
};
package = mkPackageOption pkgs "delta" { };
options = mkOption {
type =
with types;
let
primitiveType = either str (either bool int);
sectionType = attrsOf primitiveType;
in
attrsOf (either primitiveType sectionType);
default = { };
example = {
features = "decorations";
whitespace-error-style = "22 reverse";
decorations = {
commit-decoration-style = "bold yellow box ul";
file-style = "bold yellow ul";
file-decoration-style = "none";
};
};
description = ''
Options to configure delta.
'';
};
};
diff-so-fancy = {
enable = mkEnableOption "" // {
description = ''
Enable the {command}`diff-so-fancy` diff colorizer.
See <https://github.com/so-fancy/diff-so-fancy>.
'';
};
pagerOpts = mkOption {
type = types.listOf types.str;
default = [
"--tabs=4"
"-RFX"
];
description = ''
Arguments to be passed to {command}`less`.
'';
};
markEmptyLines = mkOption {
type = types.bool;
default = true;
example = false;
description = ''
Whether the first block of an empty line should be colored.
'';
};
changeHunkIndicators = mkOption {
type = types.bool;
default = true;
example = false;
description = ''
Simplify git header chunks to a more human readable format.
'';
};
stripLeadingSymbols = mkOption {
type = types.bool;
default = true;
example = false;
description = ''
Whether the `+` or `-` at
line-start should be removed.
'';
};
useUnicodeRuler = mkOption {
type = types.bool;
default = true;
example = false;
description = ''
By default, the separator for the file header uses Unicode
line-drawing characters. If this is causing output errors on
your terminal, set this to false to use ASCII characters instead.
'';
};
rulerWidth = mkOption {
type = types.nullOr types.int;
default = null;
example = false;
description = ''
By default, the separator for the file header spans the full
width of the terminal. Use this setting to set the width of
the file header manually.
'';
};
};
riff = {
enable = mkEnableOption "" // {
description = ''
Enable the <command>riff</command> diff highlighter.
See <link xlink:href="https://github.com/walles/riff" />.
'';
};
package = mkPackageOption pkgs "riffdiff" { };
commandLineOptions = mkOption {
type = types.listOf types.str;
default = [ ];
example = literalExpression ''[ "--no-adds-only-special" ]'';
apply = lib.concatStringsSep " ";
description = ''
Command line arguments to include in the <command>RIFF</command> environment variable.
Run <command>riff --help</command> for a full list of options
'';
};
};
patdiff = {
enable = mkEnableOption "" // {
description = ''
Whether to enable the {command}`patdiff` differ.
See <https://opensource.janestreet.com/patdiff/>
'';
};
package = mkPackageOption pkgs "patdiff" { };
};
};
};
imports = [
(lib.mkRenamedOptionModule
[ "programs" "git" "signing" "gpgPath" ]
[
"programs"
"git"
"signing"
"signer"
]
)
];
config = mkIf cfg.enable (
lib.mkMerge [
{
home.packages = [ cfg.package ];
assertions = [
{
assertion =
let
enabled = [
cfg.delta.enable
cfg.diff-so-fancy.enable
cfg.difftastic.enable
cfg.diff-highlight.enable
cfg.riff.enable
cfg.patdiff.enable
];
in
lib.count lib.id enabled <= 1;
message = "Only one of 'programs.git.delta.enable' or 'programs.git.difftastic.enable' or 'programs.git.diff-so-fancy.enable' or 'programs.git.diff-highlight' or 'programs.git.patdiff' can be set to true at the same time.";
}
];
programs.git.iniContent.user = {
name = mkIf (cfg.userName != null) cfg.userName;
email = mkIf (cfg.userEmail != null) cfg.userEmail;
};
xdg.configFile = {
"git/config".text = lib.generators.toGitINI cfg.iniContent;
"git/ignore" = mkIf (cfg.ignores != [ ]) {
text = concatStringsSep "\n" cfg.ignores + "\n";
};
"git/attributes" = mkIf (cfg.attributes != [ ]) {
text = concatStringsSep "\n" cfg.attributes + "\n";
};
};
}
{
programs.git.iniContent =
let
hasSmtp = name: account: account.smtp != null;
genIdentity =
name: account:
let
inherit (account)
address
realName
smtp
userName
;
in
lib.nameValuePair "sendemail.${name}" (
if account.msmtp.enable then
{
sendmailCmd = "${pkgs.msmtp}/bin/msmtp";
envelopeSender = "auto";
from = "${realName} <${address}>";
}
else
{
smtpEncryption =
if smtp.tls.enable then
(if smtp.tls.useStartTls || lib.versionOlder config.home.stateVersion "20.09" then "tls" else "ssl")
else
"";
smtpSslCertPath = mkIf smtp.tls.enable (toString smtp.tls.certificatesFile);
smtpServer = smtp.host;
smtpUser = userName;
from = "${realName} <${address}>";
}
// lib.optionalAttrs (smtp.port != null) {
smtpServerPort = smtp.port;
}
);
in
lib.mapAttrs' genIdentity (lib.filterAttrs hasSmtp config.accounts.email.accounts);
}
(mkIf (cfg.signing != { }) {
programs.git = {
signing = {
format =
if (lib.versionOlder config.home.stateVersion "25.05") then
(mkOptionDefault "openpgp")
else
(mkOptionDefault null);
signer =
let
defaultSigners = {
openpgp = lib.getExe config.programs.gpg.package;
ssh = lib.getExe' pkgs.openssh "ssh-keygen";
x509 = lib.getExe' config.programs.gpg.package "gpgsm";
};
in
mkIf (cfg.signing.format != null) (mkOptionDefault defaultSigners.${cfg.signing.format});
};
iniContent = lib.mkMerge [
(mkIf (cfg.signing.key != null) {
user.signingKey = mkDefault cfg.signing.key;
})
(mkIf (cfg.signing.signByDefault != null) {
commit.gpgSign = mkDefault cfg.signing.signByDefault;
tag.gpgSign = mkDefault cfg.signing.signByDefault;
})
(mkIf (cfg.signing.format != null) {
gpg = {
format = mkDefault cfg.signing.format;
${cfg.signing.format}.program = mkDefault cfg.signing.signer;
};
})
];
};
})
(mkIf (cfg.hooks != { }) {
programs.git.iniContent = {
core.hooksPath =
let
entries = lib.mapAttrsToList (name: path: { inherit name path; }) cfg.hooks;
in
toString (pkgs.linkFarm "git-hooks" entries);
};
})
(mkIf (cfg.aliases != { }) { programs.git.iniContent.alias = cfg.aliases; })
(mkIf (lib.isAttrs cfg.extraConfig) {
programs.git.iniContent = cfg.extraConfig;
})
(mkIf (lib.isString cfg.extraConfig) {
warnings = [
''
Using programs.git.extraConfig as a string option is
deprecated and will be removed in the future. Please
change to using it as an attribute set instead.
''
];
xdg.configFile."git/config".text = cfg.extraConfig;
})
(mkIf (cfg.includes != [ ]) {
xdg.configFile."git/config".text =
let
include =
i:
with i;
if condition != null then
{
includeIf.${condition}.path = "${path}";
}
else
{
include.path = "${path}";
};
in
lib.mkAfter (concatStringsSep "\n" (map lib.generators.toGitINI (map include cfg.includes)));
})
(mkIf cfg.lfs.enable {
home.packages = [ pkgs.git-lfs ];
programs.git.iniContent.filter.lfs =
let
skipArg = lib.optional cfg.lfs.skipSmudge "--skip";
in
{
clean = "git-lfs clean -- %f";
process = concatStringsSep " " (
[
"git-lfs"
"filter-process"
]
++ skipArg
);
required = true;
smudge = concatStringsSep " " (
[
"git-lfs"
"smudge"
]
++ skipArg
++ [
"--"
"%f"
]
);
};
})
(mkIf cfg.maintenance.enable {
programs.git.iniContent.maintenance.repo = cfg.maintenance.repositories;
systemd.user.services."git-maintenance@" = {
Unit = {
Description = "Optimize Git repositories data";
Documentation = [ "man:git-maintenance(1)" ];
};
Service = {
Type = "oneshot";
ExecStart =
let
exe = lib.getExe cfg.package;
in
''
"${exe}" for-each-repo --keep-going --config=maintenance.repo maintenance run --schedule=%i
'';
LockPersonality = "yes";
MemoryDenyWriteExecute = "yes";
NoNewPrivileges = "yes";
RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_VSOCK";
RestrictNamespaces = "yes";
RestrictRealtime = "yes";
RestrictSUIDSGID = "yes";
SystemCallArchitectures = "native";
SystemCallFilter = "@system-service";
};
};
systemd.user.timers =
let
toSystemdTimer =
name: time:
lib.attrsets.nameValuePair "git-maintenance@${name}" {
Unit.Description = "Optimize Git repositories data";
Timer = {
OnCalendar = time;
Persistent = true;
};
Install.WantedBy = [ "timers.target" ];
};
in
lib.attrsets.mapAttrs' toSystemdTimer cfg.maintenance.timers;
launchd.agents =
let
baseArguments = [
"${lib.getExe cfg.package}"
"for-each-repo"
"--keep-going"
"--config=maintenance.repo"
"maintenance"
"run"
];
in
{
"git-maintenance-hourly" = {
enable = true;
config = {
ProgramArguments = baseArguments ++ [ "--schedule=hourly" ];
StartCalendarInterval = map (hour: {
Hour = hour;
Minute = 53;
}) (lib.range 1 23);
};
};
"git-maintenance-daily" = {
enable = true;
config = {
ProgramArguments = baseArguments ++ [ "--schedule=daily" ];
StartCalendarInterval = map (weekday: {
Weekday = weekday;
Hour = 0;
Minute = 53;
}) (lib.range 1 6);
};
};
"git-maintenance-weekly" = {
enable = true;
config = {
ProgramArguments = baseArguments ++ [ "--schedule=weekly" ];
StartCalendarInterval = [
{
Weekday = 0;
Hour = 0;
Minute = 53;
}
];
};
};
};
})
(mkIf cfg.diff-highlight.enable {
programs.git.iniContent =
let
dhCommand = "${cfg.package}/share/git/contrib/diff-highlight/diff-highlight";
in
{
core.pager = "${dhCommand} | ${lib.getExe pkgs.less} ${lib.escapeShellArgs cfg.diff-highlight.pagerOpts}";
interactive.diffFilter = dhCommand;
};
})
(
let
difftCommand = concatStringsSep " " [
"${lib.getExe cfg.difftastic.package}"
"--color ${cfg.difftastic.color}"
"--background ${cfg.difftastic.background}"
"--display ${cfg.difftastic.display}"
];
in
(lib.mkMerge [
(mkIf cfg.difftastic.enable {
home.packages = [ cfg.difftastic.package ];
programs.git.iniContent = {
diff.external = difftCommand;
};
})
(mkIf cfg.difftastic.enableAsDifftool {
home.packages = [ cfg.difftastic.package ];
programs.git.iniContent = {
diff = {
tool = lib.mkDefault "difftastic";
};
difftool = {
difftastic = {
cmd = "${difftCommand} $LOCAL $REMOTE";
};
};
};
})
])
)
(
let
deltaPackage = cfg.delta.package;
deltaCommand = "${deltaPackage}/bin/delta";
in
mkIf cfg.delta.enable {
home.packages = [ deltaPackage ];
programs.git.iniContent = {
core.pager = deltaCommand;
interactive.diffFilter = "${deltaCommand} --color-only";
delta = cfg.delta.options;
};
}
)
(mkIf cfg.diff-so-fancy.enable {
home.packages = [ pkgs.diff-so-fancy ];
programs.git.iniContent =
let
dsfCommand = "${pkgs.diff-so-fancy}/bin/diff-so-fancy";
in
{
core.pager = "${dsfCommand} | ${pkgs.less}/bin/less ${lib.escapeShellArgs cfg.diff-so-fancy.pagerOpts}";
interactive.diffFilter = "${dsfCommand} --patch";
diff-so-fancy = {
markEmptyLines = cfg.diff-so-fancy.markEmptyLines;
changeHunkIndicators = cfg.diff-so-fancy.changeHunkIndicators;
stripLeadingSymbols = cfg.diff-so-fancy.stripLeadingSymbols;
useUnicodeRuler = cfg.diff-so-fancy.useUnicodeRuler;
rulerWidth = mkIf (cfg.diff-so-fancy.rulerWidth != null) (cfg.diff-so-fancy.rulerWidth);
};
};
})
(
let
riffExe = baseNameOf (lib.getExe cfg.riff.package);
in
mkIf cfg.riff.enable {
home.packages = [ cfg.riff.package ];
# https://github.com/walles/riff/blob/b17e6f17ce807c8652bc59cd46758661d23ce358/README.md#usage
programs.git.iniContent = {
pager = {
diff = riffExe;
log = riffExe;
show = riffExe;
};
interactive.diffFilter = "${riffExe} --color=on";
};
}
)
(
let
patdiffPackage = cfg.patdiff.package;
patdiffCommand = "${lib.getExe' patdiffPackage "patdiff-git-wrapper"}";
in
mkIf cfg.patdiff.enable {
home.packages = [ patdiffPackage ];
programs.git.iniContent = {
diff.external = patdiffCommand;
};
}
)
(mkIf (cfg.riff.enable && cfg.riff.commandLineOptions != "") {
home.sessionVariables.RIFF = cfg.riff.commandLineOptions;
})
]
);
}