stax 0.78.2

Fast stacked Git branches and PRs
Documentation
mod common;

#[cfg(unix)]
mod unix {
    use super::common::{OutputAssertions, TestRepo};
    use std::fs;
    use std::os::unix::fs::PermissionsExt;

    fn configure_submit_remote(repo: &TestRepo) {
        let remote_path = repo
            .remote_path()
            .expect("Expected remote path for repository with origin");
        let remote_path_str = remote_path.to_string_lossy().to_string();

        let out = repo.git(&[
            "remote",
            "set-url",
            "origin",
            "https://github.com/test-owner/test-repo.git",
        ]);
        assert!(
            out.status.success(),
            "set-url failed: {}",
            TestRepo::stderr(&out)
        );

        let out = repo.git(&["remote", "set-url", "--push", "origin", &remote_path_str]);
        assert!(
            out.status.success(),
            "set-url --push failed: {}",
            TestRepo::stderr(&out)
        );

        let file_url = format!("file://{}", remote_path_str);
        let instead_of_key = format!("url.{}.insteadOf", file_url.trim_end_matches('/'));
        let out = repo.git(&[
            "config",
            "--local",
            &instead_of_key,
            "https://github.com/test-owner/test-repo.git",
        ]);
        assert!(
            out.status.success(),
            "insteadOf config failed: {}",
            TestRepo::stderr(&out)
        );
    }

    fn install_failing_pre_push_hook(repo: &TestRepo) {
        let hook = repo.path().join(".git/hooks/pre-push");
        fs::write(
            &hook,
            "#!/bin/sh\necho 'pre-push hook blocked submit' >&2\nexit 1\n",
        )
        .expect("write pre-push hook");
        fs::set_permissions(&hook, fs::Permissions::from_mode(0o755)).expect("chmod pre-push hook");
    }

    fn repo_with_failing_pre_push_hook(branch_name: &str) -> (TestRepo, String) {
        let repo = TestRepo::new_with_remote();
        configure_submit_remote(&repo);

        repo.run_stax(&["bc", branch_name]).assert_success();
        repo.create_file(
            &format!("{branch_name}.txt"),
            &format!("content for {branch_name}"),
        );
        repo.commit(&format!("Commit for {branch_name}"));
        let branch = repo.current_branch();

        install_failing_pre_push_hook(&repo);
        (repo, branch)
    }

    fn remote_has_branch(repo: &TestRepo, branch: &str) -> bool {
        let out = repo.git(&["ls-remote", "--heads", "origin", branch]);
        assert!(
            out.status.success(),
            "ls-remote failed: {}",
            TestRepo::stderr(&out)
        );

        let expected_ref = format!("refs/heads/{branch}");
        String::from_utf8_lossy(&out.stdout)
            .lines()
            .any(|line| line.ends_with(&expected_ref))
    }

    #[test]
    fn submit_no_verify_skips_pre_push_hook() {
        let (repo, branch) = repo_with_failing_pre_push_hook("submit-no-verify");

        repo.run_stax(&["ss", "--no-fetch", "--no-pr", "--no-prompt", "--yes"])
            .assert_failure();
        assert!(
            !remote_has_branch(&repo, &branch),
            "branch should not exist after hook-blocked push"
        );

        repo.run_stax(&[
            "ss",
            "--no-fetch",
            "--no-pr",
            "--no-prompt",
            "--yes",
            "--no-verify",
        ])
        .assert_success();
        assert!(
            remote_has_branch(&repo, &branch),
            "branch should be pushed when --no-verify bypasses the hook"
        );
    }

    #[test]
    fn branch_submit_alias_no_verify_skips_pre_push_hook() {
        let (repo, branch) = repo_with_failing_pre_push_hook("bs-no-verify");

        repo.run_stax(&["bs", "--no-fetch", "--no-pr", "--no-prompt", "--yes", "-n"])
            .assert_success();
        assert!(
            remote_has_branch(&repo, &branch),
            "bs -n should bypass the pre-push hook"
        );
    }

    #[test]
    fn upstack_submit_no_verify_skips_pre_push_hook() {
        let (repo, branch) = repo_with_failing_pre_push_hook("upstack-no-verify");

        repo.run_stax(&[
            "upstack",
            "submit",
            "--no-fetch",
            "--no-pr",
            "--no-prompt",
            "--yes",
            "--no-verify",
        ])
        .assert_success();
        assert!(
            remote_has_branch(&repo, &branch),
            "upstack submit --no-verify should bypass the pre-push hook"
        );
    }
}