1/*
2 <!-- This anchor is here for backwards compatibity -->
3 []{#sec-fileset}
4
5 The [`lib.fileset`](#sec-functions-library-fileset) library allows you to work with _file sets_.
6 A file set is a (mathematical) set of local files that can be added to the Nix store for use in Nix derivations.
7 File sets are easy and safe to use, providing obvious and composable semantics with good error messages to prevent mistakes.
8
9 ## Overview {#sec-fileset-overview}
10
11 Basics:
12 - [Implicit coercion from paths to file sets](#sec-fileset-path-coercion)
13
14 - [`lib.fileset.trace`](#function-library-lib.fileset.trace)/[`lib.fileset.traceVal`](#function-library-lib.fileset.trace):
15
16 Pretty-print file sets for debugging.
17
18 - [`lib.fileset.toSource`](#function-library-lib.fileset.toSource):
19
20 Add files in file sets to the store to use as derivation sources.
21
22 Combinators:
23 - [`lib.fileset.union`](#function-library-lib.fileset.union)/[`lib.fileset.unions`](#function-library-lib.fileset.unions):
24
25 Create a larger file set from all the files in multiple file sets.
26
27 - [`lib.fileset.intersection`](#function-library-lib.fileset.intersection):
28
29 Create a smaller file set from only the files in both file sets.
30
31 - [`lib.fileset.difference`](#function-library-lib.fileset.difference):
32
33 Create a smaller file set containing all files that are in one file set, but not another one.
34
35 Filtering:
36 - [`lib.fileset.fileFilter`](#function-library-lib.fileset.fileFilter):
37
38 Create a file set from all files that satisisfy a predicate in a directory.
39
40 Utilities:
41 - [`lib.fileset.fromSource`](#function-library-lib.fileset.fromSource):
42
43 Create a file set from a `lib.sources`-based value.
44
45 - [`lib.fileset.gitTracked`](#function-library-lib.fileset.gitTracked)/[`lib.fileset.gitTrackedWith`](#function-library-lib.fileset.gitTrackedWith):
46
47 Create a file set from all tracked files in a local Git repository.
48
49 If you need more file set functions,
50 see [this issue](https://github.com/NixOS/nixpkgs/issues/266356) to request it.
51
52
53 ## Implicit coercion from paths to file sets {#sec-fileset-path-coercion}
54
55 All functions accepting file sets as arguments can also accept [paths](https://nixos.org/manual/nix/stable/language/values.html#type-path) as arguments.
56 Such path arguments are implicitly coerced to file sets containing all files under that path:
57 - A path to a file turns into a file set containing that single file.
58 - A path to a directory turns into a file set containing all files _recursively_ in that directory.
59
60 If the path points to a non-existent location, an error is thrown.
61
62 ::: {.note}
63 Just like in Git, file sets cannot represent empty directories.
64 Because of this, a path to a directory that contains no files (recursively) will turn into a file set containing no files.
65 :::
66
67 :::{.note}
68 File set coercion does _not_ add any of the files under the coerced paths to the store.
69 Only the [`toSource`](#function-library-lib.fileset.toSource) function adds files to the Nix store, and only those files contained in the `fileset` argument.
70 This is in contrast to using [paths in string interpolation](https://nixos.org/manual/nix/stable/language/values.html#type-path), which does add the entire referenced path to the store.
71 :::
72
73 ### Example {#sec-fileset-path-coercion-example}
74
75 Assume we are in a local directory with a file hierarchy like this:
76 ```
77 ├─ a/
78 │ ├─ x (file)
79 │ └─ b/
80 │ └─ y (file)
81 └─ c/
82 └─ d/
83 ```
84
85 Here's a listing of which files get included when different path expressions get coerced to file sets:
86 - `./.` as a file set contains both `a/x` and `a/b/y` (`c/` does not contain any files and is therefore omitted).
87 - `./a` as a file set contains both `a/x` and `a/b/y`.
88 - `./a/x` as a file set contains only `a/x`.
89 - `./a/b` as a file set contains only `a/b/y`.
90 - `./c` as a file set is empty, since neither `c` nor `c/d` contain any files.
91*/
92{ lib }:
93let
94
95 inherit (import ./internal.nix { inherit lib; })
96 _coerce
97 _singleton
98 _coerceMany
99 _toSourceFilter
100 _fromSourceFilter
101 _unionMany
102 _fileFilter
103 _printFileset
104 _intersection
105 _difference
106 _mirrorStorePath
107 _fetchGitSubmodulesMinver
108 ;
109
110 inherit (builtins)
111 isBool
112 isList
113 isPath
114 pathExists
115 seq
116 typeOf
117 nixVersion
118 ;
119
120 inherit (lib.lists)
121 elemAt
122 imap0
123 ;
124
125 inherit (lib.path)
126 hasPrefix
127 splitRoot
128 ;
129
130 inherit (lib.strings)
131 isStringLike
132 versionOlder
133 ;
134
135 inherit (lib.filesystem)
136 pathType
137 ;
138
139 inherit (lib.sources)
140 cleanSourceWith
141 ;
142
143 inherit (lib.trivial)
144 isFunction
145 pipe
146 inPureEvalMode
147 ;
148
149in {
150
151 /*
152 Incrementally evaluate and trace a file set in a pretty way.
153 This function is only intended for debugging purposes.
154 The exact tracing format is unspecified and may change.
155
156 This function takes a final argument to return.
157 In comparison, [`traceVal`](#function-library-lib.fileset.traceVal) returns
158 the given file set argument.
159
160 This variant is useful for tracing file sets in the Nix repl.
161
162 Type:
163 trace :: FileSet -> Any -> Any
164
165 Example:
166 trace (unions [ ./Makefile ./src ./tests/run.sh ]) null
167 =>
168 trace: /home/user/src/myProject
169 trace: - Makefile (regular)
170 trace: - src (all files in directory)
171 trace: - tests
172 trace: - run.sh (regular)
173 null
174 */
175 trace =
176 /*
177 The file set to trace.
178
179 This argument can also be a path,
180 which gets [implicitly coerced to a file set](#sec-fileset-path-coercion).
181 */
182 fileset:
183 let
184 # "fileset" would be a better name, but that would clash with the argument name,
185 # and we cannot change that because of https://github.com/nix-community/nixdoc/issues/76
186 actualFileset = _coerce "lib.fileset.trace: Argument" fileset;
187 in
188 seq
189 (_printFileset actualFileset)
190 (x: x);
191
192 /*
193 Incrementally evaluate and trace a file set in a pretty way.
194 This function is only intended for debugging purposes.
195 The exact tracing format is unspecified and may change.
196
197 This function returns the given file set.
198 In comparison, [`trace`](#function-library-lib.fileset.trace) takes another argument to return.
199
200 This variant is useful for tracing file sets passed as arguments to other functions.
201
202 Type:
203 traceVal :: FileSet -> FileSet
204
205 Example:
206 toSource {
207 root = ./.;
208 fileset = traceVal (unions [
209 ./Makefile
210 ./src
211 ./tests/run.sh
212 ]);
213 }
214 =>
215 trace: /home/user/src/myProject
216 trace: - Makefile (regular)
217 trace: - src (all files in directory)
218 trace: - tests
219 trace: - run.sh (regular)
220 "/nix/store/...-source"
221 */
222 traceVal =
223 /*
224 The file set to trace and return.
225
226 This argument can also be a path,
227 which gets [implicitly coerced to a file set](#sec-fileset-path-coercion).
228 */
229 fileset:
230 let
231 # "fileset" would be a better name, but that would clash with the argument name,
232 # and we cannot change that because of https://github.com/nix-community/nixdoc/issues/76
233 actualFileset = _coerce "lib.fileset.traceVal: Argument" fileset;
234 in
235 seq
236 (_printFileset actualFileset)
237 # We could also return the original fileset argument here,
238 # but that would then duplicate work for consumers of the fileset, because then they have to coerce it again
239 actualFileset;
240
241 /*
242 Add the local files contained in `fileset` to the store as a single [store path](https://nixos.org/manual/nix/stable/glossary#gloss-store-path) rooted at `root`.
243
244 The result is the store path as a string-like value, making it usable e.g. as the `src` of a derivation, or in string interpolation:
245 ```nix
246 stdenv.mkDerivation {
247 src = lib.fileset.toSource { ... };
248 # ...
249 }
250 ```
251
252 The name of the store path is always `source`.
253
254 Type:
255 toSource :: {
256 root :: Path,
257 fileset :: FileSet,
258 } -> SourceLike
259
260 Example:
261 # Import the current directory into the store
262 # but only include files under ./src
263 toSource {
264 root = ./.;
265 fileset = ./src;
266 }
267 => "/nix/store/...-source"
268
269 # Import the current directory into the store
270 # but only include ./Makefile and all files under ./src
271 toSource {
272 root = ./.;
273 fileset = union
274 ./Makefile
275 ./src;
276 }
277 => "/nix/store/...-source"
278
279 # Trying to include a file outside the root will fail
280 toSource {
281 root = ./.;
282 fileset = unions [
283 ./Makefile
284 ./src
285 ../LICENSE
286 ];
287 }
288 => <error>
289
290 # The root needs to point to a directory that contains all the files
291 toSource {
292 root = ../.;
293 fileset = unions [
294 ./Makefile
295 ./src
296 ../LICENSE
297 ];
298 }
299 => "/nix/store/...-source"
300
301 # The root has to be a local filesystem path
302 toSource {
303 root = "/nix/store/...-source";
304 fileset = ./.;
305 }
306 => <error>
307 */
308 toSource = {
309 /*
310 (required) The local directory [path](https://nixos.org/manual/nix/stable/language/values.html#type-path) that will correspond to the root of the resulting store path.
311 Paths in [strings](https://nixos.org/manual/nix/stable/language/values.html#type-string), including Nix store paths, cannot be passed as `root`.
312 `root` has to be a directory.
313
314 :::{.note}
315 Changing `root` only affects the directory structure of the resulting store path, it does not change which files are added to the store.
316 The only way to change which files get added to the store is by changing the `fileset` attribute.
317 :::
318 */
319 root,
320 /*
321 (required) The file set whose files to import into the store.
322 File sets can be created using other functions in this library.
323 This argument can also be a path,
324 which gets [implicitly coerced to a file set](#sec-fileset-path-coercion).
325
326 :::{.note}
327 If a directory does not recursively contain any file, it is omitted from the store path contents.
328 :::
329
330 */
331 fileset,
332 }:
333 let
334 # We cannot rename matched attribute arguments, so let's work around it with an extra `let in` statement
335 filesetArg = fileset;
336 in
337 let
338 fileset = _coerce "lib.fileset.toSource: `fileset`" filesetArg;
339 rootFilesystemRoot = (splitRoot root).root;
340 filesetFilesystemRoot = (splitRoot fileset._internalBase).root;
341 sourceFilter = _toSourceFilter fileset;
342 in
343 if ! isPath root then
344 if root ? _isLibCleanSourceWith then
345 throw ''
346 lib.fileset.toSource: `root` is a `lib.sources`-based value, but it should be a path instead.
347 To use a `lib.sources`-based value, convert it to a file set using `lib.fileset.fromSource` and pass it as `fileset`.
348 Note that this only works for sources created from paths.''
349 else if isStringLike root then
350 throw ''
351 lib.fileset.toSource: `root` (${toString root}) is a string-like value, but it should be a path instead.
352 Paths in strings are not supported by `lib.fileset`, use `lib.sources` or derivations instead.''
353 else
354 throw ''
355 lib.fileset.toSource: `root` is of type ${typeOf root}, but it should be a path instead.''
356 # Currently all Nix paths have the same filesystem root, but this could change in the future.
357 # See also ../path/README.md
358 else if ! fileset._internalIsEmptyWithoutBase && rootFilesystemRoot != filesetFilesystemRoot then
359 throw ''
360 lib.fileset.toSource: Filesystem roots are not the same for `fileset` and `root` (${toString root}):
361 `root`: Filesystem root is "${toString rootFilesystemRoot}"
362 `fileset`: Filesystem root is "${toString filesetFilesystemRoot}"
363 Different filesystem roots are not supported.''
364 else if ! pathExists root then
365 throw ''
366 lib.fileset.toSource: `root` (${toString root}) is a path that does not exist.''
367 else if pathType root != "directory" then
368 throw ''
369 lib.fileset.toSource: `root` (${toString root}) is a file, but it should be a directory instead. Potential solutions:
370 - If you want to import the file into the store _without_ a containing directory, use string interpolation or `builtins.path` instead of this function.
371 - If you want to import the file into the store _with_ a containing directory, set `root` to the containing directory, such as ${toString (dirOf root)}, and set `fileset` to the file path.''
372 else if ! fileset._internalIsEmptyWithoutBase && ! hasPrefix root fileset._internalBase then
373 throw ''
374 lib.fileset.toSource: `fileset` could contain files in ${toString fileset._internalBase}, which is not under the `root` (${toString root}). Potential solutions:
375 - Set `root` to ${toString fileset._internalBase} or any directory higher up. This changes the layout of the resulting store path.
376 - Set `fileset` to a file set that cannot contain files outside the `root` (${toString root}). This could change the files included in the result.''
377 else
378 seq sourceFilter
379 cleanSourceWith {
380 name = "source";
381 src = root;
382 filter = sourceFilter;
383 };
384
385 /*
386 The file set containing all files that are in either of two given file sets.
387 This is the same as [`unions`](#function-library-lib.fileset.unions),
388 but takes just two file sets instead of a list.
389 See also [Union (set theory)](https://en.wikipedia.org/wiki/Union_(set_theory)).
390
391 The given file sets are evaluated as lazily as possible,
392 with the first argument being evaluated first if needed.
393
394 Type:
395 union :: FileSet -> FileSet -> FileSet
396
397 Example:
398 # Create a file set containing the file `Makefile`
399 # and all files recursively in the `src` directory
400 union ./Makefile ./src
401
402 # Create a file set containing the file `Makefile`
403 # and the LICENSE file from the parent directory
404 union ./Makefile ../LICENSE
405 */
406 union =
407 # The first file set.
408 # This argument can also be a path,
409 # which gets [implicitly coerced to a file set](#sec-fileset-path-coercion).
410 fileset1:
411 # The second file set.
412 # This argument can also be a path,
413 # which gets [implicitly coerced to a file set](#sec-fileset-path-coercion).
414 fileset2:
415 _unionMany
416 (_coerceMany "lib.fileset.union" [
417 {
418 context = "First argument";
419 value = fileset1;
420 }
421 {
422 context = "Second argument";
423 value = fileset2;
424 }
425 ]);
426
427 /*
428 The file set containing all files that are in any of the given file sets.
429 This is the same as [`union`](#function-library-lib.fileset.unions),
430 but takes a list of file sets instead of just two.
431 See also [Union (set theory)](https://en.wikipedia.org/wiki/Union_(set_theory)).
432
433 The given file sets are evaluated as lazily as possible,
434 with earlier elements being evaluated first if needed.
435
436 Type:
437 unions :: [ FileSet ] -> FileSet
438
439 Example:
440 # Create a file set containing selected files
441 unions [
442 # Include the single file `Makefile` in the current directory
443 # This errors if the file doesn't exist
444 ./Makefile
445
446 # Recursively include all files in the `src/code` directory
447 # If this directory is empty this has no effect
448 ./src/code
449
450 # Include the files `run.sh` and `unit.c` from the `tests` directory
451 ./tests/run.sh
452 ./tests/unit.c
453
454 # Include the `LICENSE` file from the parent directory
455 ../LICENSE
456 ]
457 */
458 unions =
459 # A list of file sets.
460 # The elements can also be paths,
461 # which get [implicitly coerced to file sets](#sec-fileset-path-coercion).
462 filesets:
463 if ! isList filesets then
464 throw ''
465 lib.fileset.unions: Argument is of type ${typeOf filesets}, but it should be a list instead.''
466 else
467 pipe filesets [
468 # Annotate the elements with context, used by _coerceMany for better errors
469 (imap0 (i: el: {
470 context = "Element ${toString i}";
471 value = el;
472 }))
473 (_coerceMany "lib.fileset.unions")
474 _unionMany
475 ];
476
477 /*
478 The file set containing all files that are in both of two given file sets.
479 See also [Intersection (set theory)](https://en.wikipedia.org/wiki/Intersection_(set_theory)).
480
481 The given file sets are evaluated as lazily as possible,
482 with the first argument being evaluated first if needed.
483
484 Type:
485 intersection :: FileSet -> FileSet -> FileSet
486
487 Example:
488 # Limit the selected files to the ones in ./., so only ./src and ./Makefile
489 intersection ./. (unions [ ../LICENSE ./src ./Makefile ])
490 */
491 intersection =
492 # The first file set.
493 # This argument can also be a path,
494 # which gets [implicitly coerced to a file set](#sec-fileset-path-coercion).
495 fileset1:
496 # The second file set.
497 # This argument can also be a path,
498 # which gets [implicitly coerced to a file set](#sec-fileset-path-coercion).
499 fileset2:
500 let
501 filesets = _coerceMany "lib.fileset.intersection" [
502 {
503 context = "First argument";
504 value = fileset1;
505 }
506 {
507 context = "Second argument";
508 value = fileset2;
509 }
510 ];
511 in
512 _intersection
513 (elemAt filesets 0)
514 (elemAt filesets 1);
515
516 /*
517 The file set containing all files from the first file set that are not in the second file set.
518 See also [Difference (set theory)](https://en.wikipedia.org/wiki/Complement_(set_theory)#Relative_complement).
519
520 The given file sets are evaluated as lazily as possible,
521 with the first argument being evaluated first if needed.
522
523 Type:
524 union :: FileSet -> FileSet -> FileSet
525
526 Example:
527 # Create a file set containing all files from the current directory,
528 # except ones under ./tests
529 difference ./. ./tests
530
531 let
532 # A set of Nix-related files
533 nixFiles = unions [ ./default.nix ./nix ./tests/default.nix ];
534 in
535 # Create a file set containing all files under ./tests, except ones in `nixFiles`,
536 # meaning only without ./tests/default.nix
537 difference ./tests nixFiles
538 */
539 difference =
540 # The positive file set.
541 # The result can only contain files that are also in this file set.
542 #
543 # This argument can also be a path,
544 # which gets [implicitly coerced to a file set](#sec-fileset-path-coercion).
545 positive:
546 # The negative file set.
547 # The result will never contain files that are also in this file set.
548 #
549 # This argument can also be a path,
550 # which gets [implicitly coerced to a file set](#sec-fileset-path-coercion).
551 negative:
552 let
553 filesets = _coerceMany "lib.fileset.difference" [
554 {
555 context = "First argument (positive set)";
556 value = positive;
557 }
558 {
559 context = "Second argument (negative set)";
560 value = negative;
561 }
562 ];
563 in
564 _difference
565 (elemAt filesets 0)
566 (elemAt filesets 1);
567
568 /*
569 Filter a file set to only contain files matching some predicate.
570
571 Type:
572 fileFilter ::
573 ({
574 name :: String,
575 type :: String,
576 ...
577 } -> Bool)
578 -> Path
579 -> FileSet
580
581 Example:
582 # Include all regular `default.nix` files in the current directory
583 fileFilter (file: file.name == "default.nix") ./.
584
585 # Include all non-Nix files from the current directory
586 fileFilter (file: ! hasSuffix ".nix" file.name) ./.
587
588 # Include all files that start with a "." in the current directory
589 fileFilter (file: hasPrefix "." file.name) ./.
590
591 # Include all regular files (not symlinks or others) in the current directory
592 fileFilter (file: file.type == "regular") ./.
593 */
594 fileFilter =
595 /*
596 The predicate function to call on all files contained in given file set.
597 A file is included in the resulting file set if this function returns true for it.
598
599 This function is called with an attribute set containing these attributes:
600
601 - `name` (String): The name of the file
602
603 - `type` (String, one of `"regular"`, `"symlink"` or `"unknown"`): The type of the file.
604 This matches result of calling [`builtins.readFileType`](https://nixos.org/manual/nix/stable/language/builtins.html#builtins-readFileType) on the file's path.
605
606 Other attributes may be added in the future.
607 */
608 predicate:
609 # The path whose files to filter
610 path:
611 if ! isFunction predicate then
612 throw ''
613 lib.fileset.fileFilter: First argument is of type ${typeOf predicate}, but it should be a function instead.''
614 else if ! isPath path then
615 if path._type or "" == "fileset" then
616 throw ''
617 lib.fileset.fileFilter: Second argument is a file set, but it should be a path instead.
618 If you need to filter files in a file set, use `intersection fileset (fileFilter pred ./.)` instead.''
619 else
620 throw ''
621 lib.fileset.fileFilter: Second argument is of type ${typeOf path}, but it should be a path instead.''
622 else if ! pathExists path then
623 throw ''
624 lib.fileset.fileFilter: Second argument (${toString path}) is a path that does not exist.''
625 else
626 _fileFilter predicate path;
627
628 /*
629 Create a file set with the same files as a `lib.sources`-based value.
630 This does not import any of the files into the store.
631
632 This can be used to gradually migrate from `lib.sources`-based filtering to `lib.fileset`.
633
634 A file set can be turned back into a source using [`toSource`](#function-library-lib.fileset.toSource).
635
636 :::{.note}
637 File sets cannot represent empty directories.
638 Turning the result of this function back into a source using `toSource` will therefore not preserve empty directories.
639 :::
640
641 Type:
642 fromSource :: SourceLike -> FileSet
643
644 Example:
645 # There's no cleanSource-like function for file sets yet,
646 # but we can just convert cleanSource to a file set and use it that way
647 toSource {
648 root = ./.;
649 fileset = fromSource (lib.sources.cleanSource ./.);
650 }
651
652 # Keeping a previous sourceByRegex (which could be migrated to `lib.fileset.unions`),
653 # but removing a subdirectory using file set functions
654 difference
655 (fromSource (lib.sources.sourceByRegex ./. [
656 "^README\.md$"
657 # This regex includes everything in ./doc
658 "^doc(/.*)?$"
659 ])
660 ./doc/generated
661
662 # Use cleanSource, but limit it to only include ./Makefile and files under ./src
663 intersection
664 (fromSource (lib.sources.cleanSource ./.))
665 (unions [
666 ./Makefile
667 ./src
668 ]);
669 */
670 fromSource = source:
671 let
672 # This function uses `._isLibCleanSourceWith`, `.origSrc` and `.filter`,
673 # which are technically internal to lib.sources,
674 # but we'll allow this since both libraries are in the same code base
675 # and this function is a bridge between them.
676 isFiltered = source ? _isLibCleanSourceWith;
677 path = if isFiltered then source.origSrc else source;
678 in
679 # We can only support sources created from paths
680 if ! isPath path then
681 if isStringLike path then
682 throw ''
683 lib.fileset.fromSource: The source origin of the argument is a string-like value ("${toString path}"), but it should be a path instead.
684 Sources created from paths in strings cannot be turned into file sets, use `lib.sources` or derivations instead.''
685 else
686 throw ''
687 lib.fileset.fromSource: The source origin of the argument is of type ${typeOf path}, but it should be a path instead.''
688 else if ! pathExists path then
689 throw ''
690 lib.fileset.fromSource: The source origin (${toString path}) of the argument is a path that does not exist.''
691 else if isFiltered then
692 _fromSourceFilter path source.filter
693 else
694 # If there's no filter, no need to run the expensive conversion, all subpaths will be included
695 _singleton path;
696
697 /*
698 Create a file set containing all [Git-tracked files](https://git-scm.com/book/en/v2/Git-Basics-Recording-Changes-to-the-Repository) in a repository.
699
700 This function behaves like [`gitTrackedWith { }`](#function-library-lib.fileset.gitTrackedWith) - using the defaults.
701
702 Type:
703 gitTracked :: Path -> FileSet
704
705 Example:
706 # Include all files tracked by the Git repository in the current directory
707 gitTracked ./.
708
709 # Include only files tracked by the Git repository in the parent directory
710 # that are also in the current directory
711 intersection ./. (gitTracked ../.)
712 */
713 gitTracked =
714 /*
715 The [path](https://nixos.org/manual/nix/stable/language/values#type-path) to the working directory of a local Git repository.
716 This directory must contain a `.git` file or subdirectory.
717 */
718 path:
719 # See the gitTrackedWith implementation for more explanatory comments
720 let
721 fetchResult = builtins.fetchGit path;
722 in
723 if inPureEvalMode then
724 throw "lib.fileset.gitTracked: This function is currently not supported in pure evaluation mode, since it currently relies on `builtins.fetchGit`. See https://github.com/NixOS/nix/issues/9292."
725 else if ! isPath path then
726 throw "lib.fileset.gitTracked: Expected the argument to be a path, but it's a ${typeOf path} instead."
727 else if ! pathExists (path + "/.git") then
728 throw "lib.fileset.gitTracked: Expected the argument (${toString path}) to point to a local working tree of a Git repository, but it's not."
729 else
730 _mirrorStorePath path fetchResult.outPath;
731
732 /*
733 Create a file set containing all [Git-tracked files](https://git-scm.com/book/en/v2/Git-Basics-Recording-Changes-to-the-Repository) in a repository.
734 The first argument allows configuration with an attribute set,
735 while the second argument is the path to the Git working tree.
736 If you don't need the configuration,
737 you can use [`gitTracked`](#function-library-lib.fileset.gitTracked) instead.
738
739 This is equivalent to the result of [`unions`](#function-library-lib.fileset.unions) on all files returned by [`git ls-files`](https://git-scm.com/docs/git-ls-files)
740 (which uses [`--cached`](https://git-scm.com/docs/git-ls-files#Documentation/git-ls-files.txt--c) by default).
741
742 :::{.warning}
743 Currently this function is based on [`builtins.fetchGit`](https://nixos.org/manual/nix/stable/language/builtins.html#builtins-fetchGit)
744 As such, this function causes all Git-tracked files to be unnecessarily added to the Nix store,
745 without being re-usable by [`toSource`](#function-library-lib.fileset.toSource).
746
747 This may change in the future.
748 :::
749
750 Type:
751 gitTrackedWith :: { recurseSubmodules :: Bool ? false } -> Path -> FileSet
752
753 Example:
754 # Include all files tracked by the Git repository in the current directory
755 # and any submodules under it
756 gitTracked { recurseSubmodules = true; } ./.
757 */
758 gitTrackedWith =
759 {
760 /*
761 (optional, default: `false`) Whether to recurse into [Git submodules](https://git-scm.com/book/en/v2/Git-Tools-Submodules) to also include their tracked files.
762
763 If `true`, this is equivalent to passing the [--recurse-submodules](https://git-scm.com/docs/git-ls-files#Documentation/git-ls-files.txt---recurse-submodules) flag to `git ls-files`.
764 */
765 recurseSubmodules ? false,
766 }:
767 /*
768 The [path](https://nixos.org/manual/nix/stable/language/values#type-path) to the working directory of a local Git repository.
769 This directory must contain a `.git` file or subdirectory.
770 */
771 path:
772 let
773 # This imports the files unnecessarily, which currently can't be avoided
774 # because `builtins.fetchGit` is the only function exposing which files are tracked by Git.
775 # With the [lazy trees PR](https://github.com/NixOS/nix/pull/6530),
776 # the unnecessarily import could be avoided.
777 # However a simpler alternative still would be [a builtins.gitLsFiles](https://github.com/NixOS/nix/issues/2944).
778 fetchResult = builtins.fetchGit {
779 url = path;
780
781 # This is the only `fetchGit` parameter that makes sense in this context.
782 # We can't just pass `submodules = recurseSubmodules` here because
783 # this would fail for Nix versions that don't support `submodules`.
784 ${if recurseSubmodules then "submodules" else null} = true;
785 };
786 in
787 if inPureEvalMode then
788 throw "lib.fileset.gitTrackedWith: This function is currently not supported in pure evaluation mode, since it currently relies on `builtins.fetchGit`. See https://github.com/NixOS/nix/issues/9292."
789 else if ! isBool recurseSubmodules then
790 throw "lib.fileset.gitTrackedWith: Expected the attribute `recurseSubmodules` of the first argument to be a boolean, but it's a ${typeOf recurseSubmodules} instead."
791 else if recurseSubmodules && versionOlder nixVersion _fetchGitSubmodulesMinver then
792 throw "lib.fileset.gitTrackedWith: Setting the attribute `recurseSubmodules` to `true` is only supported for Nix version ${_fetchGitSubmodulesMinver} and after, but Nix version ${nixVersion} is used."
793 else if ! isPath path then
794 throw "lib.fileset.gitTrackedWith: Expected the second argument to be a path, but it's a ${typeOf path} instead."
795 # We can identify local working directories by checking for .git,
796 # see https://git-scm.com/docs/gitrepository-layout#_description.
797 # Note that `builtins.fetchGit` _does_ work for bare repositories (where there's no `.git`),
798 # even though `git ls-files` wouldn't return any files in that case.
799 else if ! pathExists (path + "/.git") then
800 throw "lib.fileset.gitTrackedWith: Expected the second argument (${toString path}) to point to a local working tree of a Git repository, but it's not."
801 else
802 _mirrorStorePath path fetchResult.outPath;
803}