mirror of
https://github.com/simplex-chat/haskell.nix.git
synced 2026-06-03 09:17:32 +00:00
f1bb3aa2f2
Suppose you have a repository with a submodule checked out in the
subdirectory `smod`.
`cleanGit { src = ./smod; }` would return the following error:
error: getting status of '/.git/modules/smod': No such file or directory
`smod/.git` is a file that contains a reference to the git module repo.
gitdir: ../.git/modules/smod
This is the same as a worktree (in fact, isWorktree = true here),
but it's a relative path instead of an absolute path.
Co-authored-by: Ewout Van Troostenberghe <evantroostenberghe@hubspot.com>
206 lines
7.9 KiB
Nix
206 lines
7.9 KiB
Nix
# From https://github.com/NixOS/nix/issues/2944
|
|
{ lib, runCommand, git, cleanSourceWith }:
|
|
{ name ? null, src, subDir ? "", includeSiblings ? false, keepGitDir ? false }:
|
|
|
|
# The function call
|
|
#
|
|
# gitSource ./toplevel subpath
|
|
#
|
|
# creates a Nix store path of ./toplevel/subpath that includes only those files
|
|
# tracked by git. More precisely: mentioned in the git index (i.e. git add is enough
|
|
# to get them to be included, you do not have to commit).
|
|
#
|
|
# This is a whitelist-based alternative to manually listing files or using
|
|
# nix-gitignore.
|
|
|
|
# Internally, it works by calling git ls-files at evaluation time. To
|
|
# avoid copying all of `.git` to the git store, it only copies the least amount
|
|
# of files necessary for `git ls-files` to work; this is a bit fragile, but
|
|
# very fast.
|
|
|
|
with builtins;
|
|
|
|
# We read the git index once, before getting the subdir parameter, so that it
|
|
# is shared among multiple invocations of gitSource:
|
|
|
|
let
|
|
remove_prefix = prefix: s:
|
|
builtins.substring
|
|
(builtins.stringLength prefix)
|
|
(builtins.stringLength s - builtins.stringLength prefix)
|
|
s;
|
|
|
|
lines = s: filter (x : x != [] && x != "") (split "\n" s);
|
|
|
|
origSrcSubDir = toString (src.origSrcSubDir or src);
|
|
in
|
|
|
|
if builtins.pathExists (origSrcSubDir + "/.git")
|
|
then
|
|
let
|
|
hasIndex = builtins.pathExists (origSrcSubDir + "/.git/index");
|
|
isWorktree = (builtins.readDir origSrcSubDir).".git" == "regular";
|
|
|
|
# Identify the .git directory and filter just the files that we need.
|
|
gitDir = cleanSourceWith ({
|
|
caller = "cleanGit";
|
|
name = (if name == null then "" else name + "-") + "gitFiles";
|
|
filter = path: type:
|
|
type == "directory" ||
|
|
lib.any (i: (lib.hasSuffix i path)) [
|
|
"/config" "/index" "/HEAD" ] ||
|
|
(lib.strings.hasInfix "modules/" path &&
|
|
lib.any (i: (lib.hasSuffix i path)) [
|
|
"/config" "/index" "/HEAD" "/objects" "/refs" ]);
|
|
} // (
|
|
if hasIndex
|
|
then { inherit src; subDir = ".git"; }
|
|
else if !isWorktree
|
|
then abort "cleanGit: ${origSrcSubDir + "/.git"} has no index file"
|
|
else {
|
|
# likely a git worktree, so follow the indirection
|
|
src =
|
|
let
|
|
git_content = lines (readFile (origSrcSubDir + "/.git"));
|
|
first_line = head git_content;
|
|
prefix = "gitdir: ";
|
|
ok = length git_content == 1 && lib.hasPrefix prefix first_line;
|
|
raw_path = remove_prefix prefix first_line;
|
|
in
|
|
if ok
|
|
then
|
|
if lib.hasPrefix "/" raw_path
|
|
then /. + raw_path
|
|
else /. + builtins.toPath (origSrcSubDir + "/" + raw_path)
|
|
else abort "gitSource.nix: Cannot parse ${origSrcSubDir + "/.git"}";
|
|
}));
|
|
|
|
# Worktrees have a commondir pointing to the common `.git` dir. We need the
|
|
# config file from there to get the list of active submodules right.
|
|
commonConfig = if builtins.pathExists (toString gitDir.origSrc + "/commondir")
|
|
then
|
|
# The commondir file contains just the relative path of the
|
|
# common `.git` dir.
|
|
let
|
|
git_content = lines (readFile (toString gitDir.origSrc + "/commondir"));
|
|
first_line = head git_content;
|
|
in gitDir.origSrc + ("/" + first_line + "/config")
|
|
else gitDir + "/config";
|
|
|
|
# We need the .gitmodules file for submoules to work.
|
|
gitModulesStr = origSrcSubDir + "/.gitmodules";
|
|
gitModules = builtins.path { name = "gitmodules"; path = gitModulesStr; };
|
|
|
|
# Files from the git repository related knownSubmoduleDirs.
|
|
gitSubmoduleFiles = knownSubmoduleDirs:
|
|
let pathsNeeded = map (p: toString (src.origSrcSubDir or src) + "/${p}") (
|
|
lib.concatMap (x: all_paren_dirs (x + "/.git")) knownSubmoduleDirs);
|
|
in cleanSourceWith {
|
|
caller = "cleanGit";
|
|
name = (if name == null then "" else name + "-") + "gitSubmoduleFiles";
|
|
inherit src;
|
|
filter = path: type:
|
|
elem path pathsNeeded
|
|
||
|
|
lib.any (i: (lib.hasSuffix i path)) [
|
|
".gitmodules" ".git/config" ".git/index" ".git/HEAD" ".git/objects" ".git/refs" ]
|
|
||
|
|
(lib.strings.hasInfix ".git/modules/" path &&
|
|
lib.any (i: (lib.hasSuffix i path)) [
|
|
"config" "index" "HEAD" "objects" "refs" ]);
|
|
};
|
|
|
|
hasSubmodules = !isWorktree && builtins.pathExists gitModulesStr;
|
|
|
|
# Make a temporary dir that looks enough like the real thing for
|
|
# `git ls-files --recurse-submodules` to give us an accurate list
|
|
# of all the files in the index.
|
|
whitelist_files = knownSubmoduleDirs:
|
|
runCommand "git-ls-files" { preferLocalBuild = true; } ''
|
|
tmp=$(mktemp -d)
|
|
cd $tmp
|
|
${ lib.optionalString hasSubmodules ''
|
|
cp -ra ${gitSubmoduleFiles knownSubmoduleDirs}/. $tmp
|
|
chmod +w -R $tmp
|
|
''}
|
|
cp -r ${gitDir} .git
|
|
chmod +w -R .git
|
|
mkdir -p .git/objects .git/refs
|
|
mkdir -p .git-common/objects .git-common/refs
|
|
cp ${commonConfig} .git-common/config
|
|
echo ../.git-common > .git/commondir
|
|
${ lib.optionalString (isWorktree && builtins.pathExists gitModulesStr) ''
|
|
cp ${gitModules} ./.gitmodules
|
|
''}
|
|
mkdir $out
|
|
${ lib.optionalString hasSubmodules ''
|
|
${git}/bin/git submodule status --recursive | awk '{ print $2 }' > $out/submoduleDirs
|
|
''}
|
|
${git}/bin/git ls-files --recurse-submodules > $out/files
|
|
'';
|
|
|
|
# Find the submodules
|
|
whitelist_recursive = knownSubmoduleDirs:
|
|
let
|
|
# Get the new list of submoduleDirs and files
|
|
# (useing the submoduleDirs we already know about)
|
|
files = whitelist_files knownSubmoduleDirs;
|
|
new = {
|
|
submoduleDirs = lines (readFile (files + "/submoduleDirs"));
|
|
files = lines (readFile (files + "/files"));
|
|
};
|
|
in
|
|
# If we are not expecting submodules or if the new list does not
|
|
# contain any additional submodules we are done.
|
|
if !hasSubmodules || lib.all (x: elem x knownSubmoduleDirs) new.submoduleDirs
|
|
then new
|
|
else
|
|
# Look again using what we know now about the submodules
|
|
whitelist_recursive new.submoduleDirs;
|
|
|
|
# Use empty as a starting point for looking for submodules.
|
|
# We could allow a list to be passed into cleanGit, but when testing
|
|
# on the `ghcjs` repo (one with a lot of submodules) it did not
|
|
# make much of a difference to the speed of `cleanGit`.
|
|
whitelist = whitelist_recursive [];
|
|
|
|
all_paren_dirs = p:
|
|
if p == "." || p == "/"
|
|
then []
|
|
else [ p ] ++ all_paren_dirs (dirOf p);
|
|
|
|
# All the paths that we need to keep as a set (including parent dirs)
|
|
whitelist_set = listToAttrs (
|
|
concatMap (p:
|
|
# Using `origSrcSubDir` (if present) makes it possible to cleanGit src that
|
|
# has already been cleaned with cleanSrcWith.
|
|
let full_path = toString (src.origSrcSubDir or src) + "/${p}"; in
|
|
map (p': { name = p'; value = true; }) (all_paren_dirs full_path)
|
|
) whitelist.files
|
|
);
|
|
|
|
# Identify files in the `.git` dir
|
|
isGitDirPath = path:
|
|
path == origSrcSubDir + "/.git"
|
|
|| lib.hasPrefix (origSrcSubDir + "/.git/") path;
|
|
|
|
filter = path: type:
|
|
hasAttr (toString path) whitelist_set
|
|
|| (keepGitDir && isGitDirPath path);
|
|
in
|
|
cleanSourceWith {
|
|
caller = "cleanGit";
|
|
inherit name src subDir includeSiblings filter;
|
|
} // {
|
|
# For testing
|
|
# inherit whitelist whitelist_set;
|
|
}
|
|
|
|
else
|
|
trace "haskell-nix.haskellLib.cleanGit: ${origSrcSubDir} does not seem to be a git repository,\nassuming it is a clean checkout." (
|
|
cleanSourceWith {
|
|
caller = "cleanGit";
|
|
inherit name src subDir includeSiblings;
|
|
}
|
|
)
|