aa479b0124
If a user using msmtp to send all their email, it would be preferred if git used it as well. The only settings necessary are to set the smtp server to the msmtp binary and set envelop sender to true, which makes git call msmtp with the -f flag to set the from address from the email.
365 lines
10 KiB
Nix
365 lines
10 KiB
Nix
{ config, lib, pkgs, ... }:
|
|
|
|
with lib;
|
|
|
|
let
|
|
|
|
cfg = config.programs.git;
|
|
|
|
# create [section "subsection"] keys from "section.subsection" attrset names
|
|
mkSectionName = name:
|
|
let
|
|
containsQuote = strings.hasInfix ''"'' name;
|
|
sections = splitString "." name;
|
|
section = head sections;
|
|
subsections = tail sections;
|
|
subsection = concatStringsSep "." subsections;
|
|
in if containsQuote || subsections == [ ] then
|
|
name
|
|
else
|
|
''${section} "${subsection}"'';
|
|
|
|
mkValueString = v:
|
|
let
|
|
escapedV = ''
|
|
"${
|
|
replaceStrings [ "\n" " " ''"'' "\\" ] [ "\\n" "\\t" ''\"'' "\\\\" ] v
|
|
}"'';
|
|
in generators.mkValueStringDefault { } (if isString v then escapedV else v);
|
|
|
|
# generation for multiple ini values
|
|
mkKeyValue = k: v:
|
|
let
|
|
mkKeyValue =
|
|
generators.mkKeyValueDefault { inherit mkValueString; } " = " k;
|
|
in concatStringsSep "\n" (map (kv: " " + mkKeyValue kv) (toList v));
|
|
|
|
# converts { a.b.c = 5; } to { "a.b".c = 5; } for toINI
|
|
gitFlattenAttrs = let
|
|
recurse = path: value:
|
|
if isAttrs value then
|
|
mapAttrsToList (name: value: recurse ([ name ] ++ path) value) value
|
|
else if length path > 1 then {
|
|
${concatStringsSep "." (reverseList (tail path))}.${head path} = value;
|
|
} else {
|
|
${head path} = value;
|
|
};
|
|
in attrs: foldl recursiveUpdate { } (flatten (recurse [ ] attrs));
|
|
|
|
gitToIni = attrs:
|
|
let toIni = generators.toINI { inherit mkKeyValue mkSectionName; };
|
|
in toIni (gitFlattenAttrs attrs);
|
|
|
|
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;
|
|
|
|
signModule = types.submodule {
|
|
options = {
|
|
key = mkOption {
|
|
type = types.str;
|
|
description = "The default GPG signing key fingerprint.";
|
|
};
|
|
|
|
signByDefault = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = "Whether commits should be signed by default.";
|
|
};
|
|
|
|
gpgPath = mkOption {
|
|
type = types.str;
|
|
default = "${pkgs.gnupg}/bin/gpg2";
|
|
defaultText = "\${pkgs.gnupg}/bin/gpg2";
|
|
description = "Path to GnuPG binary to use.";
|
|
};
|
|
};
|
|
};
|
|
|
|
includeModule = types.submodule ({ config, ... }: {
|
|
options = {
|
|
condition = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = ''
|
|
Include this configuration only when <varname>condition</varname>
|
|
matches. Allowed conditions are described in
|
|
<citerefentry>
|
|
<refentrytitle>git-config</refentrytitle>
|
|
<manvolnum>1</manvolnum>
|
|
</citerefentry>.
|
|
'';
|
|
};
|
|
|
|
path = mkOption {
|
|
type = with types; either str path;
|
|
description = "Path of the configuration file to include.";
|
|
};
|
|
|
|
contents = mkOption {
|
|
type = types.attrsOf types.anything;
|
|
default = { };
|
|
description = ''
|
|
Configuration to include. If empty then a path must be given.
|
|
'';
|
|
};
|
|
};
|
|
|
|
config.path = mkIf (config.contents != { })
|
|
(mkDefault (pkgs.writeText "contents" (gitToIni config.contents)));
|
|
});
|
|
|
|
in {
|
|
meta.maintainers = [ maintainers.rycee ];
|
|
|
|
options = {
|
|
programs.git = {
|
|
enable = mkEnableOption "Git";
|
|
|
|
package = mkOption {
|
|
type = types.package;
|
|
default = pkgs.git;
|
|
defaultText = literalExample "pkgs.git";
|
|
description = ''
|
|
Git package to install. Use <varname>pkgs.gitAndTools.gitFull</varname>
|
|
to gain access to <command>git send-email</command> 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 = mkOption {
|
|
type = types.nullOr signModule;
|
|
default = null;
|
|
description = "Options related to signing commits using GnuPG.";
|
|
};
|
|
|
|
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.
|
|
'';
|
|
};
|
|
|
|
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 = literalExample ''
|
|
[
|
|
{ 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</command>
|
|
every time a new commit is checked out on your repository.
|
|
'';
|
|
};
|
|
};
|
|
|
|
delta = {
|
|
enable = mkEnableOption "" // {
|
|
description = ''
|
|
Whether to enable the <command>delta</command> syntax highlighter.
|
|
See <link xlink:href="https://github.com/dandavison/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.
|
|
'';
|
|
};
|
|
};
|
|
};
|
|
};
|
|
|
|
config = mkIf cfg.enable (mkMerge [
|
|
{
|
|
home.packages = [ cfg.package ];
|
|
|
|
programs.git.iniContent.user = {
|
|
name = mkIf (cfg.userName != null) cfg.userName;
|
|
email = mkIf (cfg.userEmail != null) cfg.userEmail;
|
|
};
|
|
|
|
xdg.configFile = {
|
|
"git/config".text = gitToIni 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:
|
|
with account;
|
|
nameValuePair "sendemail.${name}" (if account.msmtp.enable then {
|
|
smtpServer = "${pkgs.msmtp}/bin/msmtp";
|
|
envelopeSender = true;
|
|
from = address;
|
|
} else
|
|
{
|
|
smtpEncryption = if smtp.tls.enable then
|
|
(if smtp.tls.useStartTls
|
|
|| versionOlder config.home.stateVersion "20.09" then
|
|
"tls"
|
|
else
|
|
"ssl")
|
|
else
|
|
"";
|
|
smtpServer = smtp.host;
|
|
smtpUser = userName;
|
|
from = address;
|
|
} // optionalAttrs (smtp.port != null) {
|
|
smtpServerPort = smtp.port;
|
|
});
|
|
in mapAttrs' genIdentity
|
|
(filterAttrs hasSmtp config.accounts.email.accounts);
|
|
}
|
|
|
|
(mkIf (cfg.signing != null) {
|
|
programs.git.iniContent = {
|
|
user.signingKey = cfg.signing.key;
|
|
commit.gpgSign = cfg.signing.signByDefault;
|
|
gpg.program = cfg.signing.gpgPath;
|
|
};
|
|
})
|
|
|
|
(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 mkAfter
|
|
(concatStringsSep "\n" (map gitToIni (map include cfg.includes)));
|
|
})
|
|
|
|
(mkIf cfg.lfs.enable {
|
|
home.packages = [ pkgs.git-lfs ];
|
|
|
|
programs.git.iniContent.filter.lfs =
|
|
let skipArg = 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.delta.enable {
|
|
programs.git.iniContent = let deltaCommand = "${pkgs.delta}/bin/delta";
|
|
in {
|
|
core.pager = deltaCommand;
|
|
interactive.diffFilter = "${deltaCommand} --color-only";
|
|
delta = cfg.delta.options;
|
|
};
|
|
})
|
|
]);
|
|
}
|