Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

Commit

Fix dependency update config reads and recover failed checks

syntaqxcommitted Parent162f45aBrowse files
4 files+62−190/4 viewed
+7−2
2929 // Checking for updates now takes Write, as pushing does.
3030 const refused = await refusal(context, params, "push");
3131 if (refused) return { ok: false, error: refused };
32− const checked = await security.checkUpdates(user, { namespace: params.owner, name: params.repo }, String(form.get("entry") ?? ""));
33− return checked.ok ? { ok: true } : { ok: false, error: checked.error.message };
32+ try {
33+ const checked = await security.checkUpdates(user, { namespace: params.owner, name: params.repo }, String(form.get("entry") ?? ""));
34+ return checked.ok ? { ok: true } : { ok: false, error: checked.error.message };
35+ } catch (error) {
36+ console.error("dependency update check failed", error);
37+ return { ok: false, error: "The dependency update service could not complete this check. Please try again in a few minutes." };
38+ }
3439 }
3540
3641 /** The dependency update file g1t reads, each entry's schedule, and the pull requests it opened. */
+1−0
519519 pub struct BlobArgs {
520520 pub path: RepoPath,
521521 pub viewer: Viewer,
522+ /// Empty selects the repository's default branch.
522523 #[serde(rename = "ref")]
523524 pub git_ref: String,
524525 pub file_path: String,
+22−2
951951 let Some(repo) = self.readable(&a.path, &a.viewer).await? else {
952952 return Ok(not_found());
953953 };
954+ let git_ref = blob_ref(&a.git_ref, &repo.default_branch).to_owned();
954955 let bytes = if a.file_path.is_empty() {
955956 None
956957 } else {
957958 let git = self.read_git(&repo).await?;
958− git.read_file(&a.git_ref, &a.file_path).await?
959+ git.read_file(&git_ref, &a.file_path).await?
959960 };
960961 let Some(bytes) = bytes else {
961962 return Ok(Outcome::fail(FailureCode::NotFound, "No such file."));
962963 };
963964 Ok(Outcome::Ok(BlobView {
964965 repo,
965− git_ref: a.git_ref,
966+ git_ref,
966967 path: a.file_path,
967968 size: bytes.len() as u64,
968969 text: text_of(bytes),
35803581 assert!(why.contains("not there"), "{why}");
35813582 }
35823583 }
3584+
3585+/// Blob callers may omit a ref to read the configured default branch.
3586+fn blob_ref<'a>(asked: &'a str, default_branch: &'a str) -> &'a str {
3587+ if asked.is_empty() { default_branch } else { asked }
3588+}
3589+
3590+#[cfg(test)]
3591+mod blob_ref_tests {
3592+ use super::blob_ref;
3593+
3594+ #[test]
3595+ fn omitted_blob_ref_uses_the_repository_default_and_preserves_explicit_refs() {
3596+ assert_eq!(blob_ref("", "main"), "main");
3597+ assert_eq!(blob_ref("", "release/current"), "release/current");
3598+ for explicit in ["HEAD", "feature/dependencies", "refs/tags/v1", "0123456789abcdef0123456789abcdef01234567"] {
3599+ assert_eq!(blob_ref(explicit, "main"), explicit);
3600+ }
3601+ }
3602+}
+32−15
437437 None => (None, None, None),
438438 },
439439 Ok(None) => (None, Some("The dependency update file is missing or has problems.".to_owned()), None),
440− Err(error) => (None, Some(format!("The dependency update file could not be read: {error}")), None),
440+ Err(error) => (None, Some(format!("The dependency update file could not be read: {error}")), Some(config_retry_at(now_ms()))),
441441 };
442442 self.store.finish_run(&repo.repo_id, entry_id, next.as_deref(), result.as_deref(), error.as_deref()).await
443443 }
458458 if !entry.supported {
459459 return Ok(Outcome::fail(FailureCode::Invalid, format!("g1t does not open version updates for {} yet.", entry.ecosystem)));
460460 }
461+ // Read the previous schedule before claiming, so a failed read cannot strand the claim.
462+ let mut next = self.store.runs(&repo.repo_id).await?.into_iter().find(|run| run.entry == a.entry).and_then(|run| run.next_run_at);
461463 if !self.store.claim_run(&repo.repo_id, &a.entry).await? {
462464 return Ok(Outcome::fail(FailureCode::Conflict, "This entry is being checked now."));
463465 }
464466 // Claimed: run_claimed would claim again, so run it here.
465− let loaded = self.load_config(&repo).await?;
466− let (result, error) = match loaded.as_ref().and_then(|loaded| loaded.config.updates.iter().find(|entry| entry.id() == a.entry).map(|entry| (loaded, entry))) {
467− Some((loaded, entry)) => match self.run_entry(&repo, loaded, entry).await {
468− Ok(Ok(summary)) => (Some(summary), None),
469− Ok(Err(problem)) => (None, Some(problem)),
470− Err(error) => (None, Some(format!("The check could not finish: {error}"))),
467+ let (result, error) = match self.load_config(&repo).await {
468+ Ok(loaded) => match loaded.as_ref().and_then(|loaded| loaded.config.updates.iter().find(|entry| entry.id() == a.entry).map(|entry| (loaded, entry))) {
469+ Some((loaded, entry)) => match self.run_entry(&repo, loaded, entry).await {
470+ Ok(Ok(summary)) => (Some(summary), None),
471+ Ok(Err(problem)) => (None, Some(problem)),
472+ Err(error) => (None, Some(format!("The check could not finish: {error}"))),
473+ },
474+ None => (None, Some("The dependency update file is missing or has problems.".to_owned())),
471475 },
472− None => (None, Some("The dependency update file is missing or has problems.".to_owned())),
476+ Err(error) => {
477+ // Finish the claim even when the repository service is unavailable.
478+ next = Some(config_retry_at(now_ms()));
479+ (None, Some(format!("The dependency update file could not be read: {error}")))
480+ }
473481 };
474− let next = self
475− .store
476− .runs(&repo.repo_id)
477− .await?
478− .into_iter()
479− .find(|run| run.entry == a.entry)
480− .and_then(|run| run.next_run_at);
481482 self.store.finish_run(&repo.repo_id, &a.entry, next.as_deref(), result.as_deref(), error.as_deref()).await?;
482483 Ok(Outcome::Ok(self.version_updates_view(&repo).await?))
483484 }
16671668 assert_eq!(package_ecosystem("crates.io"), Some("cargo"));
16681669 }
16691670 }
1671+
1672+/// A transient repository read failure retries soon instead of losing its schedule.
1673+fn config_retry_at(now: u64) -> String {
1674+ rfc3339(now.saturating_add(5 * 60 * 1000))
1675+}
1676+
1677+#[cfg(test)]
1678+mod config_retry_tests {
1679+ use super::*;
1680+
1681+ #[test]
1682+ fn failed_config_reads_retry_in_five_minutes() {
1683+ let now = 1_700_000_000_000;
1684+ assert_eq!(parse_rfc3339(&config_retry_at(now)), Some(now + 300_000));
1685+ }
1686+}

Comments

Sign in to comment.