stax 0.86.3

Fast stacked Git branches and PRs
Documentation
//! Integration tests for `stax get`.

use crate::common;

use common::{OutputAssertions, TestRepo};

fn push_remote_only_branch_from(
    repo: &TestRepo,
    branch: &str,
    base: &str,
    file: &str,
    content: &str,
) -> String {
    repo.git(&["checkout", "-B", branch, base]).assert_success();
    repo.create_file(file, content);
    repo.commit(&format!("Commit {}", branch));
    let sha = repo.head_sha();
    repo.git(&["push", "-u", "origin", branch]).assert_success();
    repo.git(&["checkout", "main"]).assert_success();
    repo.git(&["branch", "-D", branch]).assert_success();
    sha
}

fn push_remote_only_branch(repo: &TestRepo, branch: &str, file: &str, content: &str) -> String {
    push_remote_only_branch_from(repo, branch, "main", file, content)
}

fn parent_for(repo: &TestRepo, branch: &str) -> Option<String> {
    let json = repo.get_status_json();
    json["branches"]
        .as_array()
        .and_then(|branches| {
            branches
                .iter()
                .find(|entry| entry["name"].as_str() == Some(branch))
        })
        .and_then(|entry| entry["parent"].as_str())
        .map(ToString::to_string)
}

#[test]
fn get_fetches_remote_only_branch_and_tracks_it() {
    let repo = TestRepo::new_with_remote();
    let remote_sha = push_remote_only_branch(
        &repo,
        "remote-feature",
        "remote.txt",
        "remote branch content\n",
    );

    let out = repo.run_stax(&["get", "remote-feature"]);
    out.assert_success()
        .assert_stdout_contains("Created")
        .assert_stdout_contains("remote-feature");

    assert_eq!(repo.current_branch(), "remote-feature");
    assert_eq!(repo.get_commit_sha("remote-feature"), remote_sha);
    assert_eq!(repo.get_commit_sha("origin/remote-feature"), remote_sha);
    assert_eq!(parent_for(&repo, "remote-feature").as_deref(), Some("main"));

    repo.git(&["config", "branch.remote-feature.remote"])
        .assert_success()
        .assert_stdout_contains("origin");
    repo.git(&["config", "branch.remote-feature.merge"])
        .assert_success()
        .assert_stdout_contains("refs/heads/remote-feature");
}

#[test]
fn get_can_track_without_checkout_under_explicit_parent() {
    let repo = TestRepo::new_with_remote();
    let parent = repo.create_stack(&["review-base"]).remove(0);
    repo.git(&["checkout", "main"]).assert_success();
    let remote_sha = push_remote_only_branch_from(
        &repo,
        "remote-child",
        &parent,
        "child.txt",
        "child branch content\n",
    );

    let out = repo.run_stax(&["get", "remote-child", "--parent", &parent, "--no-checkout"]);
    out.assert_success()
        .assert_stdout_contains("Tracked")
        .assert_stdout_contains("remote-child");

    assert_eq!(repo.current_branch(), "main");
    assert_eq!(repo.get_commit_sha("remote-child"), remote_sha);
    assert_eq!(
        parent_for(&repo, "remote-child").as_deref(),
        Some(parent.as_str())
    );
}

#[test]
fn get_accepts_remote_qualified_branch_name() {
    let repo = TestRepo::new_with_remote();
    let remote_sha = push_remote_only_branch(
        &repo,
        "qualified-feature",
        "qualified.txt",
        "qualified branch content\n",
    );

    let out = repo.run_stax(&["get", "origin/qualified-feature", "--no-checkout"]);
    out.assert_success();

    assert_eq!(repo.current_branch(), "main");
    assert_eq!(repo.get_commit_sha("qualified-feature"), remote_sha);
    assert_eq!(
        parent_for(&repo, "qualified-feature").as_deref(),
        Some("main")
    );
}

#[test]
fn get_missing_remote_branch_fails_clearly() {
    let repo = TestRepo::new_with_remote();

    let out = repo.run_stax(&["get", "does-not-exist"]);
    out.assert_failure();

    let stderr = TestRepo::stderr(&out);
    assert!(
        stderr.contains("Remote branch 'does-not-exist' was not found"),
        "expected missing-branch error, got:\n{}",
        stderr
    );
}

#[test]
fn get_refuses_to_overwrite_divergent_local_branch_without_force() {
    let repo = TestRepo::new_with_remote();
    let remote_sha =
        push_remote_only_branch(&repo, "divergent-feature", "remote.txt", "remote content\n");

    repo.git(&["checkout", "-b", "divergent-feature", "main"])
        .assert_success();
    repo.create_file("local.txt", "local content\n");
    repo.commit("Local divergent commit");
    let local_sha = repo.head_sha();
    repo.git(&["checkout", "main"]).assert_success();

    let out = repo.run_stax(&["get", "divergent-feature", "--no-checkout"]);
    out.assert_failure();

    let stderr = TestRepo::stderr(&out);
    assert!(
        stderr.contains("already exists and differs"),
        "expected divergent-branch error, got:\n{}",
        stderr
    );
    assert_eq!(repo.get_commit_sha("divergent-feature"), local_sha);
    assert_ne!(repo.get_commit_sha("divergent-feature"), remote_sha);
}

#[test]
fn get_force_resets_divergent_local_branch() {
    let repo = TestRepo::new_with_remote();
    let remote_sha =
        push_remote_only_branch(&repo, "force-feature", "remote.txt", "remote content\n");

    repo.git(&["checkout", "-b", "force-feature", "main"])
        .assert_success();
    repo.create_file("local.txt", "local content\n");
    repo.commit("Local divergent commit");
    repo.git(&["checkout", "main"]).assert_success();

    let out = repo.run_stax(&["get", "force-feature", "--force", "--no-checkout"]);
    out.assert_success()
        .assert_stdout_contains("Reset")
        .assert_stdout_contains("Tracked");

    assert_eq!(repo.current_branch(), "main");
    assert_eq!(repo.get_commit_sha("force-feature"), remote_sha);
    assert_eq!(parent_for(&repo, "force-feature").as_deref(), Some("main"));
}