Unnamed repository; edit this file 'description' to name the repository.
feat(helix-loader): add support for SHA256 git repos for grammars (#15466)
The helix-loader is currently running a `git init` prior to fetching
objects. This initialize the git repo with the default object format,
which currently is SHA1. A subsequent `fetch` will fail if the repo
instead uses SHA256.
The same issue will pop up when `git init` will change the default to
SHA256: A fetch from a SHA1 repo will then fail, as the init was down
with SHA256.
This adds an explicit object format argument to the rust functions, and
extends the TOML format in a backward compatible way:
1. If the revision is prefixed `sha256:`: use SHA256
2. If the revision is prefixed `sha1:`: use SHA1
3. If the revision is a valid SHA256 hash (which is longer than a valid
SHA1 hash): use SHA256
4. In any other case: Use SHA1
| -rw-r--r-- | helix-loader/src/grammar.rs | 56 |
1 files changed, 47 insertions, 9 deletions
diff --git a/helix-loader/src/grammar.rs b/helix-loader/src/grammar.rs index 4e9f2156..501e9b47 100644 --- a/helix-loader/src/grammar.rs +++ b/helix-loader/src/grammar.rs @@ -298,6 +298,37 @@ enum FetchStatus { NonGit, } +#[derive(Copy, Clone)] +enum GitObjectFormat { + Sha1, + Sha256, +} + +impl GitObjectFormat { + fn as_str(&self) -> &'static str { + match self { + Self::Sha1 => "sha1", + Self::Sha256 => "sha256", + } + } +} + +fn extract_object_format_from_revision(rev: &str) -> (GitObjectFormat, &str) { + if let Some(stripped) = rev.strip_prefix("sha1:") { + return (GitObjectFormat::Sha1, stripped); + } + + if let Some(stripped) = rev.strip_prefix("sha256:") { + return (GitObjectFormat::Sha256, stripped); + } + + if rev.len() == 64 && rev.bytes().all(|b| b.is_ascii_hexdigit()) { + return (GitObjectFormat::Sha256, rev); + } + + (GitObjectFormat::Sha1, rev) +} + struct VendoredGrammar { dir: PathBuf, } @@ -323,8 +354,8 @@ impl VendoredGrammar { /// /// To ensure clean state, existing grammar directory is removed and re-inited /// before fetch operation. - fn fetch(&self, remote: &str, rev: &str) -> Result<()> { - self.reinit(remote)?; + fn fetch(&self, remote: &str, rev: &str, object_format: GitObjectFormat) -> Result<()> { + self.reinit(remote, object_format)?; git(&self.dir, ["fetch", "--depth", "1", REMOTE_NAME, rev])?; git(&self.dir, ["checkout", rev])?; @@ -335,7 +366,7 @@ impl VendoredGrammar { /// Initializes the grammar directory. /// /// Creates directory and sets it up as a git repo, with remote set correctly. - fn init(&self, remote: &str) -> Result<()> { + fn init(&self, remote: &str, object_format: GitObjectFormat) -> Result<()> { // Create the grammar directory if needed. fs::create_dir_all(&self.dir).context(format!( "Could not create grammar directory {:?}", @@ -344,7 +375,10 @@ impl VendoredGrammar { // Ensure directory is git initialized. if !self.dir.join(".git").exists() { - git(&self.dir, ["init"])?; + git( + &self.dir, + ["init", "--object-format", object_format.as_str()], + )?; } // Ensure the remote matches the configured remote, setting if needed. @@ -356,9 +390,9 @@ impl VendoredGrammar { } /// Removes the grammar directory before initializing again. - fn reinit(&self, remote: &str) -> Result<()> { + fn reinit(&self, remote: &str, object_format: GitObjectFormat) -> Result<()> { fs::remove_dir_all(&self.dir)?; - self.init(remote)?; + self.init(remote, object_format)?; Ok(()) } @@ -385,17 +419,21 @@ fn fetch_grammar(grammar: GrammarConfiguration) -> Result<FetchStatus> { let repo = VendoredGrammar::new(&grammar.grammar_id); + let (object_format, revision) = extract_object_format_from_revision(&revision); + // WARN: Must init before other operations are done. - repo.init(&remote)?; + repo.init(&remote, object_format)?; if repo.revision().is_some_and(|rev| rev == revision) { return Ok(FetchStatus::GitUpToDate); } // Fetch the grammar if the revision doesn't match. - repo.fetch(&remote, &revision)?; + repo.fetch(&remote, revision, object_format)?; - Ok(FetchStatus::GitUpdated { revision }) + Ok(FetchStatus::GitUpdated { + revision: revision.to_string(), + }) } // A wrapper around 'git' commands which returns stdout in success and a |