diff --git a/README.md b/README.md index 67a6748..f023756 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,6 @@ repo_whitelist = ["^important-"] repo_blacklist = ["-archive$"] create_missing = true visibility = "private" -allow_force = false conflict_resolution = "auto_rebase_pull_request" [[mirrors.endpoints]] @@ -111,13 +110,13 @@ Preview commands without writing to Git remotes: refray sync --dry-run ``` -Sync only repositories whose names match a regex: +Skip repository creation even when `create_missing = true` in the mirror group: ```sh -refray sync --repo-pattern '^(foo|bar)-' +refray sync --no-create ``` -For persistent per-group filters, set `repo_whitelist` and/or `repo_blacklist` in the config instead. `--repo-pattern` is an extra one-off filter applied on top of the group config. +To restrict which repositories sync, set `repo_whitelist` and/or `repo_blacklist` on the mirror group in config. Retry only repositories that failed during the previous non-dry-run sync: @@ -194,7 +193,6 @@ Branch conflict handling is intentionally conservative: - If all endpoints agree on a branch tip, that tip is pushed everywhere. - If one branch tip is a descendant of the others, the descendant wins and is pushed everywhere. - If branch tips diverged, `conflict_resolution` controls what happens. -- If `allow_force = true` or `refray sync --force` is used, a diverged branch chooses the newest commit timestamp and force-pushes it. Conflict resolution strategies are configured per mirror group: diff --git a/src/config.rs b/src/config.rs index 8c6c012..5e1135d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -66,8 +66,6 @@ pub struct MirrorConfig { #[serde(default)] pub visibility: Visibility, #[serde(default)] - pub allow_force: bool, - #[serde(default)] pub conflict_resolution: ConflictResolutionStrategy, } diff --git a/src/git.rs b/src/git.rs index 0983f78..640567d 100644 --- a/src/git.rs +++ b/src/git.rs @@ -166,7 +166,6 @@ impl GitMirror { pub fn branch_decisions( &self, remotes: &[RemoteSpec], - allow_force: bool, ) -> Result<(Vec, Vec)> { let mut by_branch: BTreeMap> = BTreeMap::new(); for remote in remotes { @@ -218,20 +217,6 @@ impl GitMirror { source_remotes, target_remotes, }); - } else if allow_force { - let winner = self.newest_commit(unique.iter())?; - let source_remotes = tips - .iter() - .filter_map(|(remote, sha)| (sha == &winner).then_some(remote)) - .cloned() - .collect::>(); - let target_remotes = missing_remotes(&all_remote_names, &source_remotes); - decisions.push(BranchDecision { - branch, - sha: winner, - source_remotes, - target_remotes, - }); } else { conflicts.push(BranchConflict { branch, tips }); } @@ -286,22 +271,13 @@ impl GitMirror { Ok((decisions, conflicts)) } - pub fn push_branches( - &self, - remotes: &[RemoteSpec], - branches: &[BranchDecision], - force: bool, - ) -> Result<()> { + pub fn push_branches(&self, remotes: &[RemoteSpec], branches: &[BranchDecision]) -> Result<()> { for remote in remotes { for branch in branches { if !branch.target_remotes.contains(&remote.name) { continue; } - let refspec = if force { - format!("+{}:refs/heads/{}", branch.sha, branch.branch) - } else { - format!("{}:refs/heads/{}", branch.sha, branch.branch) - }; + let refspec = format!("{}:refs/heads/{}", branch.sha, branch.branch); crate::logln!( " {} {} {} {}", style("push").green().bold(), @@ -533,23 +509,6 @@ impl GitMirror { Ok(None) } - fn newest_commit<'a>(&self, shas: impl Iterator) -> Result { - let mut newest: Option<(i64, String)> = None; - for sha in shas { - let timestamp = self - .output(["show", "-s", "--format=%ct", sha])? - .trim() - .parse::()?; - match &newest { - Some((old, _)) if *old >= timestamp => {} - _ => newest = Some((timestamp, sha.clone())), - } - } - newest - .map(|(_, sha)| sha) - .context("no commits found while choosing force winner") - } - fn merge_base(&self, left: &str, right: &str) -> Result { Ok(self.output(["merge-base", left, right])?.trim().to_string()) } diff --git a/src/interactive.rs b/src/interactive.rs index 2f35f06..e2f1bab 100644 --- a/src/interactive.rs +++ b/src/interactive.rs @@ -126,7 +126,6 @@ fn add_sync_group_styled(config: &mut Config, theme: &ColorfulTheme) -> Result<( repo_blacklist: repo_filters.blacklist, create_missing: true, visibility: Visibility::Private, - allow_force: false, conflict_resolution, }); prompt_webhook_setup_styled(config, theme)?; diff --git a/src/main.rs b/src/main.rs index 643334d..4632683 100644 --- a/src/main.rs +++ b/src/main.rs @@ -49,24 +49,18 @@ struct SyncCommand { group: Option, #[arg(long)] dry_run: bool, + /// Do not create repositories that are missing from an endpoint. #[arg(long)] no_create: bool, - #[arg(long)] - force: bool, - #[arg(long, value_name = "REGEX")] - repo_pattern: Option, + /// Sync only repositories that failed during the previous non-dry-run sync. #[arg(long)] retry_failed: bool, - #[arg(long, value_name = "PATH")] - work_dir: Option, } #[derive(Args, Debug)] struct ServeCommand { #[arg(long, default_value = "127.0.0.1:8787", value_name = "HOST:PORT")] listen: String, - #[arg(long, value_name = "PATH")] - work_dir: Option, } #[derive(Subcommand, Debug)] @@ -96,8 +90,6 @@ struct WebhookUpdateCommand { url: String, #[arg(long)] dry_run: bool, - #[arg(long, value_name = "PATH")] - work_dir: Option, } fn main() -> Result<()> { @@ -127,11 +119,9 @@ fn main() -> Result<()> { group: command.group, dry_run: command.dry_run, create_missing_override: command.no_create.then_some(false), - force_override: command.force.then_some(true), - repo_pattern: command.repo_pattern, retry_failed: command.retry_failed, - work_dir: command.work_dir, jobs: config.jobs, + ..SyncOptions::default() }, ) } @@ -154,7 +144,7 @@ fn main() -> Result<()> { listen: command.listen, secret, workers, - work_dir: command.work_dir, + work_dir: None, full_sync_interval_minutes, reachability_url, reachability_check_interval_minutes, @@ -206,7 +196,7 @@ fn main() -> Result<()> { new_url: command.url.clone(), secret, dry_run: command.dry_run, - work_dir: command.work_dir, + work_dir: None, jobs: config.jobs, }, )?; diff --git a/src/sync.rs b/src/sync.rs index 002accf..c6200c9 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -41,7 +41,6 @@ pub struct SyncOptions { pub group: Option, pub dry_run: bool, pub create_missing_override: Option, - pub force_override: Option, pub repo_pattern: Option, pub retry_failed: bool, pub work_dir: Option, @@ -54,7 +53,6 @@ impl Default for SyncOptions { group: None, dry_run: false, create_missing_override: None, - force_override: None, repo_pattern: None, retry_failed: false, work_dir: None, @@ -97,7 +95,7 @@ pub fn sync_all(config: &Config, options: SyncOptions) -> Result<()> { .as_deref() .map(Regex::new) .transpose() - .with_context(|| "invalid --repo-pattern regex")?; + .with_context(|| "invalid repository filter regex")?; let retry_failed_repos = if options.retry_failed { Some(load_failure_state(&work_dir)?.repos_by_group()) } else { @@ -163,7 +161,6 @@ fn sync_group( .options .create_missing_override .unwrap_or(mirror.create_missing); - let allow_force = context.options.force_override.unwrap_or(mirror.allow_force); let repo_filter = mirror.repo_filter()?; let all_endpoint_repos = list_group_repos(context.config, mirror, &repo_filter)?; @@ -283,7 +280,6 @@ fn sync_group( work_dir, redactor: redactor.clone(), dry_run, - allow_force, }; let result = sync_repo( &repo_context, @@ -511,7 +507,6 @@ struct RepoSyncContext<'a> { work_dir: &'a Path, redactor: Redactor, dry_run: bool, - allow_force: bool, } #[derive(Default)] @@ -890,7 +885,7 @@ fn push_repo_refs( fail_on_unresolved_conflict(context, "branch deletion conflict")?; } - let (branches, conflicts) = mirror_repo.branch_decisions(remotes, context.allow_force)?; + let (branches, conflicts) = mirror_repo.branch_decisions(remotes)?; let branches_to_push = branches .into_iter() .filter(|branch| !is_internal_conflict_branch(&branch.branch)) @@ -992,7 +987,7 @@ fn push_repo_refs( } if !branches_to_push.is_empty() { print_branch_decisions(&branches_to_push); - mirror_repo.push_branches(remotes, &branches_to_push, context.allow_force)?; + mirror_repo.push_branches(remotes, &branches_to_push)?; close_resolved_pull_requests(context, mirror_repo, remotes, repos, &pushed_branch_names)?; } if !rebased_branch_updates.is_empty() { diff --git a/tests/e2e/sequential.rs b/tests/e2e/sequential.rs index 370a39e..b3125dc 100644 --- a/tests/e2e/sequential.rs +++ b/tests/e2e/sequential.rs @@ -41,8 +41,6 @@ fn sequential_live_e2e_all_supported_features() -> Result<()> { run.failed_sync_can_retry_only_failed_repo()?; eprintln!("e2e phase: auto rebase"); run.auto_rebase_resolves_non_conflicting_divergence()?; - eprintln!("e2e phase: force sync"); - run.force_sync_chooses_newest_divergent_commit()?; eprintln!("e2e phase: pull-request conflicts"); run.pull_request_strategy_pushes_conflict_branches()?; eprintln!("e2e phase: auto-rebase PR fallback"); @@ -206,7 +204,7 @@ fn clear_all_repos_enabled(env: &EnvFile) -> Result { struct E2eRun { temp: TempDir, config_path: PathBuf, - work_dir: PathBuf, + cache_home: PathBuf, settings: E2eSettings, redactor: Redactor, run_id: String, @@ -216,7 +214,7 @@ impl E2eRun { fn new(settings: E2eSettings) -> Result { let temp = tempfile::tempdir().context("failed to create e2e temp dir")?; let config_path = temp.path().join("config.toml"); - let work_dir = temp.path().join("work"); + let cache_home = temp.path().join("cache"); let redactor = Redactor::new(settings.secrets()); let run_id = SystemTime::now() .duration_since(UNIX_EPOCH)? @@ -225,7 +223,7 @@ impl E2eRun { Ok(Self { temp, config_path, - work_dir, + cache_home, settings, redactor, run_id, @@ -298,21 +296,21 @@ impl E2eRun { fn write_config( &self, conflict_mode: ConflictMode, - repo_pattern: Option<&str>, + whitelist_pattern: Option<&str>, create_missing: bool, ) -> Result<()> { - self.write_config_for_sites(conflict_mode, repo_pattern, create_missing, None) + self.write_config_for_sites(conflict_mode, whitelist_pattern, create_missing, None) } fn write_config_for_sites( &self, conflict_mode: ConflictMode, - repo_pattern: Option<&str>, + whitelist_pattern: Option<&str>, create_missing: bool, endpoint_sites: Option<&[&str]>, ) -> Result<()> { let default_whitelist = format!("^{REPO_PREFIX}{}-", self.run_id); - let whitelist = repo_pattern.unwrap_or(&default_whitelist); + let whitelist = whitelist_pattern.unwrap_or(&default_whitelist); let mut contents = "jobs = 1\n\n".to_string(); for provider in &self.settings.providers { contents.push_str(&format!( @@ -341,7 +339,6 @@ sync_visibility = "all" repo_whitelist = ['{}'] create_missing = {} visibility = "public" -allow_force = false conflict_resolution = "{}" "#, @@ -390,7 +387,7 @@ namespace = "{}" )?; source.wait_repo_listed(&repo)?; - self.sync(["--repo-pattern", &exact_pattern(&repo)])?; + self.sync_repo(&repo, [])?; self.assert_branch_all_equal_after_optional_resync(&repo, MAIN_BRANCH)?; self.assert_branch_all_equal(&repo, "feature/github")?; self.assert_tag_all_equal(&repo, "v1.0.0")?; @@ -410,7 +407,7 @@ namespace = "{}" &git_output(&work, ["rev-parse", "HEAD"])?, )?; peer.wait_repo_listed(&repo)?; - self.sync(["--repo-pattern", &exact_pattern(&repo)])?; + self.sync_repo(&repo, [])?; self.assert_branch_all_equal_after_optional_resync(&repo, MAIN_BRANCH)?; Ok(()) } @@ -421,10 +418,10 @@ namespace = "{}" source.create_repo(&repo)?; self.seed_main(source, &repo, "dry run", 1_700_000_201)?; - self.sync(["--repo-pattern", &exact_pattern(&repo), "--dry-run"])?; + self.sync_repo(&repo, ["--dry-run"])?; self.assert_only_provider_has_repo(&repo, &source.site_name)?; - self.sync(["--repo-pattern", &exact_pattern(&repo), "--no-create"])?; + self.sync_repo(&repo, ["--no-create"])?; self.assert_only_provider_has_repo(&repo, &source.site_name)?; Ok(()) } @@ -433,13 +430,13 @@ namespace = "{}" let repo = self.repo_name("retry"); let (source, peer) = self.provider_pair(); self.seed_all_main(&repo, "retry base", 1_700_000_301)?; - self.sync(["--repo-pattern", &exact_pattern(&repo)])?; + self.sync_repo(&repo, [])?; self.unprotect_main_all(&repo)?; self.commit_to_provider( source, &repo, - "retry.txt", + "source-retry.txt", "source\n", "source retry", 1_700_000_401, @@ -447,14 +444,15 @@ namespace = "{}" self.commit_to_provider( peer, &repo, - "retry.txt", + "peer-retry.txt", "peer\n", "peer retry", 1_700_000_402, )?; self.write_config(ConflictMode::Fail, Some(&exact_pattern(&repo)), true)?; - self.sync_expect_failure(["--repo-pattern", &exact_pattern(&repo)])?; - self.sync(["--retry-failed", "--force"])?; + self.sync_repo_expect_failure(&repo, [])?; + self.write_config(ConflictMode::AutoRebase, Some(&exact_pattern(&repo)), true)?; + self.sync(["--retry-failed"])?; self.assert_branch_all_equal(&repo, MAIN_BRANCH)?; self.write_config(ConflictMode::AutoRebasePullRequest, None, true)?; Ok(()) @@ -464,7 +462,7 @@ namespace = "{}" let repo = self.repo_name("rebase"); let (source, peer) = self.provider_pair(); self.seed_all_main(&repo, "rebase base", 1_700_000_501)?; - self.sync(["--repo-pattern", &exact_pattern(&repo)])?; + self.sync_repo(&repo, [])?; self.unprotect_main_all(&repo)?; self.commit_to_provider( @@ -493,42 +491,11 @@ namespace = "{}" Ok(()) } - fn force_sync_chooses_newest_divergent_commit(&self) -> Result<()> { - let repo = self.repo_name("force"); - let (source, peer) = self.provider_pair(); - self.seed_all_main(&repo, "force base", 1_700_000_701)?; - self.sync(["--repo-pattern", &exact_pattern(&repo)])?; - self.unprotect_main_all(&repo)?; - - self.commit_to_provider( - source, - &repo, - "force.txt", - "old\n", - "old force", - 1_700_000_801, - )?; - self.commit_to_provider( - peer, - &repo, - "force.txt", - "new\n", - "new force", - 1_700_000_901, - )?; - self.sync(["--repo-pattern", &exact_pattern(&repo), "--force"])?; - self.assert_branch_all_equal(&repo, MAIN_BRANCH)?; - let clone = self.clone_repo(source, &repo, "force-verify")?; - let contents = fs::read_to_string(clone.join("force.txt"))?; - assert_eq!(contents, "new\n"); - Ok(()) - } - fn pull_request_strategy_pushes_conflict_branches(&self) -> Result<()> { let repo = self.repo_name("pull-request"); let (source, peer) = self.provider_pair(); self.seed_all_main(&repo, "pr base", 1_700_001_001)?; - self.sync(["--repo-pattern", &exact_pattern(&repo)])?; + self.sync_repo(&repo, [])?; self.unprotect_main_all(&repo)?; self.commit_to_provider( @@ -564,7 +531,7 @@ namespace = "{}" let repo = self.repo_name("fallback"); let (source, peer) = self.provider_pair(); self.seed_all_main(&repo, "fallback base", 1_700_001_201)?; - self.sync(["--repo-pattern", &exact_pattern(&repo)])?; + self.sync_repo(&repo, [])?; self.unprotect_main_all(&repo)?; self.commit_to_provider( @@ -615,13 +582,13 @@ namespace = "{}" "delete-me", &git_output(&work, ["rev-parse", "HEAD"])?, )?; - self.sync(["--repo-pattern", &exact_pattern(&repo)])?; + self.sync_repo(&repo, [])?; source.wait_repo_listed(&repo)?; self.assert_branch_all_equal(&repo, "delete-me")?; self.git(&work, ["push", "origin", ":refs/heads/delete-me"])?; source.wait_branch_absent(&repo, "delete-me")?; - self.sync(["--repo-pattern", &exact_pattern(&repo)])?; + self.sync_repo(&repo, [])?; self.assert_branch_absent_everywhere(&repo, "delete-me")?; Ok(()) } @@ -630,12 +597,12 @@ namespace = "{}" let repo = self.repo_name("repo-delete"); let source = self.primary_provider(); self.seed_all_main(&repo, "repo delete base", 1_700_001_501)?; - self.sync(["--repo-pattern", &exact_pattern(&repo)])?; + self.sync_repo(&repo, [])?; self.assert_repo_exists_everywhere(&repo)?; source.delete_repo(&repo)?; source.wait_repo_absent(&repo)?; - self.sync(["--repo-pattern", &exact_pattern(&repo)])?; + self.sync_repo(&repo, [])?; self.assert_repo_absent_everywhere(&repo)?; Ok(()) } @@ -644,7 +611,7 @@ namespace = "{}" let repo = self.repo_name("webhook"); let source = self.primary_provider(); self.seed_all_main(&repo, "webhook base", 1_700_001_601)?; - self.sync(["--repo-pattern", &exact_pattern(&repo)])?; + self.sync_repo(&repo, [])?; self.refray(["webhook", "install", "--dry-run"])?; self.refray(["webhook", "uninstall", "--dry-run"])?; @@ -785,14 +752,47 @@ namespace = "{}" assert_output_success(output, "git", &self.redactor) } + fn set_repo_whitelist(&self, pattern: &str) -> Result<()> { + let contents = fs::read_to_string(&self.config_path) + .with_context(|| format!("failed to read {}", self.config_path.display()))?; + let escaped_pattern = pattern.replace('\'', "''"); + let replacement = format!("repo_whitelist = ['{escaped_pattern}']"); + let mut replaced = false; + let mut updated = contents + .lines() + .map(|line| { + if line.starts_with("repo_whitelist = ") { + replaced = true; + replacement.clone() + } else { + line.to_string() + } + }) + .collect::>() + .join("\n"); + if contents.ends_with('\n') { + updated.push('\n'); + } + if !replaced { + bail!("config is missing repo_whitelist"); + } + fs::write(&self.config_path, updated) + .with_context(|| format!("failed to write {}", self.config_path.display())) + } + fn sync(&self, args: [&str; N]) -> Result<()> { - let mut command = vec!["sync", "--work-dir", self.work_dir.to_str().unwrap()]; + let mut command = vec!["sync"]; command.extend(args); self.refray(command) } + fn sync_repo(&self, repo: &str, args: [&str; N]) -> Result<()> { + self.set_repo_whitelist(&exact_pattern(repo))?; + self.sync(args) + } + fn sync_expect_failure(&self, args: [&str; N]) -> Result<()> { - let mut command = vec!["sync", "--work-dir", self.work_dir.to_str().unwrap()]; + let mut command = vec!["sync"]; command.extend(args); let output = self.refray_output(command)?; if output.status.success() { @@ -801,6 +801,11 @@ namespace = "{}" Ok(()) } + fn sync_repo_expect_failure(&self, repo: &str, args: [&str; N]) -> Result<()> { + self.set_repo_whitelist(&exact_pattern(repo))?; + self.sync_expect_failure(args) + } + fn refray(&self, args: I) -> Result<()> where I: IntoIterator, @@ -834,6 +839,7 @@ namespace = "{}" } command .env("GIT_TERMINAL_PROMPT", "0") + .env("XDG_CACHE_HOME", &self.cache_home) .output() .context("failed to run refray") } @@ -844,6 +850,7 @@ namespace = "{}" command.args(args); command .env("GIT_TERMINAL_PROMPT", "0") + .env("XDG_CACHE_HOME", &self.cache_home) .spawn() .context("failed to spawn refray") } @@ -938,7 +945,7 @@ namespace = "{}" Ok(()) => Ok(()), Err(first_error) => { thread::sleep(Duration::from_secs(2)); - self.sync(["--repo-pattern", &exact_pattern(repo)]) + self.sync_repo(repo, []) .with_context(|| format!("initial convergence failed: {first_error:#}"))?; self.assert_branch_all_equal(repo, branch) } diff --git a/tests/unit/cli.rs b/tests/unit/cli.rs index dec718e..61eee83 100644 --- a/tests/unit/cli.rs +++ b/tests/unit/cli.rs @@ -21,21 +21,14 @@ fn cli_rejects_removed_config_subcommands() { } #[test] -fn cli_accepts_sync_repo_pattern() { - let cli = Cli::try_parse_from([ - "refray", - "sync", - "--repo-pattern", - "^(foo|bar)-", - "--dry-run", - ]) - .unwrap(); - - let Command::Sync(args) = cli.command else { - panic!("parsed unexpected command"); - }; - assert_eq!(args.repo_pattern, Some("^(foo|bar)-".to_string())); - assert!(args.dry_run); +fn cli_rejects_removed_sync_args() { + for args in [ + ["refray", "sync", "--repo-pattern", "^(foo|bar)-"].as_slice(), + ["refray", "sync", "--work-dir", "/tmp/refray"].as_slice(), + ["refray", "sync", "--force"].as_slice(), + ] { + assert!(Cli::try_parse_from(args).is_err()); + } } #[test] @@ -85,6 +78,7 @@ fn cli_rejects_removed_serve_args() { ["refray", "serve", "--secret", "secret"].as_slice(), ["refray", "serve", "--secret-env", "WEBHOOK_SECRET"].as_slice(), ["refray", "serve", "--full-sync-interval-minutes", "30"].as_slice(), + ["refray", "serve", "--work-dir", "/tmp/refray"].as_slice(), ] { assert!(Cli::try_parse_from(args).is_err()); } @@ -262,6 +256,15 @@ fn cli_rejects_removed_webhook_update_secret_args() { "WEBHOOK_SECRET", ] .as_slice(), + [ + "refray", + "webhook", + "update", + "https://new.example.test/webhook", + "--work-dir", + "/tmp/refray", + ] + .as_slice(), ] { assert!(Cli::try_parse_from(args).is_err()); } diff --git a/tests/unit/config.rs b/tests/unit/config.rs index 21fa269..24f6171 100644 --- a/tests/unit/config.rs +++ b/tests/unit/config.rs @@ -26,7 +26,6 @@ fn parses_token_forms() { repo_blacklist = ["-archive$"] create_missing = true visibility = "private" - allow_force = false conflict_resolution = "auto_rebase_pull_request" [[mirrors.endpoints]] @@ -92,7 +91,6 @@ fn validation_rejects_unknown_sites_and_single_endpoint_groups() { repo_blacklist: Vec::new(), create_missing: true, visibility: Visibility::Private, - allow_force: false, conflict_resolution: ConflictResolutionStrategy::Fail, }], webhook: None, @@ -122,7 +120,6 @@ fn validation_rejects_unknown_sites_and_single_endpoint_groups() { repo_blacklist: Vec::new(), create_missing: true, visibility: Visibility::Private, - allow_force: false, conflict_resolution: ConflictResolutionStrategy::Fail, }], webhook: None, @@ -245,7 +242,6 @@ fn mirror_config() -> MirrorConfig { repo_blacklist: Vec::new(), create_missing: true, visibility: Visibility::Private, - allow_force: false, conflict_resolution: ConflictResolutionStrategy::Fail, } } diff --git a/tests/unit/git.rs b/tests/unit/git.rs index 3491002..f0bd3fe 100644 --- a/tests/unit/git.rs +++ b/tests/unit/git.rs @@ -75,7 +75,7 @@ fn branch_decisions_choose_fast_forward_tip() { let mirror = fixture.mirror(); fixture.fetch_all(&mirror); - let (decisions, conflicts) = mirror.branch_decisions(&fixture.remotes(), false).unwrap(); + let (decisions, conflicts) = mirror.branch_decisions(&fixture.remotes()).unwrap(); assert!(conflicts.is_empty()); let main = find_branch(&decisions, "main"); @@ -94,7 +94,7 @@ fn branch_decisions_do_not_target_remotes_that_already_match() { let mirror = fixture.mirror(); fixture.fetch_all(&mirror); - let (decisions, conflicts) = mirror.branch_decisions(&fixture.remotes(), false).unwrap(); + let (decisions, conflicts) = mirror.branch_decisions(&fixture.remotes()).unwrap(); assert!(conflicts.is_empty()); let main = find_branch(&decisions, "main"); @@ -132,7 +132,7 @@ fn cached_remote_refs_match_ls_remote_snapshot_after_fetch() { } #[test] -fn branch_decisions_report_divergent_tips_without_force() { +fn branch_decisions_report_divergent_tips_as_conflicts() { let fixture = GitFixture::new(); let base = fixture.commit("base", "base", 1_700_000_000); fixture.push_head(&fixture.remote_a, "main"); @@ -146,7 +146,7 @@ fn branch_decisions_report_divergent_tips_without_force() { let mirror = fixture.mirror(); fixture.fetch_all(&mirror); - let (decisions, conflicts) = mirror.branch_decisions(&fixture.remotes(), false).unwrap(); + let (decisions, conflicts) = mirror.branch_decisions(&fixture.remotes()).unwrap(); assert!(decisions.is_empty()); assert_eq!(conflicts.len(), 1); @@ -155,31 +155,6 @@ fn branch_decisions_report_divergent_tips_without_force() { assert!(conflicts[0].tips.iter().any(|(_, sha)| sha == &b_tip)); } -#[test] -fn branch_decisions_force_selects_newest_divergent_tip() { - let fixture = GitFixture::new(); - let base = fixture.commit("base", "base", 1_700_000_000); - fixture.push_head(&fixture.remote_a, "main"); - fixture.push_head(&fixture.remote_b, "main"); - - let older = fixture.commit("older", "older", 1_700_000_100); - fixture.push_head(&fixture.remote_a, "main"); - fixture.reset_hard(&base); - let newer = fixture.commit("newer", "newer", 1_700_000_200); - fixture.push_head(&fixture.remote_b, "main"); - - let mirror = fixture.mirror(); - fixture.fetch_all(&mirror); - let (decisions, conflicts) = mirror.branch_decisions(&fixture.remotes(), true).unwrap(); - - assert!(conflicts.is_empty()); - let main = find_branch(&decisions, "main"); - assert_eq!(main.sha, newer); - assert_ne!(main.sha, older); - assert_eq!(main.source_remotes, vec!["b".to_string()]); - assert_eq!(main.target_remotes, vec!["a".to_string()]); -} - #[test] fn auto_rebase_branch_conflict_replays_later_tip_and_marks_force_targets() { let fixture = GitFixture::new(); @@ -195,7 +170,7 @@ fn auto_rebase_branch_conflict_replays_later_tip_and_marks_force_targets() { let mirror = fixture.mirror(); fixture.fetch_all(&mirror); - let (_, conflicts) = mirror.branch_decisions(&fixture.remotes(), false).unwrap(); + let (_, conflicts) = mirror.branch_decisions(&fixture.remotes()).unwrap(); let decision = mirror .auto_rebase_branch_conflict(&fixture.remotes(), "main", &conflicts[0].tips) @@ -247,7 +222,7 @@ fn auto_rebase_branch_conflict_fails_on_file_conflict() { let mirror = fixture.mirror(); fixture.fetch_all(&mirror); - let (_, conflicts) = mirror.branch_decisions(&fixture.remotes(), false).unwrap(); + let (_, conflicts) = mirror.branch_decisions(&fixture.remotes()).unwrap(); let error = mirror .auto_rebase_branch_conflict(&fixture.remotes(), "main", &conflicts[0].tips) @@ -265,10 +240,10 @@ fn push_branches_creates_missing_branch_on_other_remotes() { let mirror = fixture.mirror(); fixture.fetch_all(&mirror); - let (decisions, conflicts) = mirror.branch_decisions(&fixture.remotes(), false).unwrap(); + let (decisions, conflicts) = mirror.branch_decisions(&fixture.remotes()).unwrap(); assert!(conflicts.is_empty()); mirror - .push_branches(&fixture.remotes(), &decisions, false) + .push_branches(&fixture.remotes(), &decisions) .unwrap(); assert_eq!( diff --git a/tests/unit/interactive.rs b/tests/unit/interactive.rs index 9c9fdfb..1c96171 100644 --- a/tests/unit/interactive.rs +++ b/tests/unit/interactive.rs @@ -47,7 +47,6 @@ fn wizard_builds_sync_group_from_profile_urls() { assert_eq!(config.mirrors[0].sync_visibility, SyncVisibility::All); assert!(config.mirrors[0].create_missing); assert_eq!(config.mirrors[0].visibility, Visibility::Private); - assert!(!config.mirrors[0].allow_force); assert_eq!( config.mirrors[0].conflict_resolution, ConflictResolutionStrategy::AutoRebasePullRequest @@ -216,7 +215,6 @@ fn wizard_starts_existing_config_at_sync_group_menu() { repo_blacklist: Vec::new(), create_missing: true, visibility: Visibility::Private, - allow_force: false, conflict_resolution: ConflictResolutionStrategy::Fail, }], webhook: None, @@ -246,7 +244,6 @@ fn wizard_can_ask_to_run_full_sync_after_config() { repo_blacklist: Vec::new(), create_missing: true, visibility: Visibility::Private, - allow_force: false, conflict_resolution: ConflictResolutionStrategy::Fail, }], webhook: None, @@ -323,7 +320,6 @@ fn wizard_edits_existing_sync_group_from_menu() { repo_blacklist: vec!["-archive$".to_string()], create_missing: false, visibility: Visibility::Public, - allow_force: true, conflict_resolution: ConflictResolutionStrategy::Fail, }], webhook: None, @@ -364,8 +360,6 @@ fn wizard_edits_existing_sync_group_from_menu() { assert_eq!(mirror.repo_whitelist, vec!["^public-".to_string()]); assert_eq!(mirror.repo_blacklist, vec!["-skip$".to_string()]); assert_eq!(mirror.visibility, Visibility::Public); - assert!(mirror.allow_force); - let output = String::from_utf8(output).unwrap(); assert!(output.contains("Edit sync group")); assert!(output.contains("updated sync group 1")); @@ -413,7 +407,6 @@ fn wizard_prefills_existing_sync_group_when_editing() { repo_blacklist: Vec::new(), create_missing: true, visibility: Visibility::Private, - allow_force: false, conflict_resolution: ConflictResolutionStrategy::Fail, }], webhook: None, @@ -478,7 +471,6 @@ fn wizard_deletes_existing_sync_group_from_menu() { repo_blacklist: Vec::new(), create_missing: true, visibility: Visibility::Private, - allow_force: false, conflict_resolution: ConflictResolutionStrategy::Fail, }], webhook: None, @@ -538,7 +530,6 @@ fn wizard_can_go_back_from_delete_menu() { repo_blacklist: Vec::new(), create_missing: true, visibility: Visibility::Private, - allow_force: false, conflict_resolution: ConflictResolutionStrategy::Fail, }], webhook: None, diff --git a/tests/unit/interactive_test_io.rs b/tests/unit/interactive_test_io.rs index 4cbcf07..50df58b 100644 --- a/tests/unit/interactive_test_io.rs +++ b/tests/unit/interactive_test_io.rs @@ -74,7 +74,6 @@ where repo_blacklist: repo_filters.blacklist, create_missing: true, visibility: Visibility::Private, - allow_force: false, conflict_resolution, }); prompt_webhook_setup(reader, writer, config)?; diff --git a/tests/unit/sync.rs b/tests/unit/sync.rs index aba8f79..e673ed1 100644 --- a/tests/unit/sync.rs +++ b/tests/unit/sync.rs @@ -413,7 +413,6 @@ fn test_mirror() -> MirrorConfig { repo_blacklist: Vec::new(), create_missing: true, visibility: crate::config::Visibility::Private, - allow_force: false, conflict_resolution: ConflictResolutionStrategy::Fail, } } diff --git a/tests/unit/webhook.rs b/tests/unit/webhook.rs index 777bc2d..fe60a8f 100644 --- a/tests/unit/webhook.rs +++ b/tests/unit/webhook.rs @@ -116,7 +116,6 @@ fn matches_jobs_by_provider_and_namespace() { repo_blacklist: Vec::new(), create_missing: true, visibility: Visibility::Private, - allow_force: false, conflict_resolution: ConflictResolutionStrategy::Fail, }], webhook: None, @@ -144,7 +143,6 @@ fn matching_jobs_respects_repo_name_filters() { repo_blacklist: vec!["-archive$".to_string()], create_missing: true, visibility: Visibility::Private, - allow_force: false, conflict_resolution: ConflictResolutionStrategy::Fail, }; let config = Config {