<commit_before>{ fetchgit, fetchurl, lib, runCommand, cargo, jq }:

{
  # Cargo lock file
  lockFile

  # Hashes for git dependencies.
, outputHashes ? {}
}:

let
  # Parse a git source into different components.
  parseGit = src:
    let
      parts = builtins.match ''git\+([^?]+)(\?rev=(.*))?#(.*)?'' src;
      rev = builtins.elemAt parts 2;
    in
      if parts == null then null
      else {
        url = builtins.elemAt parts 0;
        sha = builtins.elemAt parts 3;
      } // lib.optionalAttrs (rev != null) { inherit rev; };

  packages = (builtins.fromTOML (builtins.readFile lockFile)).package;

  # There is no source attribute for the source package itself. But
  # since we do not want to vendor the source package anyway, we can
  # safely skip it.
  depPackages = (builtins.filter (p: p ? "source") packages);

  # Create dependent crates from packages.
  #
  # Force evaluation of the git SHA -> hash mapping, so that an error is
  # thrown if there are stale hashes. We cannot rely on gitShaOutputHash
  # being evaluated otherwise, since there could be no git dependencies.
  depCrates = builtins.deepSeq (gitShaOutputHash) (builtins.map mkCrate depPackages);

  # Map package name + version to git commit SHA for packages with a git source.
  namesGitShas = builtins.listToAttrs (
    builtins.map nameGitSha (builtins.filter (pkg: lib.hasPrefix "git+" pkg.source) depPackages)
  );

  nameGitSha = pkg: let gitParts = parseGit pkg.source; in {
    name = "${pkg.name}-${pkg.version}";
    value = gitParts.sha;
  };

  # Convert the attrset provided through the `outputHashes` argument to a
  # a mapping from git commit SHA -> output hash.
  #
  # There may be multiple different packages with different names
  # originating from the same git repository (typically a Cargo
  # workspace). By using the git commit SHA as a universal identifier,
  # the user does not have to specify the output hash for every package
  # individually.
  gitShaOutputHash = lib.mapAttrs' (nameVer: hash:
    let
      unusedHash = throw "A hash was specified for ${nameVer}, but there is no corresponding git dependency.";
      rev = namesGitShas.${nameVer} or unusedHash; in {
      name = rev;
      value = hash;
    }) outputHashes;

  # We can't use the existing fetchCrate function, since it uses a
  # recursive hash of the unpacked crate.
  fetchCrate = pkg:
    assert lib.assertMsg (pkg ? checksum) ''
      Package ${pkg.name} does not have a checksum.
      Please note that the Cargo.lock format where checksums used to be listed
      under [metadata] is not supported.
      If that is the case, running `cargo update` with a recent toolchain will
      automatically update the format along with the crate's depenendencies.
    '';
    fetchurl {
      name = "crate-${pkg.name}-${pkg.version}.tar.gz";
      url = "https://crates.io/api/v1/crates/${pkg.name}/${pkg.version}/download";
      sha256 = pkg.checksum;
    };

  # Fetch and unpack a crate.
  mkCrate = pkg:
    let
      gitParts = parseGit pkg.source;
    in
      if pkg.source == "registry+https://github.com/rust-lang/crates.io-index" then
      let
        crateTarball = fetchCrate pkg;
      in runCommand "${pkg.name}-${pkg.version}" {} ''
        mkdir $out
        tar xf "${crateTarball}" -C $out --strip-components=1

        # Cargo is happy with largely empty metadata.
        printf '{"files":{},"package":"${pkg.checksum}"}' > "$out/.cargo-checksum.json"
      ''
      else if gitParts != null then
      let
        missingHash = throw ''
          No hash was found while vendoring the git dependency ${pkg.name}-${pkg.version}. You can add
          a hash through the `outputHashes` argument of `importCargoLock`:

          outputHashes = {
            "${pkg.name}-${pkg.version}" = "<hash>";
          };

          If you use `buildRustPackage`, you can add this attribute to the `cargoLock`
          attribute set.
        '';
        sha256 = gitShaOutputHash.${gitParts.sha} or missingHash;
        tree = fetchgit {
          inherit sha256;
          inherit (gitParts) url;
          rev = gitParts.sha; # The commit SHA is always available.
        };
      in runCommand "${pkg.name}-${pkg.version}" {} ''
        tree=${tree}
        if grep --quiet '\[workspace\]' "$tree/Cargo.toml"; then
          # If the target package is in a workspace, find the crate path
          # using `cargo metadata`.
          crateCargoTOML=$(${cargo}/bin/cargo metadata --format-version 1 --no-deps --manifest-path $tree/Cargo.toml | \
            ${jq}/bin/jq -r '.packages[] | select(.name == "${pkg.name}") | .manifest_path')

            if [[ ! -z $crateCargoTOML ]]; then
              tree=$(dirname $crateCargoTOML)
            else
              >&2 echo "Cannot find path for crate '${pkg.name}-${pkg.version}' in the Cargo workspace in: $tree"
              exit 1
            fi
        fi

        cp -prvd "$tree/" $out
        chmod u+w $out

        # Cargo is happy with empty metadata.
        printf '{"files":{},"package":null}' > "$out/.cargo-checksum.json"

        # Set up configuration for the vendor directory.
        cat > $out/.cargo-config <<EOF
        [source."${gitParts.url}"]
        git = "${gitParts.url}"
        ${lib.optionalString (gitParts ? rev) "rev = \"${gitParts.rev}\""}
        replace-with = "vendored-sources"
        EOF
      ''
      else throw "Cannot handle crate source: ${pkg.source}";

  vendorDir = runCommand "cargo-vendor-dir" {} ''
    mkdir -p $out/.cargo

    ln -s ${lockFile} $out/Cargo.lock

    cat > $out/.cargo/config <<EOF
    [source.crates-io]
    replace-with = "vendored-sources"

    [source.vendored-sources]
    directory = "cargo-vendor-dir"
    EOF

    declare -A keysSeen

    for crate in ${toString depCrates}; do
      # Link the crate directory, removing the output path hash from the destination.
      ln -s "$crate" $out/$(basename "$crate" | cut -c 34-)

      if [ -e "$crate/.cargo-config" ]; then
        key=$(sed 's/\[source\."\(.*\)"\]/\1/; t; d' < "$crate/.cargo-config")
        if [[ -z ''${keysSeen[$key]} ]]; then
          keysSeen[$key]=1
          cat "$crate/.cargo-config" >> $out/.cargo/config
        fi
      fi
    done
  '';
