[-] Remove legacy features
This commit is contained in:
@@ -73,7 +73,6 @@ repo_whitelist = ["^important-"]
|
|||||||
repo_blacklist = ["-archive$"]
|
repo_blacklist = ["-archive$"]
|
||||||
create_missing = true
|
create_missing = true
|
||||||
visibility = "private"
|
visibility = "private"
|
||||||
allow_force = false
|
|
||||||
conflict_resolution = "auto_rebase_pull_request"
|
conflict_resolution = "auto_rebase_pull_request"
|
||||||
|
|
||||||
[[mirrors.endpoints]]
|
[[mirrors.endpoints]]
|
||||||
@@ -111,13 +110,13 @@ Preview commands without writing to Git remotes:
|
|||||||
refray sync --dry-run
|
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
|
```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:
|
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 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 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 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:
|
Conflict resolution strategies are configured per mirror group:
|
||||||
|
|
||||||
|
|||||||
@@ -66,8 +66,6 @@ pub struct MirrorConfig {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub visibility: Visibility,
|
pub visibility: Visibility,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub allow_force: bool,
|
|
||||||
#[serde(default)]
|
|
||||||
pub conflict_resolution: ConflictResolutionStrategy,
|
pub conflict_resolution: ConflictResolutionStrategy,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-43
@@ -166,7 +166,6 @@ impl GitMirror {
|
|||||||
pub fn branch_decisions(
|
pub fn branch_decisions(
|
||||||
&self,
|
&self,
|
||||||
remotes: &[RemoteSpec],
|
remotes: &[RemoteSpec],
|
||||||
allow_force: bool,
|
|
||||||
) -> Result<(Vec<BranchDecision>, Vec<BranchConflict>)> {
|
) -> Result<(Vec<BranchDecision>, Vec<BranchConflict>)> {
|
||||||
let mut by_branch: BTreeMap<String, Vec<(String, String)>> = BTreeMap::new();
|
let mut by_branch: BTreeMap<String, Vec<(String, String)>> = BTreeMap::new();
|
||||||
for remote in remotes {
|
for remote in remotes {
|
||||||
@@ -218,20 +217,6 @@ impl GitMirror {
|
|||||||
source_remotes,
|
source_remotes,
|
||||||
target_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::<Vec<_>>();
|
|
||||||
let target_remotes = missing_remotes(&all_remote_names, &source_remotes);
|
|
||||||
decisions.push(BranchDecision {
|
|
||||||
branch,
|
|
||||||
sha: winner,
|
|
||||||
source_remotes,
|
|
||||||
target_remotes,
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
conflicts.push(BranchConflict { branch, tips });
|
conflicts.push(BranchConflict { branch, tips });
|
||||||
}
|
}
|
||||||
@@ -286,22 +271,13 @@ impl GitMirror {
|
|||||||
Ok((decisions, conflicts))
|
Ok((decisions, conflicts))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push_branches(
|
pub fn push_branches(&self, remotes: &[RemoteSpec], branches: &[BranchDecision]) -> Result<()> {
|
||||||
&self,
|
|
||||||
remotes: &[RemoteSpec],
|
|
||||||
branches: &[BranchDecision],
|
|
||||||
force: bool,
|
|
||||||
) -> Result<()> {
|
|
||||||
for remote in remotes {
|
for remote in remotes {
|
||||||
for branch in branches {
|
for branch in branches {
|
||||||
if !branch.target_remotes.contains(&remote.name) {
|
if !branch.target_remotes.contains(&remote.name) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let refspec = if force {
|
let refspec = format!("{}:refs/heads/{}", branch.sha, branch.branch);
|
||||||
format!("+{}:refs/heads/{}", branch.sha, branch.branch)
|
|
||||||
} else {
|
|
||||||
format!("{}:refs/heads/{}", branch.sha, branch.branch)
|
|
||||||
};
|
|
||||||
crate::logln!(
|
crate::logln!(
|
||||||
" {} {} {} {}",
|
" {} {} {} {}",
|
||||||
style("push").green().bold(),
|
style("push").green().bold(),
|
||||||
@@ -533,23 +509,6 @@ impl GitMirror {
|
|||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn newest_commit<'a>(&self, shas: impl Iterator<Item = &'a String>) -> Result<String> {
|
|
||||||
let mut newest: Option<(i64, String)> = None;
|
|
||||||
for sha in shas {
|
|
||||||
let timestamp = self
|
|
||||||
.output(["show", "-s", "--format=%ct", sha])?
|
|
||||||
.trim()
|
|
||||||
.parse::<i64>()?;
|
|
||||||
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<String> {
|
fn merge_base(&self, left: &str, right: &str) -> Result<String> {
|
||||||
Ok(self.output(["merge-base", left, right])?.trim().to_string())
|
Ok(self.output(["merge-base", left, right])?.trim().to_string())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,7 +126,6 @@ fn add_sync_group_styled(config: &mut Config, theme: &ColorfulTheme) -> Result<(
|
|||||||
repo_blacklist: repo_filters.blacklist,
|
repo_blacklist: repo_filters.blacklist,
|
||||||
create_missing: true,
|
create_missing: true,
|
||||||
visibility: Visibility::Private,
|
visibility: Visibility::Private,
|
||||||
allow_force: false,
|
|
||||||
conflict_resolution,
|
conflict_resolution,
|
||||||
});
|
});
|
||||||
prompt_webhook_setup_styled(config, theme)?;
|
prompt_webhook_setup_styled(config, theme)?;
|
||||||
|
|||||||
+5
-15
@@ -49,24 +49,18 @@ struct SyncCommand {
|
|||||||
group: Option<String>,
|
group: Option<String>,
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
dry_run: bool,
|
dry_run: bool,
|
||||||
|
/// Do not create repositories that are missing from an endpoint.
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
no_create: bool,
|
no_create: bool,
|
||||||
#[arg(long)]
|
/// Sync only repositories that failed during the previous non-dry-run sync.
|
||||||
force: bool,
|
|
||||||
#[arg(long, value_name = "REGEX")]
|
|
||||||
repo_pattern: Option<String>,
|
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
retry_failed: bool,
|
retry_failed: bool,
|
||||||
#[arg(long, value_name = "PATH")]
|
|
||||||
work_dir: Option<PathBuf>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args, Debug)]
|
#[derive(Args, Debug)]
|
||||||
struct ServeCommand {
|
struct ServeCommand {
|
||||||
#[arg(long, default_value = "127.0.0.1:8787", value_name = "HOST:PORT")]
|
#[arg(long, default_value = "127.0.0.1:8787", value_name = "HOST:PORT")]
|
||||||
listen: String,
|
listen: String,
|
||||||
#[arg(long, value_name = "PATH")]
|
|
||||||
work_dir: Option<PathBuf>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Subcommand, Debug)]
|
#[derive(Subcommand, Debug)]
|
||||||
@@ -96,8 +90,6 @@ struct WebhookUpdateCommand {
|
|||||||
url: String,
|
url: String,
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
dry_run: bool,
|
dry_run: bool,
|
||||||
#[arg(long, value_name = "PATH")]
|
|
||||||
work_dir: Option<PathBuf>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
@@ -127,11 +119,9 @@ fn main() -> Result<()> {
|
|||||||
group: command.group,
|
group: command.group,
|
||||||
dry_run: command.dry_run,
|
dry_run: command.dry_run,
|
||||||
create_missing_override: command.no_create.then_some(false),
|
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,
|
retry_failed: command.retry_failed,
|
||||||
work_dir: command.work_dir,
|
|
||||||
jobs: config.jobs,
|
jobs: config.jobs,
|
||||||
|
..SyncOptions::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -154,7 +144,7 @@ fn main() -> Result<()> {
|
|||||||
listen: command.listen,
|
listen: command.listen,
|
||||||
secret,
|
secret,
|
||||||
workers,
|
workers,
|
||||||
work_dir: command.work_dir,
|
work_dir: None,
|
||||||
full_sync_interval_minutes,
|
full_sync_interval_minutes,
|
||||||
reachability_url,
|
reachability_url,
|
||||||
reachability_check_interval_minutes,
|
reachability_check_interval_minutes,
|
||||||
@@ -206,7 +196,7 @@ fn main() -> Result<()> {
|
|||||||
new_url: command.url.clone(),
|
new_url: command.url.clone(),
|
||||||
secret,
|
secret,
|
||||||
dry_run: command.dry_run,
|
dry_run: command.dry_run,
|
||||||
work_dir: command.work_dir,
|
work_dir: None,
|
||||||
jobs: config.jobs,
|
jobs: config.jobs,
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|||||||
+3
-8
@@ -41,7 +41,6 @@ pub struct SyncOptions {
|
|||||||
pub group: Option<String>,
|
pub group: Option<String>,
|
||||||
pub dry_run: bool,
|
pub dry_run: bool,
|
||||||
pub create_missing_override: Option<bool>,
|
pub create_missing_override: Option<bool>,
|
||||||
pub force_override: Option<bool>,
|
|
||||||
pub repo_pattern: Option<String>,
|
pub repo_pattern: Option<String>,
|
||||||
pub retry_failed: bool,
|
pub retry_failed: bool,
|
||||||
pub work_dir: Option<PathBuf>,
|
pub work_dir: Option<PathBuf>,
|
||||||
@@ -54,7 +53,6 @@ impl Default for SyncOptions {
|
|||||||
group: None,
|
group: None,
|
||||||
dry_run: false,
|
dry_run: false,
|
||||||
create_missing_override: None,
|
create_missing_override: None,
|
||||||
force_override: None,
|
|
||||||
repo_pattern: None,
|
repo_pattern: None,
|
||||||
retry_failed: false,
|
retry_failed: false,
|
||||||
work_dir: None,
|
work_dir: None,
|
||||||
@@ -97,7 +95,7 @@ pub fn sync_all(config: &Config, options: SyncOptions) -> Result<()> {
|
|||||||
.as_deref()
|
.as_deref()
|
||||||
.map(Regex::new)
|
.map(Regex::new)
|
||||||
.transpose()
|
.transpose()
|
||||||
.with_context(|| "invalid --repo-pattern regex")?;
|
.with_context(|| "invalid repository filter regex")?;
|
||||||
let retry_failed_repos = if options.retry_failed {
|
let retry_failed_repos = if options.retry_failed {
|
||||||
Some(load_failure_state(&work_dir)?.repos_by_group())
|
Some(load_failure_state(&work_dir)?.repos_by_group())
|
||||||
} else {
|
} else {
|
||||||
@@ -163,7 +161,6 @@ fn sync_group(
|
|||||||
.options
|
.options
|
||||||
.create_missing_override
|
.create_missing_override
|
||||||
.unwrap_or(mirror.create_missing);
|
.unwrap_or(mirror.create_missing);
|
||||||
let allow_force = context.options.force_override.unwrap_or(mirror.allow_force);
|
|
||||||
let repo_filter = mirror.repo_filter()?;
|
let repo_filter = mirror.repo_filter()?;
|
||||||
|
|
||||||
let all_endpoint_repos = list_group_repos(context.config, mirror, &repo_filter)?;
|
let all_endpoint_repos = list_group_repos(context.config, mirror, &repo_filter)?;
|
||||||
@@ -283,7 +280,6 @@ fn sync_group(
|
|||||||
work_dir,
|
work_dir,
|
||||||
redactor: redactor.clone(),
|
redactor: redactor.clone(),
|
||||||
dry_run,
|
dry_run,
|
||||||
allow_force,
|
|
||||||
};
|
};
|
||||||
let result = sync_repo(
|
let result = sync_repo(
|
||||||
&repo_context,
|
&repo_context,
|
||||||
@@ -511,7 +507,6 @@ struct RepoSyncContext<'a> {
|
|||||||
work_dir: &'a Path,
|
work_dir: &'a Path,
|
||||||
redactor: Redactor,
|
redactor: Redactor,
|
||||||
dry_run: bool,
|
dry_run: bool,
|
||||||
allow_force: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@@ -890,7 +885,7 @@ fn push_repo_refs(
|
|||||||
fail_on_unresolved_conflict(context, "branch deletion conflict")?;
|
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
|
let branches_to_push = branches
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|branch| !is_internal_conflict_branch(&branch.branch))
|
.filter(|branch| !is_internal_conflict_branch(&branch.branch))
|
||||||
@@ -992,7 +987,7 @@ fn push_repo_refs(
|
|||||||
}
|
}
|
||||||
if !branches_to_push.is_empty() {
|
if !branches_to_push.is_empty() {
|
||||||
print_branch_decisions(&branches_to_push);
|
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)?;
|
close_resolved_pull_requests(context, mirror_repo, remotes, repos, &pushed_branch_names)?;
|
||||||
}
|
}
|
||||||
if !rebased_branch_updates.is_empty() {
|
if !rebased_branch_updates.is_empty() {
|
||||||
|
|||||||
+68
-61
@@ -41,8 +41,6 @@ fn sequential_live_e2e_all_supported_features() -> Result<()> {
|
|||||||
run.failed_sync_can_retry_only_failed_repo()?;
|
run.failed_sync_can_retry_only_failed_repo()?;
|
||||||
eprintln!("e2e phase: auto rebase");
|
eprintln!("e2e phase: auto rebase");
|
||||||
run.auto_rebase_resolves_non_conflicting_divergence()?;
|
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");
|
eprintln!("e2e phase: pull-request conflicts");
|
||||||
run.pull_request_strategy_pushes_conflict_branches()?;
|
run.pull_request_strategy_pushes_conflict_branches()?;
|
||||||
eprintln!("e2e phase: auto-rebase PR fallback");
|
eprintln!("e2e phase: auto-rebase PR fallback");
|
||||||
@@ -206,7 +204,7 @@ fn clear_all_repos_enabled(env: &EnvFile) -> Result<bool> {
|
|||||||
struct E2eRun {
|
struct E2eRun {
|
||||||
temp: TempDir,
|
temp: TempDir,
|
||||||
config_path: PathBuf,
|
config_path: PathBuf,
|
||||||
work_dir: PathBuf,
|
cache_home: PathBuf,
|
||||||
settings: E2eSettings,
|
settings: E2eSettings,
|
||||||
redactor: Redactor,
|
redactor: Redactor,
|
||||||
run_id: String,
|
run_id: String,
|
||||||
@@ -216,7 +214,7 @@ impl E2eRun {
|
|||||||
fn new(settings: E2eSettings) -> Result<Self> {
|
fn new(settings: E2eSettings) -> Result<Self> {
|
||||||
let temp = tempfile::tempdir().context("failed to create e2e temp dir")?;
|
let temp = tempfile::tempdir().context("failed to create e2e temp dir")?;
|
||||||
let config_path = temp.path().join("config.toml");
|
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 redactor = Redactor::new(settings.secrets());
|
||||||
let run_id = SystemTime::now()
|
let run_id = SystemTime::now()
|
||||||
.duration_since(UNIX_EPOCH)?
|
.duration_since(UNIX_EPOCH)?
|
||||||
@@ -225,7 +223,7 @@ impl E2eRun {
|
|||||||
Ok(Self {
|
Ok(Self {
|
||||||
temp,
|
temp,
|
||||||
config_path,
|
config_path,
|
||||||
work_dir,
|
cache_home,
|
||||||
settings,
|
settings,
|
||||||
redactor,
|
redactor,
|
||||||
run_id,
|
run_id,
|
||||||
@@ -298,21 +296,21 @@ impl E2eRun {
|
|||||||
fn write_config(
|
fn write_config(
|
||||||
&self,
|
&self,
|
||||||
conflict_mode: ConflictMode,
|
conflict_mode: ConflictMode,
|
||||||
repo_pattern: Option<&str>,
|
whitelist_pattern: Option<&str>,
|
||||||
create_missing: bool,
|
create_missing: bool,
|
||||||
) -> Result<()> {
|
) -> 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(
|
fn write_config_for_sites(
|
||||||
&self,
|
&self,
|
||||||
conflict_mode: ConflictMode,
|
conflict_mode: ConflictMode,
|
||||||
repo_pattern: Option<&str>,
|
whitelist_pattern: Option<&str>,
|
||||||
create_missing: bool,
|
create_missing: bool,
|
||||||
endpoint_sites: Option<&[&str]>,
|
endpoint_sites: Option<&[&str]>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let default_whitelist = format!("^{REPO_PREFIX}{}-", self.run_id);
|
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();
|
let mut contents = "jobs = 1\n\n".to_string();
|
||||||
for provider in &self.settings.providers {
|
for provider in &self.settings.providers {
|
||||||
contents.push_str(&format!(
|
contents.push_str(&format!(
|
||||||
@@ -341,7 +339,6 @@ sync_visibility = "all"
|
|||||||
repo_whitelist = ['{}']
|
repo_whitelist = ['{}']
|
||||||
create_missing = {}
|
create_missing = {}
|
||||||
visibility = "public"
|
visibility = "public"
|
||||||
allow_force = false
|
|
||||||
conflict_resolution = "{}"
|
conflict_resolution = "{}"
|
||||||
|
|
||||||
"#,
|
"#,
|
||||||
@@ -390,7 +387,7 @@ namespace = "{}"
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
source.wait_repo_listed(&repo)?;
|
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_after_optional_resync(&repo, MAIN_BRANCH)?;
|
||||||
self.assert_branch_all_equal(&repo, "feature/github")?;
|
self.assert_branch_all_equal(&repo, "feature/github")?;
|
||||||
self.assert_tag_all_equal(&repo, "v1.0.0")?;
|
self.assert_tag_all_equal(&repo, "v1.0.0")?;
|
||||||
@@ -410,7 +407,7 @@ namespace = "{}"
|
|||||||
&git_output(&work, ["rev-parse", "HEAD"])?,
|
&git_output(&work, ["rev-parse", "HEAD"])?,
|
||||||
)?;
|
)?;
|
||||||
peer.wait_repo_listed(&repo)?;
|
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)?;
|
self.assert_branch_all_equal_after_optional_resync(&repo, MAIN_BRANCH)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -421,10 +418,10 @@ namespace = "{}"
|
|||||||
source.create_repo(&repo)?;
|
source.create_repo(&repo)?;
|
||||||
self.seed_main(source, &repo, "dry run", 1_700_000_201)?;
|
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.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)?;
|
self.assert_only_provider_has_repo(&repo, &source.site_name)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -433,13 +430,13 @@ namespace = "{}"
|
|||||||
let repo = self.repo_name("retry");
|
let repo = self.repo_name("retry");
|
||||||
let (source, peer) = self.provider_pair();
|
let (source, peer) = self.provider_pair();
|
||||||
self.seed_all_main(&repo, "retry base", 1_700_000_301)?;
|
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.unprotect_main_all(&repo)?;
|
||||||
|
|
||||||
self.commit_to_provider(
|
self.commit_to_provider(
|
||||||
source,
|
source,
|
||||||
&repo,
|
&repo,
|
||||||
"retry.txt",
|
"source-retry.txt",
|
||||||
"source\n",
|
"source\n",
|
||||||
"source retry",
|
"source retry",
|
||||||
1_700_000_401,
|
1_700_000_401,
|
||||||
@@ -447,14 +444,15 @@ namespace = "{}"
|
|||||||
self.commit_to_provider(
|
self.commit_to_provider(
|
||||||
peer,
|
peer,
|
||||||
&repo,
|
&repo,
|
||||||
"retry.txt",
|
"peer-retry.txt",
|
||||||
"peer\n",
|
"peer\n",
|
||||||
"peer retry",
|
"peer retry",
|
||||||
1_700_000_402,
|
1_700_000_402,
|
||||||
)?;
|
)?;
|
||||||
self.write_config(ConflictMode::Fail, Some(&exact_pattern(&repo)), true)?;
|
self.write_config(ConflictMode::Fail, Some(&exact_pattern(&repo)), true)?;
|
||||||
self.sync_expect_failure(["--repo-pattern", &exact_pattern(&repo)])?;
|
self.sync_repo_expect_failure(&repo, [])?;
|
||||||
self.sync(["--retry-failed", "--force"])?;
|
self.write_config(ConflictMode::AutoRebase, Some(&exact_pattern(&repo)), true)?;
|
||||||
|
self.sync(["--retry-failed"])?;
|
||||||
self.assert_branch_all_equal(&repo, MAIN_BRANCH)?;
|
self.assert_branch_all_equal(&repo, MAIN_BRANCH)?;
|
||||||
self.write_config(ConflictMode::AutoRebasePullRequest, None, true)?;
|
self.write_config(ConflictMode::AutoRebasePullRequest, None, true)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -464,7 +462,7 @@ namespace = "{}"
|
|||||||
let repo = self.repo_name("rebase");
|
let repo = self.repo_name("rebase");
|
||||||
let (source, peer) = self.provider_pair();
|
let (source, peer) = self.provider_pair();
|
||||||
self.seed_all_main(&repo, "rebase base", 1_700_000_501)?;
|
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.unprotect_main_all(&repo)?;
|
||||||
|
|
||||||
self.commit_to_provider(
|
self.commit_to_provider(
|
||||||
@@ -493,42 +491,11 @@ namespace = "{}"
|
|||||||
Ok(())
|
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<()> {
|
fn pull_request_strategy_pushes_conflict_branches(&self) -> Result<()> {
|
||||||
let repo = self.repo_name("pull-request");
|
let repo = self.repo_name("pull-request");
|
||||||
let (source, peer) = self.provider_pair();
|
let (source, peer) = self.provider_pair();
|
||||||
self.seed_all_main(&repo, "pr base", 1_700_001_001)?;
|
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.unprotect_main_all(&repo)?;
|
||||||
|
|
||||||
self.commit_to_provider(
|
self.commit_to_provider(
|
||||||
@@ -564,7 +531,7 @@ namespace = "{}"
|
|||||||
let repo = self.repo_name("fallback");
|
let repo = self.repo_name("fallback");
|
||||||
let (source, peer) = self.provider_pair();
|
let (source, peer) = self.provider_pair();
|
||||||
self.seed_all_main(&repo, "fallback base", 1_700_001_201)?;
|
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.unprotect_main_all(&repo)?;
|
||||||
|
|
||||||
self.commit_to_provider(
|
self.commit_to_provider(
|
||||||
@@ -615,13 +582,13 @@ namespace = "{}"
|
|||||||
"delete-me",
|
"delete-me",
|
||||||
&git_output(&work, ["rev-parse", "HEAD"])?,
|
&git_output(&work, ["rev-parse", "HEAD"])?,
|
||||||
)?;
|
)?;
|
||||||
self.sync(["--repo-pattern", &exact_pattern(&repo)])?;
|
self.sync_repo(&repo, [])?;
|
||||||
source.wait_repo_listed(&repo)?;
|
source.wait_repo_listed(&repo)?;
|
||||||
self.assert_branch_all_equal(&repo, "delete-me")?;
|
self.assert_branch_all_equal(&repo, "delete-me")?;
|
||||||
|
|
||||||
self.git(&work, ["push", "origin", ":refs/heads/delete-me"])?;
|
self.git(&work, ["push", "origin", ":refs/heads/delete-me"])?;
|
||||||
source.wait_branch_absent(&repo, "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")?;
|
self.assert_branch_absent_everywhere(&repo, "delete-me")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -630,12 +597,12 @@ namespace = "{}"
|
|||||||
let repo = self.repo_name("repo-delete");
|
let repo = self.repo_name("repo-delete");
|
||||||
let source = self.primary_provider();
|
let source = self.primary_provider();
|
||||||
self.seed_all_main(&repo, "repo delete base", 1_700_001_501)?;
|
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)?;
|
self.assert_repo_exists_everywhere(&repo)?;
|
||||||
|
|
||||||
source.delete_repo(&repo)?;
|
source.delete_repo(&repo)?;
|
||||||
source.wait_repo_absent(&repo)?;
|
source.wait_repo_absent(&repo)?;
|
||||||
self.sync(["--repo-pattern", &exact_pattern(&repo)])?;
|
self.sync_repo(&repo, [])?;
|
||||||
self.assert_repo_absent_everywhere(&repo)?;
|
self.assert_repo_absent_everywhere(&repo)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -644,7 +611,7 @@ namespace = "{}"
|
|||||||
let repo = self.repo_name("webhook");
|
let repo = self.repo_name("webhook");
|
||||||
let source = self.primary_provider();
|
let source = self.primary_provider();
|
||||||
self.seed_all_main(&repo, "webhook base", 1_700_001_601)?;
|
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", "install", "--dry-run"])?;
|
||||||
self.refray(["webhook", "uninstall", "--dry-run"])?;
|
self.refray(["webhook", "uninstall", "--dry-run"])?;
|
||||||
@@ -785,14 +752,47 @@ namespace = "{}"
|
|||||||
assert_output_success(output, "git", &self.redactor)
|
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::<Vec<_>>()
|
||||||
|
.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<const N: usize>(&self, args: [&str; N]) -> Result<()> {
|
fn sync<const N: usize>(&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);
|
command.extend(args);
|
||||||
self.refray(command)
|
self.refray(command)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sync_repo<const N: usize>(&self, repo: &str, args: [&str; N]) -> Result<()> {
|
||||||
|
self.set_repo_whitelist(&exact_pattern(repo))?;
|
||||||
|
self.sync(args)
|
||||||
|
}
|
||||||
|
|
||||||
fn sync_expect_failure<const N: usize>(&self, args: [&str; N]) -> Result<()> {
|
fn sync_expect_failure<const N: usize>(&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);
|
command.extend(args);
|
||||||
let output = self.refray_output(command)?;
|
let output = self.refray_output(command)?;
|
||||||
if output.status.success() {
|
if output.status.success() {
|
||||||
@@ -801,6 +801,11 @@ namespace = "{}"
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sync_repo_expect_failure<const N: usize>(&self, repo: &str, args: [&str; N]) -> Result<()> {
|
||||||
|
self.set_repo_whitelist(&exact_pattern(repo))?;
|
||||||
|
self.sync_expect_failure(args)
|
||||||
|
}
|
||||||
|
|
||||||
fn refray<I, S>(&self, args: I) -> Result<()>
|
fn refray<I, S>(&self, args: I) -> Result<()>
|
||||||
where
|
where
|
||||||
I: IntoIterator<Item = S>,
|
I: IntoIterator<Item = S>,
|
||||||
@@ -834,6 +839,7 @@ namespace = "{}"
|
|||||||
}
|
}
|
||||||
command
|
command
|
||||||
.env("GIT_TERMINAL_PROMPT", "0")
|
.env("GIT_TERMINAL_PROMPT", "0")
|
||||||
|
.env("XDG_CACHE_HOME", &self.cache_home)
|
||||||
.output()
|
.output()
|
||||||
.context("failed to run refray")
|
.context("failed to run refray")
|
||||||
}
|
}
|
||||||
@@ -844,6 +850,7 @@ namespace = "{}"
|
|||||||
command.args(args);
|
command.args(args);
|
||||||
command
|
command
|
||||||
.env("GIT_TERMINAL_PROMPT", "0")
|
.env("GIT_TERMINAL_PROMPT", "0")
|
||||||
|
.env("XDG_CACHE_HOME", &self.cache_home)
|
||||||
.spawn()
|
.spawn()
|
||||||
.context("failed to spawn refray")
|
.context("failed to spawn refray")
|
||||||
}
|
}
|
||||||
@@ -938,7 +945,7 @@ namespace = "{}"
|
|||||||
Ok(()) => Ok(()),
|
Ok(()) => Ok(()),
|
||||||
Err(first_error) => {
|
Err(first_error) => {
|
||||||
thread::sleep(Duration::from_secs(2));
|
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:#}"))?;
|
.with_context(|| format!("initial convergence failed: {first_error:#}"))?;
|
||||||
self.assert_branch_all_equal(repo, branch)
|
self.assert_branch_all_equal(repo, branch)
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-15
@@ -21,21 +21,14 @@ fn cli_rejects_removed_config_subcommands() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cli_accepts_sync_repo_pattern() {
|
fn cli_rejects_removed_sync_args() {
|
||||||
let cli = Cli::try_parse_from([
|
for args in [
|
||||||
"refray",
|
["refray", "sync", "--repo-pattern", "^(foo|bar)-"].as_slice(),
|
||||||
"sync",
|
["refray", "sync", "--work-dir", "/tmp/refray"].as_slice(),
|
||||||
"--repo-pattern",
|
["refray", "sync", "--force"].as_slice(),
|
||||||
"^(foo|bar)-",
|
] {
|
||||||
"--dry-run",
|
assert!(Cli::try_parse_from(args).is_err());
|
||||||
])
|
}
|
||||||
.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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -85,6 +78,7 @@ fn cli_rejects_removed_serve_args() {
|
|||||||
["refray", "serve", "--secret", "secret"].as_slice(),
|
["refray", "serve", "--secret", "secret"].as_slice(),
|
||||||
["refray", "serve", "--secret-env", "WEBHOOK_SECRET"].as_slice(),
|
["refray", "serve", "--secret-env", "WEBHOOK_SECRET"].as_slice(),
|
||||||
["refray", "serve", "--full-sync-interval-minutes", "30"].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());
|
assert!(Cli::try_parse_from(args).is_err());
|
||||||
}
|
}
|
||||||
@@ -262,6 +256,15 @@ fn cli_rejects_removed_webhook_update_secret_args() {
|
|||||||
"WEBHOOK_SECRET",
|
"WEBHOOK_SECRET",
|
||||||
]
|
]
|
||||||
.as_slice(),
|
.as_slice(),
|
||||||
|
[
|
||||||
|
"refray",
|
||||||
|
"webhook",
|
||||||
|
"update",
|
||||||
|
"https://new.example.test/webhook",
|
||||||
|
"--work-dir",
|
||||||
|
"/tmp/refray",
|
||||||
|
]
|
||||||
|
.as_slice(),
|
||||||
] {
|
] {
|
||||||
assert!(Cli::try_parse_from(args).is_err());
|
assert!(Cli::try_parse_from(args).is_err());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ fn parses_token_forms() {
|
|||||||
repo_blacklist = ["-archive$"]
|
repo_blacklist = ["-archive$"]
|
||||||
create_missing = true
|
create_missing = true
|
||||||
visibility = "private"
|
visibility = "private"
|
||||||
allow_force = false
|
|
||||||
conflict_resolution = "auto_rebase_pull_request"
|
conflict_resolution = "auto_rebase_pull_request"
|
||||||
|
|
||||||
[[mirrors.endpoints]]
|
[[mirrors.endpoints]]
|
||||||
@@ -92,7 +91,6 @@ fn validation_rejects_unknown_sites_and_single_endpoint_groups() {
|
|||||||
repo_blacklist: Vec::new(),
|
repo_blacklist: Vec::new(),
|
||||||
create_missing: true,
|
create_missing: true,
|
||||||
visibility: Visibility::Private,
|
visibility: Visibility::Private,
|
||||||
allow_force: false,
|
|
||||||
conflict_resolution: ConflictResolutionStrategy::Fail,
|
conflict_resolution: ConflictResolutionStrategy::Fail,
|
||||||
}],
|
}],
|
||||||
webhook: None,
|
webhook: None,
|
||||||
@@ -122,7 +120,6 @@ fn validation_rejects_unknown_sites_and_single_endpoint_groups() {
|
|||||||
repo_blacklist: Vec::new(),
|
repo_blacklist: Vec::new(),
|
||||||
create_missing: true,
|
create_missing: true,
|
||||||
visibility: Visibility::Private,
|
visibility: Visibility::Private,
|
||||||
allow_force: false,
|
|
||||||
conflict_resolution: ConflictResolutionStrategy::Fail,
|
conflict_resolution: ConflictResolutionStrategy::Fail,
|
||||||
}],
|
}],
|
||||||
webhook: None,
|
webhook: None,
|
||||||
@@ -245,7 +242,6 @@ fn mirror_config() -> MirrorConfig {
|
|||||||
repo_blacklist: Vec::new(),
|
repo_blacklist: Vec::new(),
|
||||||
create_missing: true,
|
create_missing: true,
|
||||||
visibility: Visibility::Private,
|
visibility: Visibility::Private,
|
||||||
allow_force: false,
|
|
||||||
conflict_resolution: ConflictResolutionStrategy::Fail,
|
conflict_resolution: ConflictResolutionStrategy::Fail,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-33
@@ -75,7 +75,7 @@ fn branch_decisions_choose_fast_forward_tip() {
|
|||||||
|
|
||||||
let mirror = fixture.mirror();
|
let mirror = fixture.mirror();
|
||||||
fixture.fetch_all(&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());
|
assert!(conflicts.is_empty());
|
||||||
let main = find_branch(&decisions, "main");
|
let main = find_branch(&decisions, "main");
|
||||||
@@ -94,7 +94,7 @@ fn branch_decisions_do_not_target_remotes_that_already_match() {
|
|||||||
|
|
||||||
let mirror = fixture.mirror();
|
let mirror = fixture.mirror();
|
||||||
fixture.fetch_all(&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());
|
assert!(conflicts.is_empty());
|
||||||
let main = find_branch(&decisions, "main");
|
let main = find_branch(&decisions, "main");
|
||||||
@@ -132,7 +132,7 @@ fn cached_remote_refs_match_ls_remote_snapshot_after_fetch() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn branch_decisions_report_divergent_tips_without_force() {
|
fn branch_decisions_report_divergent_tips_as_conflicts() {
|
||||||
let fixture = GitFixture::new();
|
let fixture = GitFixture::new();
|
||||||
let base = fixture.commit("base", "base", 1_700_000_000);
|
let base = fixture.commit("base", "base", 1_700_000_000);
|
||||||
fixture.push_head(&fixture.remote_a, "main");
|
fixture.push_head(&fixture.remote_a, "main");
|
||||||
@@ -146,7 +146,7 @@ fn branch_decisions_report_divergent_tips_without_force() {
|
|||||||
|
|
||||||
let mirror = fixture.mirror();
|
let mirror = fixture.mirror();
|
||||||
fixture.fetch_all(&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!(decisions.is_empty());
|
||||||
assert_eq!(conflicts.len(), 1);
|
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));
|
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]
|
#[test]
|
||||||
fn auto_rebase_branch_conflict_replays_later_tip_and_marks_force_targets() {
|
fn auto_rebase_branch_conflict_replays_later_tip_and_marks_force_targets() {
|
||||||
let fixture = GitFixture::new();
|
let fixture = GitFixture::new();
|
||||||
@@ -195,7 +170,7 @@ fn auto_rebase_branch_conflict_replays_later_tip_and_marks_force_targets() {
|
|||||||
|
|
||||||
let mirror = fixture.mirror();
|
let mirror = fixture.mirror();
|
||||||
fixture.fetch_all(&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
|
let decision = mirror
|
||||||
.auto_rebase_branch_conflict(&fixture.remotes(), "main", &conflicts[0].tips)
|
.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();
|
let mirror = fixture.mirror();
|
||||||
fixture.fetch_all(&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
|
let error = mirror
|
||||||
.auto_rebase_branch_conflict(&fixture.remotes(), "main", &conflicts[0].tips)
|
.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();
|
let mirror = fixture.mirror();
|
||||||
fixture.fetch_all(&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());
|
assert!(conflicts.is_empty());
|
||||||
mirror
|
mirror
|
||||||
.push_branches(&fixture.remotes(), &decisions, false)
|
.push_branches(&fixture.remotes(), &decisions)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ fn wizard_builds_sync_group_from_profile_urls() {
|
|||||||
assert_eq!(config.mirrors[0].sync_visibility, SyncVisibility::All);
|
assert_eq!(config.mirrors[0].sync_visibility, SyncVisibility::All);
|
||||||
assert!(config.mirrors[0].create_missing);
|
assert!(config.mirrors[0].create_missing);
|
||||||
assert_eq!(config.mirrors[0].visibility, Visibility::Private);
|
assert_eq!(config.mirrors[0].visibility, Visibility::Private);
|
||||||
assert!(!config.mirrors[0].allow_force);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
config.mirrors[0].conflict_resolution,
|
config.mirrors[0].conflict_resolution,
|
||||||
ConflictResolutionStrategy::AutoRebasePullRequest
|
ConflictResolutionStrategy::AutoRebasePullRequest
|
||||||
@@ -216,7 +215,6 @@ fn wizard_starts_existing_config_at_sync_group_menu() {
|
|||||||
repo_blacklist: Vec::new(),
|
repo_blacklist: Vec::new(),
|
||||||
create_missing: true,
|
create_missing: true,
|
||||||
visibility: Visibility::Private,
|
visibility: Visibility::Private,
|
||||||
allow_force: false,
|
|
||||||
conflict_resolution: ConflictResolutionStrategy::Fail,
|
conflict_resolution: ConflictResolutionStrategy::Fail,
|
||||||
}],
|
}],
|
||||||
webhook: None,
|
webhook: None,
|
||||||
@@ -246,7 +244,6 @@ fn wizard_can_ask_to_run_full_sync_after_config() {
|
|||||||
repo_blacklist: Vec::new(),
|
repo_blacklist: Vec::new(),
|
||||||
create_missing: true,
|
create_missing: true,
|
||||||
visibility: Visibility::Private,
|
visibility: Visibility::Private,
|
||||||
allow_force: false,
|
|
||||||
conflict_resolution: ConflictResolutionStrategy::Fail,
|
conflict_resolution: ConflictResolutionStrategy::Fail,
|
||||||
}],
|
}],
|
||||||
webhook: None,
|
webhook: None,
|
||||||
@@ -323,7 +320,6 @@ fn wizard_edits_existing_sync_group_from_menu() {
|
|||||||
repo_blacklist: vec!["-archive$".to_string()],
|
repo_blacklist: vec!["-archive$".to_string()],
|
||||||
create_missing: false,
|
create_missing: false,
|
||||||
visibility: Visibility::Public,
|
visibility: Visibility::Public,
|
||||||
allow_force: true,
|
|
||||||
conflict_resolution: ConflictResolutionStrategy::Fail,
|
conflict_resolution: ConflictResolutionStrategy::Fail,
|
||||||
}],
|
}],
|
||||||
webhook: None,
|
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_whitelist, vec!["^public-".to_string()]);
|
||||||
assert_eq!(mirror.repo_blacklist, vec!["-skip$".to_string()]);
|
assert_eq!(mirror.repo_blacklist, vec!["-skip$".to_string()]);
|
||||||
assert_eq!(mirror.visibility, Visibility::Public);
|
assert_eq!(mirror.visibility, Visibility::Public);
|
||||||
assert!(mirror.allow_force);
|
|
||||||
|
|
||||||
let output = String::from_utf8(output).unwrap();
|
let output = String::from_utf8(output).unwrap();
|
||||||
assert!(output.contains("Edit sync group"));
|
assert!(output.contains("Edit sync group"));
|
||||||
assert!(output.contains("updated sync group 1"));
|
assert!(output.contains("updated sync group 1"));
|
||||||
@@ -413,7 +407,6 @@ fn wizard_prefills_existing_sync_group_when_editing() {
|
|||||||
repo_blacklist: Vec::new(),
|
repo_blacklist: Vec::new(),
|
||||||
create_missing: true,
|
create_missing: true,
|
||||||
visibility: Visibility::Private,
|
visibility: Visibility::Private,
|
||||||
allow_force: false,
|
|
||||||
conflict_resolution: ConflictResolutionStrategy::Fail,
|
conflict_resolution: ConflictResolutionStrategy::Fail,
|
||||||
}],
|
}],
|
||||||
webhook: None,
|
webhook: None,
|
||||||
@@ -478,7 +471,6 @@ fn wizard_deletes_existing_sync_group_from_menu() {
|
|||||||
repo_blacklist: Vec::new(),
|
repo_blacklist: Vec::new(),
|
||||||
create_missing: true,
|
create_missing: true,
|
||||||
visibility: Visibility::Private,
|
visibility: Visibility::Private,
|
||||||
allow_force: false,
|
|
||||||
conflict_resolution: ConflictResolutionStrategy::Fail,
|
conflict_resolution: ConflictResolutionStrategy::Fail,
|
||||||
}],
|
}],
|
||||||
webhook: None,
|
webhook: None,
|
||||||
@@ -538,7 +530,6 @@ fn wizard_can_go_back_from_delete_menu() {
|
|||||||
repo_blacklist: Vec::new(),
|
repo_blacklist: Vec::new(),
|
||||||
create_missing: true,
|
create_missing: true,
|
||||||
visibility: Visibility::Private,
|
visibility: Visibility::Private,
|
||||||
allow_force: false,
|
|
||||||
conflict_resolution: ConflictResolutionStrategy::Fail,
|
conflict_resolution: ConflictResolutionStrategy::Fail,
|
||||||
}],
|
}],
|
||||||
webhook: None,
|
webhook: None,
|
||||||
|
|||||||
@@ -74,7 +74,6 @@ where
|
|||||||
repo_blacklist: repo_filters.blacklist,
|
repo_blacklist: repo_filters.blacklist,
|
||||||
create_missing: true,
|
create_missing: true,
|
||||||
visibility: Visibility::Private,
|
visibility: Visibility::Private,
|
||||||
allow_force: false,
|
|
||||||
conflict_resolution,
|
conflict_resolution,
|
||||||
});
|
});
|
||||||
prompt_webhook_setup(reader, writer, config)?;
|
prompt_webhook_setup(reader, writer, config)?;
|
||||||
|
|||||||
@@ -413,7 +413,6 @@ fn test_mirror() -> MirrorConfig {
|
|||||||
repo_blacklist: Vec::new(),
|
repo_blacklist: Vec::new(),
|
||||||
create_missing: true,
|
create_missing: true,
|
||||||
visibility: crate::config::Visibility::Private,
|
visibility: crate::config::Visibility::Private,
|
||||||
allow_force: false,
|
|
||||||
conflict_resolution: ConflictResolutionStrategy::Fail,
|
conflict_resolution: ConflictResolutionStrategy::Fail,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,7 +116,6 @@ fn matches_jobs_by_provider_and_namespace() {
|
|||||||
repo_blacklist: Vec::new(),
|
repo_blacklist: Vec::new(),
|
||||||
create_missing: true,
|
create_missing: true,
|
||||||
visibility: Visibility::Private,
|
visibility: Visibility::Private,
|
||||||
allow_force: false,
|
|
||||||
conflict_resolution: ConflictResolutionStrategy::Fail,
|
conflict_resolution: ConflictResolutionStrategy::Fail,
|
||||||
}],
|
}],
|
||||||
webhook: None,
|
webhook: None,
|
||||||
@@ -144,7 +143,6 @@ fn matching_jobs_respects_repo_name_filters() {
|
|||||||
repo_blacklist: vec!["-archive$".to_string()],
|
repo_blacklist: vec!["-archive$".to_string()],
|
||||||
create_missing: true,
|
create_missing: true,
|
||||||
visibility: Visibility::Private,
|
visibility: Visibility::Private,
|
||||||
allow_force: false,
|
|
||||||
conflict_resolution: ConflictResolutionStrategy::Fail,
|
conflict_resolution: ConflictResolutionStrategy::Fail,
|
||||||
};
|
};
|
||||||
let config = Config {
|
let config = Config {
|
||||||
|
|||||||
Reference in New Issue
Block a user