-
Notifications
You must be signed in to change notification settings - Fork 1
/
git.nix
126 lines (110 loc) · 3.75 KB
/
git.nix
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
{ config, pkgs, lib, ... }:
with lib;
let
conf = config.git;
esc = escapeShellArg;
mapAttrsToLines = f: attrs: concatStringsSep "\n" (mapAttrsToList f attrs);
get_remote = role: remote:
let r = getAttr remote conf.remotes;
in if isAttrs r then getAttr role r else r;
# If the 'main_remote' option correspond to a configured remote,
# Use 'git clone' if possible, fallback to 'git init' if the
# 'main_remote' option doesn't correspond to a configured remote.
# If the main branch is set to be guessed, it might only be set in the
# 'git clone' branch and won't be set in the fallback branch.
init_repository = if hasAttr conf.main_remote conf.remotes then ''
git clone --origin=${esc conf.main_remote} ${
esc (get_remote "fetch" conf.main_remote)
} .
${if conf.main_branch == null then ''
# Set the 'MAIN' symbolic ref to the HEAD advertised by the remote.
update_default_branch "$(git symbolic-ref --short HEAD)"
'' else
""}
'' else ''
git init ${
if conf.main_branch != null then
"--initial-branch=${esc conf.main_branch}"
else
""
}
'';
update_remotes =
let update_remote = n: u: role: "update_remote ${esc n} ${esc u} ${role}";
in mapAttrsToLines (n: u:
if isAttrs u then ''
${update_remote n u.fetch "fetch"}
${update_remote n u.push "push"}
'' else
update_remote n u "fetch") conf.remotes;
# If the 'main_branch' option is set, make sure it is uptodate. Otherwise,
# guess it.
update_default_branch = if conf.main_branch == null then ''
if ! [[ -e .git/MAIN ]]; then guess_default_branch; fi
'' else
"update_default_branch ${esc conf.main_branch}";
update_gitignore = if config.git.gitignore == "" then
""
else ''
ln -sf "${builtins.toFile "gitignore" conf.gitignore}" .git/info/exclude
'';
in {
options.git = with types; {
remotes = mkOption {
type = attrsOf (either str (submodule [{
options = {
fetch = mkOption { type = str; };
push = mkOption { type = str; };
};
}]));
default = { };
description = ''
Git remotes. When the workspace is activated, new remotes defined here
are added to the Git repository automatically and changed URL are
updated.
Adding a remote is enough to activate this module. The repository is
cloned on the first time the workspace is opened.
'';
};
main_branch = mkOption {
type = nullOr str;
default = null;
description = ''
Defines the 'MAIN' symbolic ref.
Also used to set the 'init.defaultBranch' config and for the initial
checkout.
By default, the default branch is taken from the remote repository
during the initial checkout. This can only work if the 'main_remote'
option is set to a configured remote.
If it is not set at the time of activation, it is guessed.
'';
};
main_remote = mkOption {
type = str;
default = "origin";
description = "See the 'main_branch' option.";
};
gitignore = mkOption {
type = lines;
default = "";
description = "Local gitignore rules.";
};
};
config = mkIf (conf.remotes != { }) {
buildInputs = with pkgs; [ git ];
# 'init_repository' might or might not fetch the main remote. In any case,
# fetch again to be sure to have all the remotes and tags.
init_script = ''
. ${./git_update_states.sh}
${init_repository}
${update_remotes}
git fetch --all --tags --update-head-ok --no-show-forced-updates --force
'';
activation_script = ''
. ${./git_update_states.sh}
${update_remotes}
${update_default_branch}
${update_gitignore}
'';
};
}