use lib::git::GitVersion; use lib::testing::{ make_git_with_remote_repo, remove_nondeterministic_lines, GitInitOptions, GitRunOptions, GitWrapperWithRemoteRepo, }; /// Minimum version due to changes in the output of `git push`. const MIN_VERSION: GitVersion = GitVersion(2, 36, 0); fn redact_remotes(output: String) -> String { output .lines() .map(|line| { if line.contains("To file://") { "To: file://\n".to_string() } else if line.contains("From file://") { "From: file://\n".to_string() } else if line.contains("error: failed to push some refs to 'file://") { "error: failed to push some refs to 'file://'\n".to_string() } else { format!("{line}\n") } }) .collect() } #[test] fn test_submit() -> eyre::Result<()> { let GitWrapperWithRemoteRepo { temp_dir: _guard, original_repo, cloned_repo, } = make_git_with_remote_repo()?; if original_repo.get_version()? < MIN_VERSION { return Ok(()); } { original_repo.init_repo()?; original_repo.commit_file("test1", 1)?; original_repo.commit_file("test2", 2)?; original_repo.clone_repo_into(&cloned_repo, &[])?; } cloned_repo.init_repo_with_options(&GitInitOptions { make_initial_commit: false, ..Default::default() })?; cloned_repo.run(&["checkout", "-b", "foo"])?; cloned_repo.commit_file("test3", 3)?; cloned_repo.run(&["checkout", "-b", "bar", "master"])?; cloned_repo.commit_file("test4", 4)?; cloned_repo.run(&["checkout", "-b", "qux"])?; cloned_repo.commit_file("test5", 5)?; { let (stdout, stderr) = cloned_repo.run(&["submit"])?; insta::assert_snapshot!(stderr, @""); insta::assert_snapshot!(stdout, @r###" Skipped 2 commits (not yet on remote): bar, qux These commits were skipped because they were not already associated with a remote repository. To submit them, retry this operation with the --create option. "###); } // test handling of revset argument (should be identical to above) { let (stdout, stderr) = cloned_repo.run(&["submit", "bar+qux"])?; insta::assert_snapshot!(stderr, @""); insta::assert_snapshot!(stdout, @r###" Skipped 2 commits (not yet on remote): bar, qux These commits were skipped because they were not already associated with a remote repository. To submit them, retry this operation with the --create option. "###); } // test handling of multiple revset arguments (should be identical to above) { let (stdout, stderr) = cloned_repo.run(&["submit", "bar", "qux"])?; insta::assert_snapshot!(stderr, @""); insta::assert_snapshot!(stdout, @r###" Skipped 2 commits (not yet on remote): bar, qux These commits were skipped because they were not already associated with a remote repository. To submit them, retry this operation with the --create option. "###); } { let (stdout, stderr) = cloned_repo.run(&["submit", "--dry-run"])?; insta::assert_snapshot!(stderr, @""); insta::assert_snapshot!(stdout, @r###" Would skip 2 commits (not yet on remote): bar, qux These commits would be skipped because they are not already associated with a remote repository. To submit them, retry this operation with the --create option. "###); } { let (stdout, stderr) = cloned_repo.run(&["submit", "--create", "--dry-run"])?; insta::assert_snapshot!(stderr, @""); insta::assert_snapshot!(stdout, @r###" Would submit 2 commits: bar, qux "###); } { let (stdout, stderr) = cloned_repo.run(&["submit", "--create"])?; let stderr = redact_remotes(stderr); insta::assert_snapshot!(stderr, @r###" branchless: processing 1 update: branch bar branchless: processing 1 update: branch qux To: file:// * [new branch] bar -> bar * [new branch] qux -> qux branchless: processing 1 update: remote branch origin/bar branchless: processing 1 update: remote branch origin/qux "###); insta::assert_snapshot!(stdout, @r###" branchless: running command: push --set-upstream origin bar qux branch 'bar' set up to track 'origin/bar'. branch 'qux' set up to track 'origin/qux'. Submitted 2 commits: bar, qux "###); } { let (stdout, stderr) = original_repo.run(&["branch", "-a"])?; insta::assert_snapshot!(stderr, @""); insta::assert_snapshot!(stdout, @r###" bar * master qux "###); } cloned_repo.run(&["commit", "--amend", "-m", "updated message"])?; { let (stdout, stderr) = cloned_repo.run(&["submit"])?; let stderr = redact_remotes(stderr); insta::assert_snapshot!(stderr, @r###" From: file:// * branch bar -> FETCH_HEAD * branch qux -> FETCH_HEAD branchless: processing 1 update: branch qux To: file:// + 20230db...bae8307 qux -> qux (forced update) branchless: processing 1 update: remote branch origin/qux "###); insta::assert_snapshot!(stdout, @r###" branchless: running command: fetch origin refs/heads/bar refs/heads/qux branchless: running command: push --force-with-lease origin qux Updated 1 commit: qux Skipped 1 commit (already up-to-date): bar "###); } // Test case where there are no remote branches to create, even though user has asked for `--create` { let (stdout, stderr) = cloned_repo.run(&["submit", "--create"])?; let stderr = redact_remotes(stderr); insta::assert_snapshot!(stderr, @r###" From: file:// * branch bar -> FETCH_HEAD * branch qux -> FETCH_HEAD "###); insta::assert_snapshot!(stdout, @r###" branchless: running command: fetch origin refs/heads/bar refs/heads/qux Skipped 2 commits (already up-to-date): bar, qux "###); } Ok(()) } #[test] fn test_submit_multiple_remotes() -> eyre::Result<()> { let GitWrapperWithRemoteRepo { temp_dir: _guard, original_repo, cloned_repo, } = make_git_with_remote_repo()?; if original_repo.get_version()? < MIN_VERSION { return Ok(()); } { original_repo.init_repo()?; original_repo.commit_file("test1", 1)?; original_repo.commit_file("test2", 2)?; original_repo.clone_repo_into(&cloned_repo, &[])?; } cloned_repo.init_repo_with_options(&GitInitOptions { make_initial_commit: false, ..Default::default() })?; cloned_repo.run(&["checkout", "-b", "foo"])?; cloned_repo.commit_file("test3", 3)?; cloned_repo.run(&["branch", "--unset-upstream", "master"])?; cloned_repo.run(&["remote", "add", "other-repo", "file://dummy-file"])?; { let (stdout, stderr) = cloned_repo.branchless_with_options( "submit", &["--create"], &GitRunOptions { expected_exit_code: 1, ..Default::default() }, )?; insta::assert_snapshot!(stderr, @""); insta::assert_snapshot!(stdout, @r###" No upstream repository was associated with branch master and no value was specified for `remote.pushDefault`, so cannot push these branches: foo Configure a value with: git config remote.pushDefault These remotes are available: origin, other-repo "###); } Ok(()) } #[test] fn test_submit_existing_branch() -> eyre::Result<()> { let GitWrapperWithRemoteRepo { temp_dir: _guard, original_repo, cloned_repo, } = make_git_with_remote_repo()?; if original_repo.get_version()? < MIN_VERSION { return Ok(()); } original_repo.init_repo()?; original_repo.commit_file("test1", 1)?; original_repo.commit_file("test2", 2)?; original_repo.clone_repo_into(&cloned_repo, &[])?; cloned_repo.init_repo_with_options(&GitInitOptions { make_initial_commit: false, ..Default::default() })?; original_repo.run(&["checkout", "-b", "feature"])?; original_repo.commit_file("test3", 3)?; cloned_repo.run(&["checkout", "-b", "feature"])?; cloned_repo.commit_file("test4", 4)?; { let (stdout, stderr) = cloned_repo.branchless_with_options( "submit", &["--create"], &GitRunOptions { expected_exit_code: 1, ..Default::default() }, )?; let stderr = redact_remotes(stderr); let stderr = remove_nondeterministic_lines(stderr); insta::assert_snapshot!(stderr, @r###" To: file:// ! [rejected] feature -> feature (fetch first) error: failed to push some refs to 'file://' "###); insta::assert_snapshot!(stdout, @"branchless: running command: push --set-upstream origin feature "); } { cloned_repo.run(&["fetch"])?; let (stdout, _stderr) = cloned_repo.run(&["branch", "--all", "--verbose"])?; insta::assert_snapshot!(stdout, @r###" * feature f57e36f create test4.txt master 96d1c37 create test2.txt remotes/origin/HEAD -> origin/master remotes/origin/feature 70deb1e create test3.txt remotes/origin/master 96d1c37 create test2.txt "###); } Ok(()) } #[test] fn test_submit_up_to_date_branch() -> eyre::Result<()> { let GitWrapperWithRemoteRepo { temp_dir: _guard, original_repo, cloned_repo, } = make_git_with_remote_repo()?; if original_repo.get_version()? < MIN_VERSION { return Ok(()); } { original_repo.init_repo()?; original_repo.commit_file("test1", 1)?; original_repo.commit_file("test2", 2)?; original_repo.clone_repo_into(&cloned_repo, &[])?; cloned_repo.init_repo_with_options(&GitInitOptions { make_initial_commit: false, ..Default::default() })?; } cloned_repo.run(&["checkout", "-b", "feature"])?; cloned_repo.commit_file("test3", 3)?; { let (stdout, stderr) = cloned_repo.run(&["submit", "--create", "feature"])?; let stderr = redact_remotes(stderr); insta::assert_snapshot!(stderr, @r###" branchless: processing 1 update: branch feature To: file:// * [new branch] feature -> feature branchless: processing 1 update: remote branch origin/feature "###); insta::assert_snapshot!(stdout, @r###" branchless: running command: push --set-upstream origin feature branch 'feature' set up to track 'origin/feature'. Submitted 1 commit: feature "###); } cloned_repo.detach_head()?; { let (stdout, stderr) = cloned_repo.run(&["submit", "feature"])?; let stderr = redact_remotes(stderr); insta::assert_snapshot!(stderr, @r###" From: file:// * branch feature -> FETCH_HEAD "###); insta::assert_snapshot!(stdout, @r###" branchless: running command: fetch origin refs/heads/feature Skipped 1 commit (already up-to-date): feature "###); } Ok(()) }