use crate::common::{
TestRepo, make_snapshot_cmd, repo, repo_with_feature_worktree, repo_with_remote,
setup_snapshot_settings,
};
use insta_cmd::assert_cmd_snapshot;
use rstest::rstest;
fn snapshot_push(test_name: &str, repo: &TestRepo, args: &[&str], cwd: Option<&std::path::Path>) {
let settings = setup_snapshot_settings(repo);
settings.bind(|| {
let mut step_args = vec!["push"];
step_args.extend_from_slice(args);
let mut cmd = make_snapshot_cmd(repo, "step", &step_args, cwd);
assert_cmd_snapshot!(test_name, cmd);
});
}
#[rstest]
fn test_push_fast_forward(mut repo: TestRepo) {
repo.add_main_worktree();
let feature_wt =
repo.add_worktree_with_commit("feature", "test.txt", "test content", "Add test file");
snapshot_push("push_fast_forward", &repo, &["main"], Some(&feature_wt));
}
#[rstest]
fn test_push_not_fast_forward(mut repo: TestRepo) {
repo.commit_in_worktree(
repo.root_path(),
"main.txt",
"main content",
"Add main file",
);
let feature_wt = repo.add_feature();
snapshot_push("push_not_fast_forward", &repo, &["main"], Some(&feature_wt));
}
#[rstest]
fn test_push_to_default_branch(#[from(repo_with_feature_worktree)] repo: TestRepo) {
let feature_wt = repo.worktree_path("feature");
snapshot_push("push_to_default", &repo, &[], Some(feature_wt));
}
#[rstest]
fn test_push_with_dirty_target(mut repo: TestRepo) {
std::fs::write(repo.root_path().join("conflict.txt"), "old content").unwrap();
let feature_wt = repo.add_worktree_with_commit(
"feature",
"conflict.txt",
"new content",
"Add conflict file",
);
snapshot_push(
"push_dirty_target_overlap",
&repo,
&["main"],
Some(&feature_wt),
);
let main_contents = std::fs::read_to_string(repo.root_path().join("conflict.txt")).unwrap();
assert_eq!(main_contents, "old content");
let stash_list = repo.git_command().args(["stash", "list"]).run().unwrap();
assert!(
String::from_utf8_lossy(&stash_list.stdout)
.trim()
.is_empty()
);
}
#[rstest]
fn test_push_dirty_target_autostash(mut repo: TestRepo) {
std::fs::write(repo.root_path().join("notes.txt"), "temporary notes").unwrap();
let feature_wt = repo.add_feature();
snapshot_push(
"push_dirty_target_autostash",
&repo,
&["main"],
Some(&feature_wt),
);
let notes = std::fs::read_to_string(repo.root_path().join("notes.txt")).unwrap();
assert_eq!(notes, "temporary notes");
let stash_list = repo.git_command().args(["stash", "list"]).run().unwrap();
assert!(
String::from_utf8_lossy(&stash_list.stdout)
.trim()
.is_empty()
);
}
#[rstest]
fn test_push_dirty_target_overlap_renamed_file(mut repo: TestRepo) {
repo.commit_in_worktree(
repo.root_path(),
"file.txt",
"original content",
"Initial file",
);
let feature_wt = repo.add_worktree("feature");
std::fs::write(repo.root_path().join("file.txt"), "modified in target").unwrap();
repo.run_git_in(&feature_wt, &["mv", "file.txt", "renamed.txt"]);
repo.run_git_in(
&feature_wt,
&["commit", "-m", "Rename file.txt to renamed.txt"],
);
snapshot_push(
"push_dirty_target_overlap_renamed_file",
&repo,
&["main"],
Some(&feature_wt),
);
let main_contents = std::fs::read_to_string(repo.root_path().join("file.txt")).unwrap();
assert_eq!(main_contents, "modified in target");
let stash_list = repo.git_command().args(["stash", "list"]).run().unwrap();
assert!(
String::from_utf8_lossy(&stash_list.stdout)
.trim()
.is_empty()
);
}
#[rstest]
fn test_push_error_not_fast_forward(#[from(repo_with_remote)] mut repo: TestRepo) {
let feature_wt = repo.add_worktree("feature");
repo.commit_in_worktree(
repo.root_path(),
"main-file.txt",
"main content",
"Main commit",
);
repo.push_branch("main");
repo.commit_in_worktree(
&feature_wt,
"feature.txt",
"feature content",
"Feature commit",
);
snapshot_push(
"push_error_not_fast_forward",
&repo,
&["main"],
Some(&feature_wt),
);
}
#[rstest]
fn test_push_with_merge_commits(mut repo: TestRepo) {
let feature_wt = repo.add_worktree_with_commit("feature", "file1.txt", "content1", "Commit 1");
repo.run_git_in(&feature_wt, &["checkout", "-b", "temp"]);
repo.commit_in_worktree(&feature_wt, "file2.txt", "content2", "Commit 2");
repo.run_git_in(&feature_wt, &["checkout", "feature"]);
repo.run_git_in(
&feature_wt,
&["merge", "temp", "--no-ff", "-m", "Merge temp"],
);
snapshot_push(
"push_with_merge_commits",
&repo,
&["main"],
Some(&feature_wt),
);
}
#[rstest]
fn test_push_no_ff(mut repo: TestRepo) {
repo.add_main_worktree();
let feature_wt =
repo.add_worktree_with_commit("feature", "test.txt", "test content", "Add test file");
snapshot_push("push_no_ff", &repo, &["--no-ff", "main"], Some(&feature_wt));
let cat_file = repo.git_output(&["cat-file", "-p", "main"]);
let parents: Vec<&str> = cat_file
.lines()
.filter(|l| l.starts_with("parent "))
.collect();
assert_eq!(
parents.len(),
2,
"Merge commit should have exactly 2 parents"
);
let commit_msg = repo.git_output(&["log", "-1", "--format=%s", "main"]);
assert_eq!(commit_msg, "Merge branch 'feature' into main");
}
#[rstest]
fn test_push_with_submodule_recurse_config(mut repo: TestRepo) {
repo.run_git(&["config", "submodule.recurse", "true"]);
repo.add_main_worktree();
let feature_wt =
repo.add_worktree_with_commit("feature", "test.txt", "test content", "Add test file");
snapshot_push(
"push_with_submodule_recurse",
&repo,
&["main"],
Some(&feature_wt),
);
}
#[rstest]
fn test_push_no_ff_with_submodule_recurse_config(mut repo: TestRepo) {
repo.run_git(&["config", "submodule.recurse", "true"]);
repo.add_main_worktree();
let feature_wt =
repo.add_worktree_with_commit("feature", "test.txt", "test content", "Add test file");
snapshot_push(
"push_no_ff_with_submodule_recurse",
&repo,
&["--no-ff", "main"],
Some(&feature_wt),
);
}
#[rstest]
fn test_push_no_remote(#[from(repo_with_feature_worktree)] repo: TestRepo) {
let feature_wt = repo.worktree_path("feature");
snapshot_push("push_no_remote", &repo, &[], Some(feature_wt));
}