From 41bad096e38dd45e3a0c6832ff26ff7df3fb7eef Mon Sep 17 00:00:00 2001 From: rootile Date: Sun, 5 Jul 2026 15:50:41 +0200 Subject: [PATCH] rust: migrate libutil/git.cc Change-Id: Ic3932fdf3afd3dc09019623516597f1c648f7fc4 --- Cargo.lock | 34 ++++++++++- Cargo.toml | 1 + lix/libfetchers/git.cc | 21 ++++--- lix/libfetchers/github.cc | 17 +++--- lix/libutil/git.cc | 26 --------- lix/libutil/git.hh | 43 -------------- lix/libutil/meson.build | 2 - lix/lix-rs/Cargo.toml | 1 + lix/lix-rs/meson.build | 2 + lix/lix-rs/src/fetchers.zng | 29 +++++++++ lix/lix-rs/src/fetchers/git.rs | 104 +++++++++++++++++++++++++++++++++ lix/lix-rs/src/fetchers/mod.rs | 1 + lix/lix-rs/src/lib.rs | 1 + lix/lix-rs/src/lib.zng | 1 + lix/lix-rs/std-common.zng | 9 +++ tests/unit/libutil/git.cc | 32 ---------- tests/unit/meson.build | 1 - 17 files changed, 201 insertions(+), 124 deletions(-) delete mode 100644 lix/libutil/git.cc delete mode 100644 lix/libutil/git.hh create mode 100644 lix/lix-rs/src/fetchers.zng create mode 100644 lix/lix-rs/src/fetchers/git.rs create mode 100644 lix/lix-rs/src/fetchers/mod.rs delete mode 100644 tests/unit/libutil/git.cc diff --git a/Cargo.lock b/Cargo.lock index 6b4e59c78..ef8364a0c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -189,7 +189,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e82d74e6c83060ec269fe9e0d408d6de4a1645d525f9a0bbbb841ba4efd91ac" dependencies = [ "hashbrown 0.15.5", - "regex-automata", + "regex-automata 0.3.9", "serde", "stacker", "unicode-ident", @@ -451,6 +451,7 @@ name = "lix" version = "0.0.0" dependencies = [ "lix-doc", + "regex", "rustyline", "rustyline-derive", "zngur", @@ -604,6 +605,18 @@ dependencies = [ "nibble_vec", ] +[[package]] +name = "regex" +version = "1.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.14", + "regex-syntax 0.8.11", +] + [[package]] name = "regex-automata" version = "0.3.9" @@ -612,7 +625,18 @@ checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.7.5", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.11", ] [[package]] @@ -621,6 +645,12 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + [[package]] name = "rnix" version = "0.12.0" diff --git a/Cargo.toml b/Cargo.toml index 7c59d3c4c..bada976dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ edition = "2021" [workspace.dependencies] clap = "4" +regex = "1.12.4" rustyline = "18" rustyline-derive = "0.12" syn = "2.0" diff --git a/lix/libfetchers/git.cc b/lix/libfetchers/git.cc index 36da76928..134af17df 100644 --- a/lix/libfetchers/git.cc +++ b/lix/libfetchers/git.cc @@ -16,9 +16,10 @@ #include "lix/libutil/url-parts.hh" #include "lix/libstore/pathlocks.hh" #include "lix/libutil/users.hh" -#include "lix/libutil/git.hh" #include "lix/libutil/logging.hh" #include "lix/libutil/finally.hh" +#include "lix/lix-rs/main.gen.hh" +#include "lix/lix-rs/utils.hh" #include "lix/libfetchers/fetch-settings.hh" @@ -81,17 +82,15 @@ try { )); std::string_view line = output; - line = line.substr(0, line.find("\n")); - if (const auto parseResult = git::parseLsRemoteLine(line)) { - switch (parseResult->kind) { - case git::LsRemoteRefLine::Kind::Symbolic: - debug("resolved HEAD ref '%s' for repo '%s'", parseResult->target, path); - break; - case git::LsRemoteRefLine::Kind::Object: - debug("resolved HEAD rev '%s' for repo '%s'", parseResult->target, path); - break; + auto line_rs = rust::to_string(line.substr(0, line.find("\n"))); + if (const auto parseResult = to_std(rust::lix::fetchers::git::parse_ls_remote_line(line_rs.as_str()))) { + auto target = to_std_string(parseResult->target.as_str()); + if (parseResult->kind.matches_Object()) { + debug("resolved HEAD rev '%s' for repo '%s'", target, path); + } else { + debug("resolved HEAD ref '%s' for repo '%s'", target, path); } - co_return parseResult->target; + co_return to_std_string(parseResult->target.as_str()); } co_return std::nullopt; } catch (ExecError &) { diff --git a/lix/libfetchers/github.cc b/lix/libfetchers/github.cc index fdb0727f1..39cca6006 100644 --- a/lix/libfetchers/github.cc +++ b/lix/libfetchers/github.cc @@ -7,10 +7,11 @@ #include "lix/libutil/result.hh" #include "lix/libutil/types.hh" #include "lix/libutil/url-parts.hh" -#include "lix/libutil/git.hh" #include "lix/libutil/json.hh" #include "lix/libfetchers/fetchers.hh" #include "lix/libfetchers/fetch-settings.hh" +#include "lix/lix-rs/main.gen.hh" +#include "lix/lix-rs/utils.hh" #include #include @@ -486,15 +487,15 @@ struct SourceHutInputScheme : GitArchiveInputScheme std::string line; getline(is, line); - auto remoteLine = git::parseLsRemoteLine(line); + auto remoteLine = + to_std(rust::lix::fetchers::git::parse_ls_remote_line(rust::to_string(line).as_str())); if (!remoteLine) { throw BadURL("in '%d', couldn't resolve HEAD ref '%d'", input.to_string(), ref); } - refUri = remoteLine->target; + refUri = to_std_string(remoteLine->target.as_str()); } else { refUri = fmt("refs/(heads|tags)/%s", ref); } - std::regex refRegex = regex::parse(refUri); auto file = store->toRealPath( TRY_AWAIT(downloadFile(store, fmt("%s/info/refs", base_url), "source", false, headers)) @@ -505,9 +506,11 @@ struct SourceHutInputScheme : GitArchiveInputScheme std::string line; std::optional id; while(!id && getline(is, line)) { - auto parsedLine = git::parseLsRemoteLine(line); - if (parsedLine && parsedLine->reference && std::regex_match(*parsedLine->reference, refRegex)) - id = parsedLine->target; + auto parsedLine = + to_std(rust::lix::fetchers::git::parse_ls_remote_line(rust::to_string(line).as_str())); + if (parsedLine && (*parsedLine).matches_ref_uri(rust::to_string(refUri).as_str())) { + id = to_std_string(parsedLine->target.as_str()); + } } if(!id) diff --git a/lix/libutil/git.cc b/lix/libutil/git.cc deleted file mode 100644 index 8069515c4..000000000 --- a/lix/libutil/git.cc +++ /dev/null @@ -1,26 +0,0 @@ -#include "lix/libutil/git.hh" -#include "regex.hh" - -#include - -namespace nix { -namespace git { - -std::optional parseLsRemoteLine(std::string_view line) -{ - const static std::regex line_regex = regex::parse("^(ref: *)?([^\\s]+)(?:\\t+(.*))?$"); - std::match_results match; - if (!std::regex_match(line.cbegin(), line.cend(), match, line_regex)) - return std::nullopt; - - return LsRemoteRefLine { - .kind = match[1].length() == 0 - ? LsRemoteRefLine::Kind::Object - : LsRemoteRefLine::Kind::Symbolic, - .target = match[2], - .reference = match[3].length() == 0 ? std::nullopt : std::optional{ match[3] } - }; -} - -} -} diff --git a/lix/libutil/git.hh b/lix/libutil/git.hh deleted file mode 100644 index bf2b9a286..000000000 --- a/lix/libutil/git.hh +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once -///@file - -#include -#include -#include - -namespace nix { - -namespace git { - -/** - * A line from the output of `git ls-remote --symref`. - * - * These can be of two kinds: - * - * - Symbolic references of the form - * - * ref: {target} {reference} - * - * where {target} is itself a reference and {reference} is optional - * - * - Object references of the form - * - * {target} {reference} - * - * where {target} is a commit id and {reference} is mandatory - */ -struct LsRemoteRefLine { - enum struct Kind { - Symbolic, - Object - }; - Kind kind; - std::string target; - std::optional reference; -}; - -std::optional parseLsRemoteLine(std::string_view line); - -} - -} diff --git a/lix/libutil/meson.build b/lix/libutil/meson.build index 452432c2c..9095c67df 100644 --- a/lix/libutil/meson.build +++ b/lix/libutil/meson.build @@ -22,7 +22,6 @@ liblix_sources += files( 'file-descriptor.cc', 'file-system.cc', 'fmt.cc', - 'git.cc', 'hash.cc', 'hilite.cc', 'io-buffer.cc', @@ -103,7 +102,6 @@ libutil_headers = files( 'finally.hh', 'fmt.hh', 'generator.hh', - 'git.hh', 'hash.hh', 'hilite.hh', 'input-accessor.hh', diff --git a/lix/lix-rs/Cargo.toml b/lix/lix-rs/Cargo.toml index 442517622..96fce17a8 100644 --- a/lix/lix-rs/Cargo.toml +++ b/lix/lix-rs/Cargo.toml @@ -12,6 +12,7 @@ default = [] [dependencies] lix-doc.path = "../lix-doc" +regex.workspace=true rustyline.workspace = true rustyline-derive.workspace = true diff --git a/lix/lix-rs/meson.build b/lix/lix-rs/meson.build index 4f89ec963..606d4cc96 100644 --- a/lix/lix-rs/meson.build +++ b/lix/lix-rs/meson.build @@ -1,6 +1,8 @@ lix_rs_files += files( 'Cargo.toml', # keep-sorted start + 'src/fetchers/git.rs', + 'src/fetchers/mod.rs', 'src/ffi_test.rs', 'src/lib.rs', 'src/repl.rs', diff --git a/lix/lix-rs/src/fetchers.zng b/lix/lix-rs/src/fetchers.zng new file mode 100644 index 000000000..423ff3bc8 --- /dev/null +++ b/lix/lix-rs/src/fetchers.zng @@ -0,0 +1,29 @@ +use ::std::string::String as String; + +mod crate::fetchers::git { + type Kind { + #layout_conservative(size=16, align=8); + constructor Symbolic; + constructor Object; + } + type LsRemoteRefLine { + #layout_conservative(size=56, align=8); + field kind (offset=auto, type=Kind); + field target (offset=auto, type=String); + field reference (offset=auto, type=::std::option::Option); + + fn matches_ref_uri(&self, &str) -> bool; + } + + fn parse_ls_remote_line(&str) -> ::std::option::Option; + + type ::std::option::Option { + #layout_conservative(size=56, align=8); + + constructor None; + constructor Some(LsRemoteRefLine); + + fn unwrap(self) -> LsRemoteRefLine; + } + +} diff --git a/lix/lix-rs/src/fetchers/git.rs b/lix/lix-rs/src/fetchers/git.rs new file mode 100644 index 000000000..6ca245754 --- /dev/null +++ b/lix/lix-rs/src/fetchers/git.rs @@ -0,0 +1,104 @@ +use std::sync::LazyLock; + +use regex::Regex; + +#[derive(Debug, Eq, PartialEq)] +pub enum Kind { + Symbolic, + Object, +} + +/// +/// A line from the output of `git ls-remote --symref` +/// +/// These can be of two kinds: +/// +/// - Symbolic references of the form +/// ref: {target} {references} +/// where {target} is itself a reference and {reference} is optional +/// +/// - Object reference of the form +/// {target} {reference} +/// where {target} is a commit id and {reference} is mandatory +pub struct LsRemoteRefLine { + pub kind: Kind, + pub target: String, + pub reference: Option, +} + +impl LsRemoteRefLine { + pub fn matches_ref_uri(&self, uri: &str) -> bool { + let uri_regex = Regex::new(uri).unwrap(); + match &self.reference { + None => false, + Some(r) => uri_regex.is_match(r.as_str()), + } + } +} + +static LS_REMOTE_REF_REGEX: LazyLock = + LazyLock::new(|| Regex::new(r"^(ref: *)?([^\s]+)(?:\t+(.*))?$").unwrap()); + +pub fn parse_ls_remote_line(line: &str) -> Option { + LS_REMOTE_REF_REGEX.captures(line).map(|m| { + let kind = m + .get(1) + .filter(|ma| !ma.is_empty()) + .map_or(Kind::Object, |_| Kind::Symbolic); + let re = m.get(3).map(|ma| ma.as_str()).map(|ma| { + if ma.is_empty() { + None + } else { + Some(ma.to_string()) + } + }); + + LsRemoteRefLine { + kind: kind, + target: m + .get(2) + .expect("regex for matching remotes has two capture groups defined, so this should not error") + .as_str() + .to_string(), + reference: re.flatten(), + } + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_symref_line_with_reference() { + let line = "ref: refs/head/main\tHEAD"; + let res = parse_ls_remote_line(line); + assert!(res.is_some()); + let res = res.unwrap(); + assert_eq!(res.kind, Kind::Symbolic); + assert_eq!(res.target, "refs/head/main"); + assert_eq!(res.reference, Some("HEAD".to_string())); + } + + #[test] + fn parse_symref_line_with_no_reference() { + let line = "ref: refs/head/main"; + let res = parse_ls_remote_line(line); + assert!(res.is_some()); + let res = res.unwrap(); + assert_eq!(res.kind, Kind::Symbolic); + assert_eq!(res.target, "refs/head/main"); + assert_eq!(res.reference, None); + } + + #[test] + fn parse_object_ref_line() { + let line = "abc123 refs/head/main"; + let res = parse_ls_remote_line(line); + assert!(res.is_some()); + let res = res.unwrap(); + assert_eq!(res.kind, Kind::Object); + assert_eq!(res.target, "abc123"); + assert_eq!(res.reference, Some("refs/head/main".to_string())); + } +} diff --git a/lix/lix-rs/src/fetchers/mod.rs b/lix/lix-rs/src/fetchers/mod.rs new file mode 100644 index 000000000..c2bf1c3ee --- /dev/null +++ b/lix/lix-rs/src/fetchers/mod.rs @@ -0,0 +1 @@ +pub mod git; diff --git a/lix/lix-rs/src/lib.rs b/lix/lix-rs/src/lib.rs index 44e5d19b2..2848f3fda 100644 --- a/lix/lix-rs/src/lib.rs +++ b/lix/lix-rs/src/lib.rs @@ -145,5 +145,6 @@ pub(crate) mod log { } } +pub mod fetchers; mod ffi_test; mod repl; diff --git a/lix/lix-rs/src/lib.zng b/lix/lix-rs/src/lib.zng index ac691b626..b3fa26b62 100644 --- a/lix/lix-rs/src/lib.zng +++ b/lix/lix-rs/src/lib.zng @@ -1,5 +1,6 @@ merge "./ffi_test.zng"; merge "./repl.zng"; +merge "./fetchers.zng"; mod crate::ffi { use ::std::result::Result as Result; diff --git a/lix/lix-rs/std-common.zng b/lix/lix-rs/std-common.zng index 915b195d4..4e79c130c 100644 --- a/lix/lix-rs/std-common.zng +++ b/lix/lix-rs/std-common.zng @@ -35,6 +35,15 @@ mod ::std::option { fn unwrap(self) -> u64; } + + type Option { + #layout_conservative(size=24, align=8); + + constructor Some(String); + constructor None; + + fn unwrap(self) -> String; + } } mod ::std::result { diff --git a/tests/unit/libutil/git.cc b/tests/unit/libutil/git.cc deleted file mode 100644 index 1dbdf4896..000000000 --- a/tests/unit/libutil/git.cc +++ /dev/null @@ -1,32 +0,0 @@ -#include "lix/libutil/git.hh" -#include - -namespace nix { - - TEST(GitLsRemote, parseSymrefLineWithReference) { - auto line = "ref: refs/head/main HEAD"; - auto res = git::parseLsRemoteLine(line); - ASSERT_TRUE(res.has_value()); - ASSERT_EQ(res->kind, git::LsRemoteRefLine::Kind::Symbolic); - ASSERT_EQ(res->target, "refs/head/main"); - ASSERT_EQ(res->reference, "HEAD"); - } - - TEST(GitLsRemote, parseSymrefLineWithNoReference) { - auto line = "ref: refs/head/main"; - auto res = git::parseLsRemoteLine(line); - ASSERT_TRUE(res.has_value()); - ASSERT_EQ(res->kind, git::LsRemoteRefLine::Kind::Symbolic); - ASSERT_EQ(res->target, "refs/head/main"); - ASSERT_EQ(res->reference, std::nullopt); - } - - TEST(GitLsRemote, parseObjectRefLine) { - auto line = "abc123 refs/head/main"; - auto res = git::parseLsRemoteLine(line); - ASSERT_TRUE(res.has_value()); - ASSERT_EQ(res->kind, git::LsRemoteRefLine::Kind::Object); - ASSERT_EQ(res->target, "abc123"); - ASSERT_EQ(res->reference, "refs/head/main"); - } -} diff --git a/tests/unit/meson.build b/tests/unit/meson.build index d7f950a94..267661245 100644 --- a/tests/unit/meson.build +++ b/tests/unit/meson.build @@ -55,7 +55,6 @@ libutil_tests_sources = files( 'libutil/escape-string.cc', 'libutil/fmt.cc', 'libutil/generator.cc', - 'libutil/git.cc', 'libutil/hash.cc', 'libutil/hilite.cc', 'libutil/io-buffer.cc',