mod common;
use assert_cmd::Command;
use assert_cmd::cargo::cargo_bin_cmd;
use common::{add_test_remote, create_nested_repo, create_test_repo, get_remote_url, write_config};
use predicates::prelude::*;
fn gemote() -> Command {
cargo_bin_cmd!("gemote")
}
#[test]
fn sync_no_config() {
let (dir, _repo) = create_test_repo();
gemote()
.args(["--repo", dir.path().to_str().unwrap(), "sync"])
.assert()
.failure()
.stderr(predicate::str::contains("config"));
}
#[test]
fn sync_adds_missing_remote() {
let (dir, repo) = create_test_repo();
write_config(
dir.path(),
r#"
[remotes.origin]
url = "https://example.com/repo.git"
"#,
);
gemote()
.args(["--repo", dir.path().to_str().unwrap(), "sync"])
.assert()
.success()
.stdout(predicate::str::contains("add"));
let (url, _) = get_remote_url(&repo, "origin");
assert_eq!(url, "https://example.com/repo.git");
}
#[test]
fn sync_adds_with_push_url() {
let (dir, repo) = create_test_repo();
write_config(
dir.path(),
r#"
[remotes.origin]
url = "https://example.com/repo.git"
push_url = "git@example.com:repo.git"
"#,
);
gemote()
.args(["--repo", dir.path().to_str().unwrap(), "sync"])
.assert()
.success();
let (url, push_url) = get_remote_url(&repo, "origin");
assert_eq!(url, "https://example.com/repo.git");
assert_eq!(push_url.as_deref(), Some("git@example.com:repo.git"));
}
#[test]
fn sync_updates_url() {
let (dir, repo) = create_test_repo();
add_test_remote(&repo, "origin", "https://old.com/repo.git", None);
write_config(
dir.path(),
r#"
[remotes.origin]
url = "https://new.com/repo.git"
"#,
);
gemote()
.args(["--repo", dir.path().to_str().unwrap(), "sync"])
.assert()
.success()
.stdout(predicate::str::contains("update"));
let (url, _) = get_remote_url(&repo, "origin");
assert_eq!(url, "https://new.com/repo.git");
}
#[test]
fn sync_updates_push_url() {
let (dir, repo) = create_test_repo();
add_test_remote(&repo, "origin", "https://example.com/repo.git", None);
write_config(
dir.path(),
r#"
[remotes.origin]
url = "https://example.com/repo.git"
push_url = "git@example.com:repo.git"
"#,
);
gemote()
.args(["--repo", dir.path().to_str().unwrap(), "sync"])
.assert()
.success();
let (_, push_url) = get_remote_url(&repo, "origin");
assert_eq!(push_url.as_deref(), Some("git@example.com:repo.git"));
}
#[test]
fn sync_already_in_sync() {
let (dir, repo) = create_test_repo();
add_test_remote(&repo, "origin", "https://example.com/repo.git", None);
write_config(
dir.path(),
r#"
[remotes.origin]
url = "https://example.com/repo.git"
"#,
);
gemote()
.args(["--repo", dir.path().to_str().unwrap(), "sync"])
.assert()
.success()
.stdout(predicate::str::contains("Already in sync"));
}
#[test]
fn sync_dry_run_no_apply() {
let (dir, repo) = create_test_repo();
write_config(
dir.path(),
r#"
[remotes.origin]
url = "https://example.com/repo.git"
"#,
);
gemote()
.args(["--repo", dir.path().to_str().unwrap(), "sync", "--dry-run"])
.assert()
.success()
.stdout(predicate::str::contains("dry run"));
assert!(repo.find_remote("origin").is_err());
}
#[test]
fn sync_extra_ignore() {
let (dir, repo) = create_test_repo();
add_test_remote(&repo, "extra", "https://extra.com/repo.git", None);
write_config(
dir.path(),
r#"
[settings]
extra_remotes = "ignore"
"#,
);
gemote()
.args(["--repo", dir.path().to_str().unwrap(), "sync"])
.assert()
.success()
.stdout(predicate::str::contains("Already in sync"));
assert!(repo.find_remote("extra").is_ok());
}
#[test]
fn sync_extra_warn() {
let (dir, repo) = create_test_repo();
add_test_remote(&repo, "extra", "https://extra.com/repo.git", None);
write_config(
dir.path(),
r#"
[settings]
extra_remotes = "warn"
"#,
);
gemote()
.args(["--repo", dir.path().to_str().unwrap(), "sync"])
.assert()
.success()
.stderr(predicate::str::contains("warning"));
assert!(repo.find_remote("extra").is_ok());
}
#[test]
fn sync_extra_remove() {
let (dir, repo) = create_test_repo();
add_test_remote(&repo, "extra", "https://extra.com/repo.git", None);
write_config(
dir.path(),
r#"
[settings]
extra_remotes = "remove"
"#,
);
gemote()
.args(["--repo", dir.path().to_str().unwrap(), "sync"])
.assert()
.success()
.stdout(predicate::str::contains("remove"));
assert!(repo.find_remote("extra").is_err());
}
#[test]
fn sync_custom_config_path() {
let (dir, repo) = create_test_repo();
let config_path = dir.path().join("custom-config.toml");
std::fs::write(
&config_path,
r#"
[remotes.origin]
url = "https://example.com/repo.git"
"#,
)
.unwrap();
gemote()
.args([
"--repo",
dir.path().to_str().unwrap(),
"--config",
config_path.to_str().unwrap(),
"sync",
])
.assert()
.success();
let (url, _) = get_remote_url(&repo, "origin");
assert_eq!(url, "https://example.com/repo.git");
}
#[test]
fn sync_not_a_repo() {
let dir = tempfile::TempDir::new().unwrap();
gemote()
.args(["--repo", dir.path().to_str().unwrap(), "sync"])
.assert()
.failure();
}
#[test]
fn sync_recursive_applies_to_nested() {
let (dir, _repo) = create_test_repo();
let nested = create_nested_repo(dir.path(), "libs/core");
write_config(
dir.path(),
r#"
[remotes.origin]
url = "https://example.com/repo.git"
[submodules."libs/core".remotes.origin]
url = "https://example.com/core.git"
"#,
);
gemote()
.args(["--repo", dir.path().to_str().unwrap(), "sync", "-r"])
.assert()
.success();
let (url, _) = get_remote_url(&nested, "origin");
assert_eq!(url, "https://example.com/core.git");
}
#[test]
fn sync_recursive_dry_run() {
let (dir, _repo) = create_test_repo();
let nested = create_nested_repo(dir.path(), "libs/core");
write_config(
dir.path(),
r#"
[submodules."libs/core".remotes.origin]
url = "https://example.com/core.git"
"#,
);
gemote()
.args([
"--repo",
dir.path().to_str().unwrap(),
"sync",
"-r",
"--dry-run",
])
.assert()
.success()
.stdout(predicate::str::contains("dry run"));
assert!(nested.find_remote("origin").is_err());
}
#[test]
fn sync_recursive_warns_missing_repo() {
let (dir, _repo) = create_test_repo();
write_config(
dir.path(),
r#"
[submodules."nonexistent".remotes.origin]
url = "https://example.com/missing.git"
"#,
);
gemote()
.args(["--repo", dir.path().to_str().unwrap(), "sync", "-r"])
.assert()
.success()
.stderr(predicate::str::contains("no matching repo found"));
}
#[test]
fn sync_recursive_warns_no_config() {
let (dir, _repo) = create_test_repo();
let _nested = create_nested_repo(dir.path(), "libs/core");
write_config(
dir.path(),
r#"
[settings]
extra_remotes = "ignore"
"#,
);
gemote()
.args(["--repo", dir.path().to_str().unwrap(), "sync", "-r"])
.assert()
.success()
.stderr(predicate::str::contains("has no config section"));
}
#[test]
fn sync_recursive_deeply_nested() {
let (dir, _repo) = create_test_repo();
let nested = create_nested_repo(dir.path(), "libs/core");
nested.remote("old", "https://old.com/core.git").unwrap();
let deep = create_nested_repo(dir.path().join("libs/core").as_path(), "inner");
deep.remote("stale", "https://stale.com/inner.git").unwrap();
write_config(
dir.path(),
r#"
[remotes.origin]
url = "https://example.com/repo.git"
[submodules."libs/core".remotes.origin]
url = "https://example.com/core.git"
[submodules."libs/core".submodules."inner".remotes.origin]
url = "https://example.com/inner.git"
"#,
);
gemote()
.args(["--repo", dir.path().to_str().unwrap(), "sync", "-r"])
.assert()
.success()
.stdout(predicate::str::contains("Submodule"));
let (url, _) = get_remote_url(&deep, "origin");
assert_eq!(url, "https://example.com/inner.git");
}
#[test]
fn sync_recursive_deeply_nested_no_config() {
let (dir, _repo) = create_test_repo();
let _nested = create_nested_repo(dir.path(), "libs/core");
let _deep = create_nested_repo(dir.path().join("libs/core").as_path(), "inner");
write_config(
dir.path(),
r#"
[submodules."libs/core".remotes.origin]
url = "https://example.com/core.git"
[submodules."libs/core".submodules."other".remotes.origin]
url = "https://example.com/other.git"
"#,
);
gemote()
.args(["--repo", dir.path().to_str().unwrap(), "sync", "-r"])
.assert()
.success()
.stderr(predicate::str::contains("has no config section"));
}
#[test]
fn sync_nonrecursive_ignores_nested() {
let (dir, _repo) = create_test_repo();
let nested = create_nested_repo(dir.path(), "libs/core");
write_config(
dir.path(),
r#"
[submodules."libs/core".remotes.origin]
url = "https://example.com/core.git"
"#,
);
gemote()
.args(["--repo", dir.path().to_str().unwrap(), "sync"])
.assert()
.success()
.stderr(predicate::str::contains("recursion is disabled"));
assert!(nested.find_remote("origin").is_err());
}
#[test]
fn sync_config_recursive_true_recurses_without_flag() {
let (dir, _repo) = create_test_repo();
let nested = create_nested_repo(dir.path(), "libs/core");
write_config(
dir.path(),
r#"
[settings]
recursive = true
[submodules."libs/core".remotes.origin]
url = "https://example.com/core.git"
"#,
);
gemote()
.args(["--repo", dir.path().to_str().unwrap(), "sync"])
.assert()
.success()
.stderr(predicate::str::contains("recursion is disabled").not());
let (url, _) = get_remote_url(&nested, "origin");
assert_eq!(url, "https://example.com/core.git");
}
#[test]
fn sync_no_recursive_flag_overrides_config_true() {
let (dir, _repo) = create_test_repo();
let nested = create_nested_repo(dir.path(), "libs/core");
write_config(
dir.path(),
r#"
[settings]
recursive = true
[submodules."libs/core".remotes.origin]
url = "https://example.com/core.git"
"#,
);
gemote()
.args([
"--repo",
dir.path().to_str().unwrap(),
"sync",
"--no-recursive",
])
.assert()
.success();
assert!(nested.find_remote("origin").is_err());
}
#[test]
fn sync_no_warning_when_flat_config() {
let (dir, _repo) = create_test_repo();
write_config(
dir.path(),
r#"
[remotes.origin]
url = "https://example.com/repo.git"
"#,
);
gemote()
.args(["--repo", dir.path().to_str().unwrap(), "sync"])
.assert()
.success()
.stderr(predicate::str::contains("recursion is disabled").not());
}