Skip to content

Commit d9f9fd2

Browse files
RoloEditsfryeb
authored andcommitted
fix: ensure vendored grammar is clean before fetch (helix-editor#14817)
1 parent e6c62ea commit d9f9fd2

1 file changed

Lines changed: 81 additions & 46 deletions

File tree

helix-loader/src/grammar.rs

Lines changed: 81 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -264,69 +264,104 @@ enum FetchStatus {
264264
NonGit,
265265
}
266266

267-
fn fetch_grammar(grammar: GrammarConfiguration) -> Result<FetchStatus> {
268-
if let GrammarSource::Git {
269-
remote, revision, ..
270-
} = grammar.source
271-
{
272-
let grammar_dir = crate::runtime_dirs()
267+
struct VendoredGrammar {
268+
dir: PathBuf,
269+
}
270+
271+
impl VendoredGrammar {
272+
fn new(grammar: &str) -> Self {
273+
let dir = crate::runtime_dirs()
273274
.first()
274275
.expect("No runtime directories provided") // guaranteed by post-condition
275276
.join("grammars")
276277
.join("sources")
277-
.join(&grammar.grammar_id);
278+
.join(grammar);
279+
280+
Self { dir }
281+
}
282+
283+
/// Gets the current revision of the repo.
284+
fn revision(&self) -> Option<String> {
285+
git(&self.dir, ["rev-parse", "HEAD"]).ok()
286+
}
278287

279-
fs::create_dir_all(&grammar_dir).context(format!(
288+
/// Fetches grammar at the given revision.
289+
///
290+
/// To ensure clean state, existing grammar directory is removed and re-inited
291+
/// before fetch operation.
292+
fn fetch(&self, remote: &str, rev: &str) -> Result<()> {
293+
self.reinit(remote)?;
294+
295+
git(&self.dir, ["fetch", "--depth", "1", REMOTE_NAME, rev])?;
296+
git(&self.dir, ["checkout", rev])?;
297+
298+
Ok(())
299+
}
300+
301+
/// Initializes the grammar directory.
302+
///
303+
/// Creates directory and sets it up as a git repo, with remote set correctly.
304+
fn init(&self, remote: &str) -> Result<()> {
305+
// Create the grammar directory if needed.
306+
fs::create_dir_all(&self.dir).context(format!(
280307
"Could not create grammar directory {:?}",
281-
grammar_dir
308+
&self.dir
282309
))?;
283310

284-
// create the grammar dir contains a git directory
285-
if !grammar_dir.join(".git").exists() {
286-
git(&grammar_dir, ["init"])?;
311+
// Ensure directory is git initialized.
312+
if !self.dir.join(".git").exists() {
313+
git(&self.dir, ["init"])?;
287314
}
288315

289-
// ensure the remote matches the configured remote
290-
if get_remote_url(&grammar_dir).as_ref() != Some(&remote) {
291-
set_remote(&grammar_dir, &remote)?;
316+
// Ensure the remote matches the configured remote, setting if needed.
317+
if self.remote().as_deref() != Some(remote) {
318+
self.set_remote(remote)?;
292319
}
293320

294-
// ensure the revision matches the configured revision
295-
if get_revision(&grammar_dir).as_ref() != Some(&revision) {
296-
// Fetch the exact revision from the remote.
297-
// Supported by server-side git since v2.5.0 (July 2015),
298-
// enabled by default on major git hosts.
299-
git(
300-
&grammar_dir,
301-
["fetch", "--depth", "1", REMOTE_NAME, &revision],
302-
)?;
303-
git(&grammar_dir, ["checkout", &revision])?;
304-
305-
Ok(FetchStatus::GitUpdated { revision })
306-
} else {
307-
Ok(FetchStatus::GitUpToDate)
308-
}
309-
} else {
310-
Ok(FetchStatus::NonGit)
321+
Ok(())
311322
}
312-
}
313323

314-
// Sets the remote for a repository to the given URL, creating the remote if
315-
// it does not yet exist.
316-
fn set_remote(repository_dir: &Path, remote_url: &str) -> Result<String> {
317-
git(
318-
repository_dir,
319-
["remote", "set-url", REMOTE_NAME, remote_url],
320-
)
321-
.or_else(|_| git(repository_dir, ["remote", "add", REMOTE_NAME, remote_url]))
322-
}
324+
/// Removes the grammar directory before initializing again.
325+
fn reinit(&self, remote: &str) -> Result<()> {
326+
fs::remove_dir_all(&self.dir)?;
327+
self.init(remote)?;
328+
Ok(())
329+
}
330+
331+
/// Gets remote URL of grammar repo.
332+
fn remote(&self) -> Option<String> {
333+
git(&self.dir, ["remote", "get-url", REMOTE_NAME]).ok()
334+
}
323335

324-
fn get_remote_url(repository_dir: &Path) -> Option<String> {
325-
git(repository_dir, ["remote", "get-url", REMOTE_NAME]).ok()
336+
/// Sets remote URL of grammar repo.
337+
fn set_remote(&self, remote: &str) -> Result<()> {
338+
git(&self.dir, ["remote", "set-url", REMOTE_NAME, remote])
339+
.or_else(|_| git(&self.dir, ["remote", "add", REMOTE_NAME, remote]))?;
340+
Ok(())
341+
}
326342
}
327343

328-
fn get_revision(repository_dir: &Path) -> Option<String> {
329-
git(repository_dir, ["rev-parse", "HEAD"]).ok()
344+
fn fetch_grammar(grammar: GrammarConfiguration) -> Result<FetchStatus> {
345+
let GrammarSource::Git {
346+
remote, revision, ..
347+
} = grammar.source
348+
else {
349+
return Ok(FetchStatus::NonGit);
350+
};
351+
352+
let repo = VendoredGrammar::new(&grammar.grammar_id);
353+
354+
// WARN: Must init before other operations are done.
355+
repo.init(&remote)?;
356+
357+
if repo.revision().is_some_and(|rev| rev == revision) {
358+
return Ok(FetchStatus::GitUpToDate);
359+
}
360+
361+
// Fetch the grammar if the revision doesn't match.
362+
repo.fetch(&remote, &revision)?;
363+
364+
Ok(FetchStatus::GitUpdated { revision })
330365
}
331366

332367
// A wrapper around 'git' commands which returns stdout in success and a

0 commit comments

Comments
 (0)