[F] Fix heuristic

This commit is contained in:
2026-05-07 02:22:34 +00:00
parent 92bcee49ea
commit b70eaee2aa
3 changed files with 99 additions and 5 deletions
+58 -3
View File
@@ -109,6 +109,27 @@ impl GitMirror {
self.run(["fetch", "--prune", &remote.name, &tag_refspec])
}
pub fn cached_remote_refs_match(
&self,
remote: &RemoteSpec,
expected: &RemoteRefSnapshot,
) -> Result<bool> {
if !self.path.exists() || self.dry_run {
return Ok(false);
}
let branches = self.remote_branches(&remote.name)?;
let tags = self.remote_tags(&remote.name)?;
let mut refs = Vec::with_capacity(branches.len() + tags.len());
for (branch, sha) in branches {
refs.push(format!("{sha}\trefs/heads/{branch}"));
}
for (tag, sha) in tags {
refs.push(format!("{sha}\trefs/tags/{tag}"));
}
let snapshot = snapshot_from_refs(refs);
Ok(&snapshot == expected)
}
pub fn branch_decisions(
&self,
remotes: &[RemoteSpec],
@@ -432,18 +453,23 @@ pub fn ls_remote_refs(remote: &RemoteSpec, redactor: &Redactor) -> Result<Remote
return Err(GitCommandError::new("git ls-remote", stdout, stderr).into());
}
let mut refs = String::from_utf8_lossy(&output.stdout)
let refs = String::from_utf8_lossy(&output.stdout)
.lines()
.map(str::trim)
.filter(|line| !line.is_empty())
.map(ToOwned::to_owned)
.collect::<Vec<_>>();
Ok(snapshot_from_refs(refs))
}
fn snapshot_from_refs(mut refs: Vec<String>) -> RemoteRefSnapshot {
refs.sort();
Ok(RemoteRefSnapshot {
RemoteRefSnapshot {
hash: stable_ref_hash(&refs),
refs: refs.len(),
})
}
}
fn stable_ref_hash(refs: &[String]) -> String {
@@ -698,6 +724,35 @@ mod tests {
assert!(main.target_remotes.is_empty());
}
#[test]
fn cached_remote_refs_match_ls_remote_snapshot_after_fetch() {
let fixture = GitFixture::new();
fixture.commit("base", "base", 1_700_000_000);
fixture.tag("v1");
fixture.push_head(&fixture.remote_a, "main");
fixture.push_tag(&fixture.remote_a, "v1");
let mirror = fixture.mirror();
let remote = fixture.remotes().remove(0);
assert!(
!mirror
.cached_remote_refs_match(
&remote,
&ls_remote_refs(&remote, &Redactor::new(Vec::new())).unwrap(),
)
.unwrap()
);
mirror.fetch_remote(&remote).unwrap();
let snapshot = ls_remote_refs(&remote, &Redactor::new(Vec::new())).unwrap();
assert!(mirror.cached_remote_refs_match(&remote, &snapshot).unwrap());
fixture.commit("newer", "newer", 1_700_000_100);
fixture.push_head(&fixture.remote_a, "main");
let changed = ls_remote_refs(&remote, &Redactor::new(Vec::new())).unwrap();
assert!(!mirror.cached_remote_refs_match(&remote, &changed).unwrap());
}
#[test]
fn branch_decisions_report_divergent_tips_without_force() {
let fixture = GitFixture::new();
+40 -1
View File
@@ -230,6 +230,15 @@ impl From<RemoteRefSnapshot> for RemoteRefState {
}
}
impl From<&RemoteRefState> for RemoteRefSnapshot {
fn from(value: &RemoteRefState) -> Self {
Self {
hash: value.hash.clone(),
refs: value.refs,
}
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
struct RefState {
#[serde(default)]
@@ -659,8 +668,9 @@ fn sync_repo(
let Some(initial_ref_state) = check_remote_refs(context, repo_name, &initial_remotes)? else {
return Ok(RepoSyncOutcome::default());
};
let all_endpoints_present = all_configured_endpoints_present(context.mirror, repos);
if !context.dry_run
&& all_configured_endpoints_present(context.mirror, repos)
&& all_endpoints_present
&& ref_state.repo_matches(&context.mirror.name, repo_name, &initial_ref_state)
{
crate::logln!(
@@ -677,6 +687,19 @@ fn sync_repo(
let mirror_repo = GitMirror::open(path, context.redactor.clone(), context.dry_run)?;
mirror_repo.configure_remotes(&initial_remotes)?;
if !context.dry_run
&& all_endpoints_present
&& cached_refs_match(&mirror_repo, &initial_remotes, &initial_ref_state)?
{
crate::logln!(
" {} refs unchanged from local mirror cache",
style("up-to-date").green().bold()
);
return Ok(RepoSyncOutcome {
ref_update: Some(initial_ref_state),
});
}
for remote in &initial_remotes {
if let Err(error) = mirror_repo.fetch_remote(remote) {
if is_disabled_repository_error(&error) {
@@ -763,6 +786,22 @@ fn all_configured_endpoints_present(mirror: &MirrorConfig, repos: &[EndpointRepo
.all(|endpoint| present.contains(endpoint))
}
fn cached_refs_match(
mirror_repo: &GitMirror,
remotes: &[RemoteSpec],
expected_refs: &BTreeMap<String, RemoteRefState>,
) -> Result<bool> {
for remote in remotes {
let Some(expected) = expected_refs.get(&remote.name) else {
return Ok(false);
};
if !mirror_repo.cached_remote_refs_match(remote, &RemoteRefSnapshot::from(expected))? {
return Ok(false);
}
}
Ok(true)
}
fn check_remote_refs(
context: &RepoSyncContext<'_>,
repo_name: &str,