summaryrefslogtreecommitdiff
path: root/pkgs/build-support/fetchgit/default.nix
blob: 5cf447e37a0deb7a5e4aa68e3fdbf311b1c073f9 (plain)
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
{
  config,
  lib,
  stdenvNoCC,
  git,
  git-lfs,
  cacert,
}:

let
  urlToName =
    {
      url,
      rev,
      append,
    }:
    let
      shortRev = lib.sources.shortRev rev;
      appendShort = lib.optionalString ((builtins.match "[a-f0-9]*" rev) != null) "-${shortRev}";
    in
    "${lib.sources.urlToName url}${if append == "" then appendShort else append}";

  getRevWithTag =
    {
      rev ? null,
      tag ? null,
    }:
    if tag != null && rev != null then
      throw "fetchgit requires one of either `rev` or `tag` to be provided (not both)."
    else if tag != null then
      "refs/tags/${tag}"
    else if rev != null then
      rev
    else
      # FIXME fetching HEAD if no rev or tag is provided is problematic at best
      "HEAD";
in

lib.makeOverridable (
  lib.extendMkDerivation {
    constructDrv = stdenvNoCC.mkDerivation;

    excludeDrvArgNames = [
      # Additional stdenv.mkDerivation arguments from derived fetchers.
      "derivationArgs"

      # Hashes, handled by `lib.fetchers.withNormalizedHash`
      # whose outputs contain outputHash* attributes.
      # Use `hash` when overriding with `<pkg>.overrideAttrs`.
      "sha256"
    ];

    extendDrvArgs =
      finalAttrs:
      lib.fetchers.withNormalizedHash { } (
        # NOTE Please document parameter additions or changes in
        #   ../../../doc/build-helpers/fetchers.chapter.md
        {
          url,
          tag ? null,
          rev ? null,
          name ? urlToName {
            inherit url;
            rev = lib.revOrTag finalAttrs.revCustom finalAttrs.tag;
            # when rootDir is specified, avoid invalidating the result when rev changes
            append = if rootDir != "" then "-${lib.strings.sanitizeDerivationName rootDir}" else "";
          },
          # When null, will default to: `deepClone || fetchTags`
          leaveDotGit ? null,
          outputHash ? lib.fakeHash,
          outputHashAlgo ? null,
          fetchSubmodules ? true,
          deepClone ? false,
          branchName ? null,
          # When null, will default to: `lib.optional (rootdir != "") rootdir`
          sparseCheckout ? null,
          # When null, will default to: `rootDir != ""`
          nonConeMode ? null,
          nativeBuildInputs ? [ ],
          # Shell code executed before the file has been fetched.  This, in
          # particular, can do things like set NIX_PREFETCH_GIT_CHECKOUT_HOOK to
          # run operations between the checkout completing and deleting the .git
          # directory.
          preFetch ? "",
          # Shell code executed after `git checkout` and before .git directory removal/sanitization.
          postCheckout ? "",
          # Shell code executed after the file has been fetched
          # successfully. This can do things like check or transform the file.
          postFetch ? "",
          preferLocalBuild ? true,
          fetchLFS ? false,
          # Shell code to build a netrc file for BASIC auth
          netrcPhase ? null,
          # Impure env vars (https://nixos.org/nix/manual/#sec-advanced-attributes)
          # needed for netrcPhase
          netrcImpureEnvVars ? [ ],
          passthru ? { },
          meta ? { },
          allowedRequisites ? null,
          # fetch all tags after tree (useful for git describe)
          fetchTags ? false,
          # make this subdirectory the root of the result
          rootDir ? "",
          # GIT_CONFIG_GLOBAL (as a file)
          gitConfigFile ? config.gitConfigFile,
          # Additional stdenvNoCC.mkDerivation arguments.
          # It is typically for derived fetchers to pass down additional arguments,
          # and the specified arguments have lower precedence than other mkDerivation arguments.
          derivationArgs ? { },
        }:

        /*
          NOTE:
          fetchgit has one problem: git fetch only works for refs.
          This is because fetching arbitrary (maybe dangling) commits creates garbage collection risks
          and checking whether a commit belongs to a ref is expensive. This may
          change in the future when some caching is added to git (?)
          Usually refs are either tags (refs/tags/*) or branches (refs/heads/*)
          Cloning branches will make the hash check fail when there is an update.
          But not all patches we want can be accessed by tags.

          The workaround is getting the last n commits so that it's likely that they
          still contain the hash we want.

          for now : increase depth iteratively (TODO)

          real fix: ask git folks to add a
          git fetch $HASH contained in $BRANCH
          facility because checking that $HASH is contained in $BRANCH is less
          expensive than fetching --depth $N.
          Even if git folks implemented this feature soon it may take years until
          server admins start using the new version?
        */

        let
          finalHashHasColon = lib.hasInfix ":" finalAttrs.hash;
          finalHashColonMatch = lib.match "([^:]+)[:](.*)" finalAttrs.hash;
        in

        derivationArgs
        // {
          __structuredAttrs = true;

          inherit name;

          builder = ./builder.sh;
          fetcher = ./nix-prefetch-git;

          nativeBuildInputs = [
            git
            cacert
          ]
          ++ lib.optionals fetchLFS [ git-lfs ]
          ++ nativeBuildInputs;

          hash =
            if outputHashAlgo == null || outputHash == "" || lib.hasPrefix outputHashAlgo outputHash then
              outputHash
            else
              "${outputHashAlgo}:${outputHash}";

          outputHash =
            if finalAttrs.hash == "" then
              lib.fakeHash
            else if finalHashHasColon then
              lib.elemAt finalHashColonMatch 1
            else
              finalAttrs.hash;
          outputHashAlgo = if finalHashHasColon then lib.head finalHashColonMatch else null;
          outputHashMode = "recursive";

          sparseCheckout =
            let
              default = lib.optional (finalAttrs.rootDir != "") finalAttrs.rootDir;
            in
            lib.defaultTo default sparseCheckout;
          sparseCheckoutText =
            # Changed to throw on 2023-06-04
            assert (
              lib.assertMsg (lib.isList finalAttrs.sparseCheckout) "Please provide directories/patterns for sparse checkout as a list of strings. Passing a (multi-line) string is not supported any more."
            );
            assert finalAttrs.nonConeMode -> (finalAttrs.sparseCheckout != [ ]);
            # git-sparse-checkout(1) says:
            # > When the --stdin option is provided, the directories or patterns are read
            # > from standard in as a newline-delimited list instead of from the arguments.
            builtins.concatStringsSep "\n" finalAttrs.sparseCheckout;

          inherit
            url
            fetchLFS
            fetchSubmodules
            deepClone
            branchName
            preFetch
            postCheckout
            postFetch
            fetchTags
            rootDir
            gitConfigFile
            ;
          leaveDotGit =
            if leaveDotGit != null then
              assert fetchTags -> leaveDotGit;
              assert rootDir != "" -> !leaveDotGit;
              leaveDotGit
            else
              deepClone || fetchTags;
          nonConeMode = lib.defaultTo (finalAttrs.rootDir != "") nonConeMode;
          inherit tag;
          revCustom = rev;
          rev = getRevWithTag {
            inherit (finalAttrs) tag;
            rev = finalAttrs.revCustom;
          };

          postHook =
            if netrcPhase == null then
              null
            else
              ''
                ${netrcPhase}
                # required that git uses the netrc file
                mv {,.}netrc
                export NETRC=$PWD/.netrc
                export HOME=$PWD
              '';

          impureEnvVars =
            lib.fetchers.proxyImpureEnvVars
            ++ netrcImpureEnvVars
            ++ [
              "GIT_PROXY_COMMAND"
              "NIX_GIT_SSL_CAINFO"
              "SOCKS_SERVER"

              # This is a parameter intended to be set by setup hooks or preFetch
              # scripts that want per-URL control over HTTP proxies used by Git
              # (if per-URL control isn't needed, `http_proxy` etc. will
              # suffice). It must be a whitespace-separated (with backslash as an
              # escape character) list of pairs like this:
              #
              #   http://domain1/path1 proxy1 https://domain2/path2 proxy2
              #
              # where the URLs are as documented in the `git-config` manual page
              # under `http.<url>.*`, and the proxies are as documented on the
              # same page under `http.proxy`.
              "FETCHGIT_HTTP_PROXIES"
            ];

          outputChecks.out = {
            ${if allowedRequisites != null then "allowedRequisites" else null} = allowedRequisites;
          };

          inherit preferLocalBuild meta;

          env = {
            NIX_PREFETCH_GIT_CHECKOUT_HOOK = finalAttrs.postCheckout;
          };

          passthru = {
            gitRepoUrl = url;
          }
          // passthru;
        }
      );

    # No ellipsis.
    inheritFunctionArgs = false;
  }
)
// {
  inherit getRevWithTag;
}