[F] Fix heuristic
This commit is contained in:
+58
-3
@@ -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
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user