mod common;
use common::*;
use std::path::{Path, PathBuf};
const HELLO: &str = "https://github.com/octocat/Hello-World.git";
const SUBMOD: &str = "https://github.com/git-fixtures/submodule.git";
fn gated() -> bool {
if std::env::var("GKIT_NET_TESTS").as_deref() == Ok("1") {
true
} else {
eprintln!("skipping network test (set GKIT_NET_TESTS=1 to run)");
false
}
}
fn clone_public(repo: &str, tag: &str) -> PathBuf {
let base = temp_dir(tag);
let work = base.join("repo");
git_ok(&base, &["clone", repo, work.to_str().unwrap()]);
work
}
fn pristine(work: &Path) {
git_ok(work, &["checkout", "-qf", "master"]);
git_ok(work, &["reset", "--hard", "-q", "origin/master"]);
let _ = git(work, &["clean", "-fdq"]);
let heads = git(
work,
&["for-each-ref", "--format=%(refname:short)", "refs/heads/*"],
)
.stdout;
for b in heads.lines().map(str::trim).filter(|b| !b.is_empty()) {
if b != "master" {
let _ = git(work, &["branch", "-D", b]);
}
}
let _ = git(work, &["config", "--unset", "gkit.solo"]);
let _ = git(work, &["config", "--unset", "gkit.baseBranch"]);
}
fn logoff_v(work: &Path) -> Out {
gkit(
work,
&["logoff", "-v", "--no-fetch", work.to_str().unwrap()],
)
}
fn logoff_vv(work: &Path) -> Out {
gkit(
work,
&["logoff", "-vv", "--no-fetch", work.to_str().unwrap()],
)
}
#[test]
fn net_logoff_scenarios_no_submodules() {
if !gated() {
return;
}
let work = clone_public(HELLO, "net-hw");
pristine(&work);
let o = logoff_v(&work);
assert_check(&o.stdout, &work, "committed", "true");
assert_check(&o.stdout, &work, "all-commits-pushed", "true");
assert_check(&o.stdout, &work, "branches-have-remote", "true");
assert_check(&o.stdout, &work, "correct-branch", "true");
assert!(
!o.stdout.contains("branch-rule") && !o.stdout.contains("base-branch"),
"the -v scan carries no metadata lines:\n{}",
o.stdout
);
assert_eq!(o.code, 0, "pristine clone should pass:\n{}", o.all());
let ovv = logoff_vv(&work);
assert_check(
&ovv.stdout,
&work,
"base-branch",
"master (derived from remote origin/master)",
);
pristine(&work);
std::fs::write(work.join("DIRTY.txt"), "x\n").unwrap();
let o = logoff_v(&work);
assert_check(&o.stdout, &work, "committed", "false");
assert_eq!(o.code, 1);
pristine(&work);
std::fs::write(work.join("local.txt"), "x\n").unwrap();
git_ok(&work, &["add", "."]);
git_ok(&work, &["commit", "-m", "local only"]);
let o = logoff_v(&work);
assert_check(&o.stdout, &work, "all-commits-pushed", "false");
assert_eq!(o.code, 1);
pristine(&work);
git_ok(&work, &["checkout", "-q", "-b", "local-only"]);
std::fs::write(work.join("f.txt"), "x\n").unwrap();
git_ok(&work, &["add", "."]);
git_ok(&work, &["commit", "-m", "wip"]);
let o = logoff_v(&work);
assert_check(&o.stdout, &work, "branches-have-remote", "false");
assert_eq!(o.code, 1);
pristine(&work);
git_ok(&work, &["checkout", "-q", "--detach", "HEAD"]);
let o = logoff_v(&work);
assert_check(&o.stdout, &work, "correct-branch", "false");
assert_eq!(o.code, 1);
pristine(&work);
git_ok(&work, &["checkout", "-q", "-b", "feature-x"]);
std::fs::write(work.join("feat.txt"), "x\n").unwrap();
git_ok(&work, &["add", "."]);
git_ok(&work, &["commit", "-m", "feature"]);
git_ok(&work, &["checkout", "-q", "master"]);
let o = logoff_v(&work);
assert_check(&o.stdout, &work, "correct-branch", "false");
assert_eq!(o.code, 1);
pristine(&work);
git_ok(&work, &["config", "gkit.baseBranch", "dev"]);
let o = logoff_vv(&work);
assert_check(
&o.stdout,
&work,
"base-branch",
"dev (from git config gkit.baseBranch)",
);
pristine(&work);
git_ok(&work, &["config", "gkit.solo", "true"]);
let o = logoff_vv(&work);
assert_check(
&o.stdout,
&work,
"branch-rule",
"solo (gkit.solo on) — flags any feature branch on the remote",
);
assert_check(&o.stdout, &work, "R5 correct-branch", "false");
assert_eq!(
o.code,
1,
"solo rule should fail (remote has feature branches):\n{}",
o.all()
);
pristine(&work);
let o = logoff_v(&work);
assert_check(&o.stdout, &work, "correct-branch", "true");
assert_eq!(
o.code,
0,
"team rule passes where solo failed:\n{}",
o.all()
);
}
#[test]
fn net_stmb_switch_and_delete() {
if !gated() {
return;
}
let work = clone_public(HELLO, "net-stmb");
git_ok(&work, &["checkout", "-q", "-b", "feat-net"]);
let o = gkit(
&work,
&["stmb", "--yes", "--base", "master", work.to_str().unwrap()],
);
assert_contains(&o.stdout, "+ git checkout master");
assert_contains(&o.stdout, "+ git branch -d feat-net");
assert_contains(&o.stdout, "--- logoff ---");
assert!(
git(&work, &["branch", "--list", "feat-net"])
.stdout
.trim()
.is_empty(),
"feat-net should be deleted:\n{}",
o.all()
);
assert_eq!(
o.code,
0,
"stmb + verifying logoff should pass:\n{}",
o.all()
);
}
#[test]
fn net_logoff_submodule_recursion() {
if !gated() {
return;
}
let base = temp_dir("net-sub");
let sup = base.join("super");
git_ok(&base, &["clone", SUBMOD, sup.to_str().unwrap()]);
git_ok(&sup, &["submodule", "update", "--init", "basic"]);
let basic = sup.join("basic");
let o = logoff_v(&sup);
assert_check(&o.stdout, &basic, "correct-branch", "false"); assert!(
result_index(&o.stdout, "basic") < result_index(&o.stdout, "super"),
"submodule should be reported before superproject:\n{}",
o.stdout
);
assert_eq!(
o.code,
1,
"detached submodule should fail the gate:\n{}",
o.all()
);
git_ok(&basic, &["checkout", "-q", "master"]);
let o = logoff_v(&sup);
assert_check(&o.stdout, &basic, "correct-branch", "true");
assert_check(&o.stdout, &sup, "correct-branch", "true");
assert_eq!(
o.code,
0,
"super + on-branch submodule should pass:\n{}",
o.all()
);
}
fn result_index(out: &str, last: &str) -> usize {
out.lines()
.position(|l| {
l.contains("\tRESULT\t")
&& l.split('\t')
.next()
.unwrap()
.replace('\\', "/")
.rsplit('/')
.next()
== Some(last)
})
.unwrap_or_else(|| panic!("no RESULT line with last component {last}:\n{out}"))
}