mod cli_test_utils;
use anyhow::Result;
use cli_test_utils::*;
use std::os::unix::fs::MetadataExt;
const REPO_NAME: &str = "my-repository";
const REPO_NAME_2: &str = "my-other-repository";
const DEFAULT_DESCRIPTION: &str =
"Unnamed repository; edit this file 'description' to name the repository.";
#[test]
fn can_init_a_new_git_repo() -> Result<()> {
let mut c = TestContext::new_interactive()?;
c.p.send_line(&format!("init {}", REPO_NAME))?;
c.expect_successful_init_message(&personal_repo_path(REPO_NAME))?;
let repo_dir = c.personal_repo_dir(REPO_NAME);
verify_repo_exists(&repo_dir);
verify_current_branch(&repo_dir, "refs/heads/main");
verify_repo_config_value(&repo_dir, "core.sharedrepository", None);
Ok(())
}
#[test]
fn can_init_a_new_shared_git_repo() -> Result<()> {
let mut c = TestContext::new_interactive()?;
let group = arbitrary_user_group();
c.p.send_line(&format!("init --group {} {}", group, REPO_NAME))?;
c.expect_successful_init_message(&group_repo_path(&group, REPO_NAME))?;
let repo_dir = c.group_repo_dir(&group, REPO_NAME);
verify_repo_exists(&repo_dir);
verify_repo_config_value(&repo_dir, "core.sharedrepository", Some("1"));
let expected_gid = nix::unistd::Group::from_name(&group)
.unwrap()
.unwrap()
.gid
.as_raw();
let group_dir = repo_dir.parent().unwrap();
let group_dir_metadata = group_dir.metadata().unwrap();
assert_eq!(group_dir_metadata.gid(), expected_gid);
assert_eq!(
group_dir_metadata.mode(),
0o42770,
"Mode is {:o}",
group_dir_metadata.mode()
);
assert_eq!(repo_dir.metadata().unwrap().gid(), expected_gid);
Ok(())
}
#[test]
fn does_not_init_shared_repo_if_the_user_isnt_in_the_group() -> Result<()> {
let mut c = TestContext::new_interactive()?;
let group = "not-a-real-group";
c.p.send_line(&format!("init --group {} {}", group, REPO_NAME))?;
c.p.exp_string("Unknown group")?;
Ok(())
}
#[test]
fn list_can_print_an_empty_list() -> Result<()> {
let mut c = TestContext::new_interactive()?;
c.expect_list_table(&[])?;
Ok(())
}
#[test]
fn list_can_print_a_list_of_personal_repos_with_descriptions() -> Result<()> {
let mut c = TestContext::new_interactive()?;
c.p.send_line(&format!("init {}", REPO_NAME))?;
c.expect_successful_init_message(&personal_repo_path(REPO_NAME))?;
c.expect_list_table(&[(&personal_repo_path(REPO_NAME), DEFAULT_DESCRIPTION)])?;
Ok(())
}
#[test]
fn list_can_print_a_list_of_all_repos_with_descriptions() -> Result<()> {
let mut c = TestContext::new_interactive()?;
c.p.send_line(&format!("init {}", REPO_NAME))?;
c.expect_successful_init_message(&personal_repo_path(REPO_NAME))?;
let group = arbitrary_user_group();
c.p.send_line(&format!("init --group {} {}", group, REPO_NAME_2))?;
c.expect_successful_init_message(&group_repo_path(&group, REPO_NAME_2))?;
c.expect_list_table(&[
(&personal_repo_path(REPO_NAME), DEFAULT_DESCRIPTION),
(&group_repo_path(&group, REPO_NAME_2), DEFAULT_DESCRIPTION),
])?;
Ok(())
}
#[test]
fn list_can_print_a_verbose_list_of_all_repos() -> Result<()> {
let mut c = TestContext::new_interactive()?;
c.p.send_line(&format!("init {}", REPO_NAME))?;
c.expect_successful_init_message(&personal_repo_path(REPO_NAME))?;
let group = arbitrary_user_group();
c.p.send_line(&format!("init --group {} {}", group, REPO_NAME_2))?;
c.expect_successful_init_message(&group_repo_path(&group, REPO_NAME_2))?;
c.expect_list_table_verbose(&[
(&personal_repo_path(REPO_NAME), DEFAULT_DESCRIPTION),
(&group_repo_path(&group, REPO_NAME_2), DEFAULT_DESCRIPTION),
])?;
Ok(())
}
#[test]
fn can_set_the_description_on_a_repo_during_init() -> Result<()> {
let mut c = TestContext::new_interactive()?;
let description = "A cool repo that does cool things";
c.p.send_line(&format!("init --description \"{description}\" {REPO_NAME}"))?;
c.expect_successful_init_message(&personal_repo_path(REPO_NAME))?;
c.expect_list_table(&[(&personal_repo_path(REPO_NAME), description)])?;
Ok(())
}
#[test]
fn can_change_the_description_on_a_repo() -> Result<()> {
let mut c = TestContext::new_interactive()?;
let description = "A cool repo that does cool things";
let repo_path = personal_repo_path(REPO_NAME);
c.p.send_line(&format!("init {REPO_NAME}"))?;
c.p.exp_string(&format!("Successfully created \"{repo_path}\"",))?;
c.p.send_line(&format!(
"set-description \"{repo_path}\" \"{description}\""
))?;
c.p.exp_string("Successfully updated description")?;
c.expect_prompt()?;
c.expect_list_table(&[(&repo_path, description)])?;
Ok(())
}
#[test]
fn can_set_the_main_branch_of_a_new_git_repo() -> Result<()> {
let mut c = TestContext::new_interactive()?;
let main_branch = "foobar";
c.p.send_line(&format!("init --branch {} {}", main_branch, REPO_NAME))?;
c.expect_successful_init_message(&personal_repo_path(REPO_NAME))?;
let repo_dir = c.personal_repo_dir(REPO_NAME);
verify_current_branch(&repo_dir, &format!("refs/heads/{main_branch}"));
Ok(())
}
#[test]
fn can_change_the_main_branch_on_a_repo() -> Result<()> {
let mut c = TestContext::new_interactive()?;
let main_branch = "foobar";
let repo_path = personal_repo_path(REPO_NAME);
c.p.send_line(&format!("init {}", REPO_NAME))?;
c.expect_successful_init_message(&repo_path)?;
c.p.send_line(&format!("set-branch \"{repo_path}\" \"{main_branch}\""))?;
c.p.exp_string("Successfully updated branch")?;
let repo_dir = c.personal_repo_dir(REPO_NAME);
verify_current_branch(&repo_dir, &format!("refs/heads/{main_branch}"));
Ok(())
}
#[test]
fn can_delete_a_repo() -> Result<()> {
let mut c = TestContext::new_interactive()?;
let repo_path = personal_repo_path(REPO_NAME);
let repo_dir = c.personal_repo_dir(REPO_NAME);
c.p.send_line(&format!("init {}", REPO_NAME))?;
c.expect_successful_init_message(&repo_path)?;
verify_repo_exists(&repo_dir);
c.p.send_line(&format!("delete \"{repo_path}\""))?;
c.p.exp_string(&format!(
"Are you sure you want to delete \"{repo_path}\"? (yes/no)"
))?;
c.p.send_line("yes")?;
c.p.exp_string(&format!("Successfully deleted \"{repo_path}\""))?;
verify_repo_does_not_exist(&repo_dir);
Ok(())
}
#[test]
fn repo_is_not_deleted_if_you_say_youre_not_sure() -> Result<()> {
let mut c = TestContext::new_interactive()?;
let repo_path = personal_repo_path(REPO_NAME);
let repo_dir = c.personal_repo_dir(REPO_NAME);
c.p.send_line(&format!("init {}", REPO_NAME))?;
c.expect_successful_init_message(&repo_path)?;
verify_repo_exists(&repo_dir);
c.p.send_line(&format!("delete \"{repo_path}\""))?;
c.p.exp_string(&format!(
"Are you sure you want to delete \"{repo_path}\"? (yes/no)"
))?;
c.p.send_line("no")?;
c.p.exp_string(&format!("Action cancelled"))?;
verify_repo_exists(&repo_dir);
Ok(())
}
#[test]
fn git_housekeeping_repacks_objects() -> Result<()> {
let mut c = TestContext::new_interactive()?;
let repo_path = personal_repo_path(REPO_NAME);
let repo_dir = c.personal_repo_dir(REPO_NAME);
c.p.send_line(&format!("init {}", REPO_NAME))?;
c.expect_successful_init_message(&repo_path)?;
let checkout_dir = create_clone(&c, &repo_dir, REPO_NAME);
create_commit(&checkout_dir)?;
push(&checkout_dir, "main");
let packs_dir = repo_dir.join("objects").join("pack");
assert_eq!(packs_dir.read_dir()?.count(), 0);
c.p.send_line(&format!("housekeeping {repo_path}"))?;
c.p.exp_string(&format!("Successfully did housekeeping on \"{repo_path}\""))?;
assert!(packs_dir.read_dir()?.count() > 0);
Ok(())
}
#[test]
fn git_housekeeping_cleans_out_stale_refs() -> Result<()> {
let mut c = TestContext::new_interactive()?;
let repo_path = personal_repo_path(REPO_NAME);
let repo_dir = c.personal_repo_dir(REPO_NAME);
c.p.send_line(&format!("init {}", REPO_NAME))?;
c.expect_successful_init_message(&repo_path)?;
let checkout_dir = create_clone(&c, &repo_dir, REPO_NAME);
let commit_hash = create_commit(&checkout_dir)?;
push(&checkout_dir, "main:temporary-branch");
push(&checkout_dir, ":temporary-branch");
verify_commit_exists(&repo_dir, &commit_hash);
c.p.send_line(&format!("housekeeping"))?;
c.p.exp_string(&format!("Successfully did housekeeping on \"{repo_path}\""))?;
verify_commit_does_not_exist(&repo_dir, &commit_hash);
Ok(())
}
#[test]
fn can_mirror_an_existing_git_repo() -> Result<()> {
let mut c = TestContext::new_interactive()?;
c.p.send_line(&format!("init {}", REPO_NAME))?;
c.expect_successful_init_message(&personal_repo_path(REPO_NAME))?;
let upstream_repo_dir = c.personal_repo_dir(REPO_NAME);
let upstream_checkout_dir = create_clone(&c, &upstream_repo_dir, REPO_NAME);
let commit_hash = create_commit(&upstream_checkout_dir)?;
push(&upstream_checkout_dir, "main");
c.p.send_line(&format!(
"init --mirror {} {}",
upstream_repo_dir.display(),
REPO_NAME_2
))?;
c.expect_successful_init_message(&personal_repo_path(REPO_NAME_2))?;
let repo_dir = c.personal_repo_dir(REPO_NAME_2);
verify_repo_exists(&repo_dir);
verify_current_branch(&repo_dir, "refs/heads/main");
verify_commit_exists(&repo_dir, &commit_hash);
verify_repo_config_value(&repo_dir, "core.sharedrepository", None);
verify_repo_config_value(&repo_dir, "remote.origin.mirror", Some("true"));
c.expect_list_table(&[
(
&personal_repo_path(REPO_NAME_2),
&format!("Mirror of {}", upstream_repo_dir.display()),
),
(&personal_repo_path(REPO_NAME), DEFAULT_DESCRIPTION),
])?;
Ok(())
}
#[test]
fn can_mirror_an_existing_git_repo_with_a_custom_description() -> Result<()> {
let mut c = TestContext::new_interactive()?;
c.p.send_line(&format!("init {}", REPO_NAME))?;
c.expect_successful_init_message(&personal_repo_path(REPO_NAME))?;
let upstream_repo_dir = c.personal_repo_dir(REPO_NAME);
let upstream_checkout_dir = create_clone(&c, &upstream_repo_dir, REPO_NAME);
let commit_hash = create_commit(&upstream_checkout_dir)?;
push(&upstream_checkout_dir, "main");
let custom_description = "Does all the things";
c.p.send_line(&format!(
"init --mirror {} --description \"{}\" {}",
upstream_repo_dir.display(),
custom_description,
REPO_NAME_2
))?;
c.expect_successful_init_message(&personal_repo_path(REPO_NAME_2))?;
let repo_dir = c.personal_repo_dir(REPO_NAME_2);
verify_repo_exists(&repo_dir);
verify_current_branch(&repo_dir, "refs/heads/main");
verify_commit_exists(&repo_dir, &commit_hash);
verify_repo_config_value(&repo_dir, "core.sharedrepository", None);
verify_repo_config_value(&repo_dir, "remote.origin.mirror", Some("true"));
c.expect_list_table(&[
(&personal_repo_path(REPO_NAME_2), &custom_description),
(&personal_repo_path(REPO_NAME), DEFAULT_DESCRIPTION),
])?;
Ok(())
}