in
  vendorDir
<commit_msg>importCargoLock: git deps with rev, branch or tag<commit_after>{ fetchgit, fetchurl, lib, runCommand, cargo, jq }:

{
  # Cargo lock file
  lockFile

  # Hashes for git dependencies.
, outputHashes ? {}
}:

let
  # Parse a git source into different components.
  parseGit = src:
    let
      parts = builtins.match ''git\+([^?]+)(\?(rev|tag|branch)=(.*))?#(.*)'' src;
      type = builtins.elemAt parts 2; # rev, tag or branch
      value = builtins.elemAt parts 3;
    in
      if parts == null then null
      else {
        url = builtins.elemAt parts 0;
        sha = builtins.elemAt parts 4;
      } // lib.optionalAttrs (type != null) { inherit type value; };

  packages = (builtins.fromTOML (builtins.readFile lockFile)).package;

  # There is no source attribute for the source package itself. But
  # since we do not want to vendor the source package anyway, we can
  # safely skip it.
  depPackages = (builtins.filter (p: p ? "source") packages);

  # Create dependent crates from packages.
  #
  # Force evaluation of the git SHA -> hash mapping, so that an error is
  # thrown if there are stale hashes. We cannot rely on gitShaOutputHash
  # being evaluated otherwise, since there could be no git dependencies.
  depCrates = builtins.deepSeq (gitShaOutputHash) (builtins.map mkCrate depPackages);

  # Map package name + version to git commit SHA for packages with a git source.
  namesGitShas = builtins.listToAttrs (
    builtins.map nameGitSha (builtins.filter (pkg: lib.hasPrefix "git+" pkg.source) depPackages)
  );

  nameGitSha = pkg: let gitParts = parseGit pkg.source; in {
    name = "${pkg.name}-${pkg.version}";
    value = gitParts.sha;
  };

  # Convert the attrset provided through the `outputHashes` argument to a
  # a mapping from git commit SHA -> output hash.
  #
  # There may be multiple different packages with different names
  # originating from the same git repository (typically a Cargo
  # workspace). By using the git commit SHA as a universal identifier,
  # the user does not have to specify the output hash for every package
  # individually.
  gitShaOutputHash = lib.mapAttrs' (nameVer: hash:
    let
      unusedHash = throw "A hash was specified for ${nameVer}, but there is no corresponding git dependency.";
      rev = namesGitShas.${nameVer} or unusedHash; in {
      name = rev;
      value = hash;
    }) outputHashes;

  # We can't use the existing fetchCrate function, since it uses a
  # recursive hash of the unpacked crate.
  fetchCrate = pkg:
    assert lib.assertMsg (pkg ? checksum) ''
      Package ${pkg.name} does not have a checksum.
      Please note that the Cargo.lock format where checksums used to be listed
      under [metadata] is not supported.
      If that is the case, running `cargo update` with a recent toolchain will
      automatically update the format along with the crate's depenendencies.
    '';
    fetchurl {
      name = "crate-${pkg.name}-${pkg.version}.tar.gz";
      url = "https://crates.io/api/v1/crates/${pkg.name}/${pkg.version}/download";
      sha256 = pkg.checksum;
    };

  # Fetch and unpack a crate.
  mkCrate = pkg:
    let
      gitParts = parseGit pkg.source;
    in
      if pkg.source == "registry+https://github.com/rust-lang/crates.io-index" then
      let
        crateTarball = fetchCrate pkg;
      in runCommand "${pkg.name}-${pkg.version}" {} ''
        mkdir $out
        tar xf "${crateTarball}" -C $out --strip-components=1

        # Cargo is happy with largely empty metadata.
        printf '{"files":{},"package":"${pkg.checksum}"}' > "$out/.cargo-checksum.json"
      ''
      else if gitParts != null then
      let
        missingHash = throw ''
          No hash was found while vendoring the git dependency ${pkg.name}-${pkg.version}. You can add
          a hash through the `outputHashes` argument of `importCargoLock`:

          outputHashes = {
            "${pkg.name}-${pkg.version}" = "<hash>";
          };

          If you use `buildRustPackage`, you can add this attribute to the `cargoLock`
          attribute set.
        '';
        sha256 = gitShaOutputHash.${gitParts.sha} or missingHash;
        tree = fetchgit {
          inherit sha256;
          inherit (gitParts) url;
          rev = gitParts.sha; # The commit SHA is always available.
        };
      in runCommand "${pkg.name}-${pkg.version}" {} ''
        tree=${tree}
        if grep --quiet '\[workspace\]' "$tree/Cargo.toml"; then
          # If the target package is in a workspace, find the crate path
          # using `cargo metadata`.
          crateCargoTOML=$(${cargo}/bin/cargo metadata --format-version 1 --no-deps --manifest-path $tree/Cargo.toml | \
            ${jq}/bin/jq -r '.packages[] | select(.name == "${pkg.name}") | .manifest_path')

            if [[ ! -z $crateCargoTOML ]]; then
              tree=$(dirname $crateCargoTOML)
            else
              >&2 echo "Cannot find path for crate '${pkg.name}-${pkg.version}' in the Cargo workspace in: $tree"
              exit 1
            fi
        fi

        cp -prvd "$tree/" $out
        chmod u+w $out

        # Cargo is happy with empty metadata.
        printf '{"files":{},"package":null}' > "$out/.cargo-checksum.json"

        # Set up configuration for the vendor directory.
        cat > $out/.cargo-config <<EOF
        [source."${gitParts.url}"]
        git = "${gitParts.url}"
        ${lib.optionalString (gitParts ? type) "${gitParts.type} = \"${gitParts.value}\""}
        replace-with = "vendored-sources"
        EOF
      ''
      else throw "Cannot handle crate source: ${pkg.source}";

  vendorDir = runCommand "cargo-vendor-dir" {} ''
    mkdir -p $out/.cargo

    ln -s ${lockFile} $out/Cargo.lock

    cat > $out/.cargo/config <<EOF
    [source.crates-io]
    replace-with = "vendored-sources"

    [source.vendored-sources]
    directory = "cargo-vendor-dir"
    EOF

    declare -A keysSeen

    for crate in ${toString depCrates}; do
      # Link the crate directory, removing the output path hash from the destination.
      ln -s "$crate" $out/$(basename "$crate" | cut -c 34-)

      if [ -e "$crate/.cargo-config" ]; then
        key=$(sed 's/\[source\."\(.*\)"\]/\1/; t; d' < "$crate/.cargo-config")
        if [[ -z ''${keysSeen[$key]} ]]; then
          keysSeen[$key]=1
          cat "$crate/.cargo-config" >> $out/.cargo/config
        fi
      fi
    done
  '';
in
  vendorDir

{'fraction_non_alphanumeric': 0.12637059241145382, 'fraction_numerical': 0.003391969708921669, 'mean_word_length': 2.4535548896758375, 'pattern_counts': {'":': 8, '<': 15, '<?xml version=': 0, '>': 23, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '519465', 'n_tokens_mistral': 3928, 'n_tokens_neox': 3818, 'n_words': 1268}