Skip to content

Commit f89afd3

Browse files
committed
fix: ensure vendored grammar is clean before fetch
1 parent 948ac05 commit f89afd3

1 file changed

Lines changed: 79 additions & 44 deletions

File tree

helix-loader/src/grammar.rs

Lines changed: 79 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -265,68 +265,103 @@ enum FetchStatus {
265265
}
266266

267267
fn fetch_grammar(grammar: GrammarConfiguration) -> Result<FetchStatus> {
268-
if let GrammarSource::Git {
268+
let GrammarSource::Git {
269269
remote, revision, ..
270270
} = grammar.source
271-
{
272-
let grammar_dir = crate::runtime_dirs()
271+
else {
272+
return Ok(FetchStatus::NonGit);
273+
};
274+
275+
let repo = VendoredGrammar::new(&grammar.grammar_id);
276+
277+
// WARN: Must init before other operations are done.
278+
repo.init(&remote)?;
279+
280+
if repo.revision().as_ref() == Some(&revision) {
281+
return Ok(FetchStatus::GitUpToDate);
282+
}
283+
284+
// Fetch the grammar if the revision doesn't match.
285+
repo.fetch(&remote, &revision)?;
286+
287+
Ok(FetchStatus::GitUpdated { revision })
288+
}
289+
290+
struct VendoredGrammar {
291+
dir: PathBuf,
292+
}
293+
294+
impl VendoredGrammar {
295+
fn new(grammar: &str) -> Self {
296+
let dir = crate::runtime_dirs()
273297
.first()
274298
.expect("No runtime directories provided") // guaranteed by post-condition
275299
.join("grammars")
276300
.join("sources")
277-
.join(&grammar.grammar_id);
301+
.join(grammar);
302+
303+
Self { dir }
304+
}
305+
306+
/// Gets the current revision of the repo.
307+
fn revision(&self) -> Option<String> {
308+
git(&self.dir, ["rev-parse", "HEAD"]).ok()
309+
}
278310

279-
fs::create_dir_all(&grammar_dir).context(format!(
311+
/// Fetches grammar at the given revision.
312+
///
313+
/// To ensure clean state, existing grammar directory is removed and re-inited
314+
/// before fetch operation.
315+
fn fetch(&self, remote: &str, rev: &str) -> Result<()> {
316+
self.reinit(remote)?;
317+
318+
git(&self.dir, ["fetch", "--depth", "1", REMOTE_NAME, rev])?;
319+
git(&self.dir, ["checkout", rev])?;
320+
321+
Ok(())
322+
}
323+
324+
/// Initializes the grammar directory.
325+
///
326+
/// Creates directory and sets it up as a git repo, with remote set correctly.
327+
fn init(&self, remote: &str) -> Result<()> {
328+
// Create the grammar directory if needed.
329+
fs::create_dir_all(&self.dir).context(format!(
280330
"Could not create grammar directory {:?}",
281-
grammar_dir
331+
&self.dir
282332
))?;
283333

284-
// create the grammar dir contains a git directory
285-
if !grammar_dir.join(".git").exists() {
286-
git(&grammar_dir, ["init"])?;
334+
// Ensure directory is git initialized.
335+
if !self.dir.join(".git").exists() {
336+
git(&self.dir, ["init"])?;
287337
}
288338

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)?;
339+
// Ensure the remote matches the configured remote, setting if needed.
340+
if self.remote().as_deref() != Some(remote) {
341+
self.set_remote(remote)?;
292342
}
293343

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)
344+
Ok(())
311345
}
312-
}
313346

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-
}
347+
/// Removes the grammar directory before initializing again.
348+
fn reinit(&self, remote: &str) -> Result<()> {
349+
fs::remove_dir_all(&self.dir)?;
350+
self.init(remote)?;
351+
Ok(())
352+
}
323353

324-
fn get_remote_url(repository_dir: &Path) -> Option<String> {
325-
git(repository_dir, ["remote", "get-url", REMOTE_NAME]).ok()
326-
}
354+
/// Gets remote URL of grammar repo.
355+
fn remote(&self) -> Option<String> {
356+
git(&self.dir, ["remote", "get-url", REMOTE_NAME]).ok()
357+
}
327358

328-
fn get_revision(repository_dir: &Path) -> Option<String> {
329-
git(repository_dir, ["rev-parse", "HEAD"]).ok()
359+
/// Sets remote URL of grammar repo.
360+
fn set_remote(&self, remote: &str) -> Result<()> {
361+
git(&self.dir, ["remote", "set-url", REMOTE_NAME, remote])
362+
.or_else(|_| git(&self.dir, ["remote", "add", REMOTE_NAME, remote]))?;
363+
Ok(())
364+
}
330365
}
331366

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

0 commit comments

Comments
 (0)