diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index ddfcdad..ed2469a 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -77,7 +77,7 @@ jobs: with: use-cross: ${{ matrix.job.usecross }} command: test - args: --release --target=${{ matrix.job.target }} + args: --target=${{ matrix.job.target }} - name: e2e_test_general run: bash -c "bats test/general" env: diff --git a/.github/workflows/build_test_no_release.yml b/.github/workflows/build_test_no_release.yml index 28dc699..0c3e408 100644 --- a/.github/workflows/build_test_no_release.yml +++ b/.github/workflows/build_test_no_release.yml @@ -57,7 +57,7 @@ jobs: with: use-cross: ${{ matrix.job.usecross }} command: test - args: --release --target=${{ matrix.job.target }} + args: --target=${{ matrix.job.target }} - name: e2e_test_general run: bash -c "bats test/general" env: diff --git a/src/check.rs b/src/check.rs index ea39fb1..55b5ac1 100644 --- a/src/check.rs +++ b/src/check.rs @@ -6,6 +6,7 @@ use super::exec_helpers; use super::split::Runner; use super::repo_file::RepoFile; use super::split; +use super::die; use super::commands::REPO_FILE_ARG; use super::commands::LOCAL_ARG; use super::commands::RECURSIVE_ARG; @@ -43,7 +44,7 @@ impl<'a> CheckUpdates for Runner<'a> { let repo = if let Some(ref r) = self.repo { r } else { - panic!("Failed to get repo"); + die!("Failed to get repo"); }; // TODO: probably need to add blob_applies_to_repo_file here? // I think in most cases this isnt necessary, but I should @@ -51,7 +52,7 @@ impl<'a> CheckUpdates for Runner<'a> { let all_upstream_blobs = get_all_blobs_in_branch(upstream_branch); let all_commits_of_current = match git_helpers::get_all_commits_from_ref(repo, current_branch) { Ok(v) => v, - Err(e) => panic!("Failed to get all commits! {}", e), + Err(e) => die!("Failed to get all commits! {}", e), }; // println!("GOT ALL UPSTREAM BLOBS: {}", all_upstream_blobs.len()); // println!("GOT ALL CURRENT COMMITS: {}", all_commits_of_current.len()); @@ -92,7 +93,7 @@ impl<'a> CheckUpdates for Runner<'a> { // println!("Succesfully deleted FETCH_HEAD"); // println!("git prune successful? {}", tf); // }, - // Err(e) => panic!("Failed to delete FETCH_HEAD:\n{}", e), + // Err(e) => die!("Failed to delete FETCH_HEAD:\n{}", e), // }; } @@ -334,7 +335,7 @@ pub fn fetch_branch(remote: &str, branch: &str) { }, }; if let Some(err) = err_msg { - panic!("Error fetching {} {}\n{}", remote, branch, err); + die!("Error fetching {} {}\n{}", remote, branch, err); } } @@ -365,7 +366,7 @@ fn get_local_branch(runner: &Runner) -> String { fn get_remote_branch(runner: &Runner) -> String { let remote_repo = match runner.repo_file.remote_repo { Some(ref s) => s, - None => panic!("repo file missing remote_repo"), + None => die!("repo file missing remote_repo"), }; // check if user provided a --remote let mut remote_branch = match runner.matches.value_of(REMOTE_BRANCH_ARG[0]) { @@ -437,7 +438,7 @@ pub fn run_check(matches: &ArgMatches) { let repo_files = get_all_repo_files(repo_file_path, should_recurse, any_extension); files_to_check = match repo_files { Ok(files) => files, - Err(e) => panic!("Failed to read repo file directory: {}", e), + Err(e) => die!("Failed to read repo file directory: {}", e), }; } diff --git a/src/commands.rs b/src/commands.rs index 825325a..464da38 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -43,8 +43,8 @@ const CHECK_CMD_DESCRIPTION: &'static str = "check if remote has commits not pre const REPO_FILE_DESCRIPTION: &'static str = "path to file that contains instructions of how to split a repository"; const REPO_URI_DESCRIPTION: &'static str = "a valid git url of the repository to split in"; const AS_SUBDIR_DESCRIPTION: &'static str = "path relative to root of the local repository that will contain the entire repository being split"; -const REBASE_DESCRIPTION: &'static str = "after generating a branch with rewritten history, rebase that branch such that it can be fast forwarded back into the comparison branch. For split-in, the comparison branch is the branch you started on. For split-out, the comparison branch is the remote branch"; -const TOPBASE_DESCRIPTION: &'static str = "like rebase, but it finds a fork point to only take the top commits from the created branch that dont exist in your starting branch"; +const REBASE_DESCRIPTION: &'static str = "after generating a branch with rewritten history, rebase that branch such that it can be fast forwarded back into the comparison branch. For split-in, the comparison branch is the branch you started on. For split-out, the comparison branch is the remote branch. By specifying a value for , you can use a specific remote branch and override what is in your repo file."; +const TOPBASE_DESCRIPTION: &'static str = "like rebase, but it finds a fork point to only take the top commits from the created branch that dont exist in your starting branch. Optionally pass in the name of a remote branch to override what is in your repo file."; const TOPBASE_TOP_DESCRIPTION: &'static str = "the branch that will be rebased. defaults to current branch"; const TOPBASE_BASE_DESCRIPTION: &'static str = "the branch to rebase onto."; const LOCAL_ARG_DESCRIPTION: &'static str = "check if the local branch has commits not present in remote"; @@ -132,15 +132,19 @@ fn base_command<'a, 'b>(cmd: CommandName) -> App<'a, 'b> { Arg::with_name(REBASE_ARG[0]) .long(REBASE_ARG[0]) .short(REBASE_ARG[1]) + .takes_value(true) + .default_value("") + .hide_default_value(true) .help(REBASE_DESCRIPTION) - .conflicts_with(TOPBASE_ARG[0]) ) .arg( Arg::with_name(TOPBASE_ARG[0]) .long(TOPBASE_ARG[0]) .short(TOPBASE_ARG[1]) + .takes_value(true) + .default_value("") + .hide_default_value(true) .help(TOPBASE_DESCRIPTION) - .conflicts_with(REBASE_ARG[0]) ) .arg( Arg::with_name(OUTPUT_BRANCH_ARG[0]) @@ -180,15 +184,18 @@ pub fn split_in_as<'a, 'b>() -> App<'a, 'b> { .long(REBASE_ARG[0]) .short(REBASE_ARG[1]) .help(REBASE_DESCRIPTION) - .conflicts_with(TOPBASE_ARG[0]) + .takes_value(true) + .default_value("") ) // TODO: should remove topbase from split-in-as? i dont think it makes sense .arg( Arg::with_name(TOPBASE_ARG[0]) .long(TOPBASE_ARG[0]) .short(TOPBASE_ARG[1]) + .takes_value(true) + .default_value("") .help(TOPBASE_DESCRIPTION) - .conflicts_with(REBASE_ARG[0]) + .hide_default_value(true) ) .arg( Arg::with_name(AS_SUBDIR_ARG) @@ -197,6 +204,7 @@ pub fn split_in_as<'a, 'b>() -> App<'a, 'b> { .value_name(AS_SUBDIR_ARG_NAME) .required(true) .takes_value(true) + .hide_default_value(true) ) .arg( Arg::with_name(DRY_RUN_ARG[0]) diff --git a/src/git_helpers.rs b/src/git_helpers.rs index a37f96c..202707f 100644 --- a/src/git_helpers.rs +++ b/src/git_helpers.rs @@ -4,6 +4,7 @@ use std::path::PathBuf; use std::path::Path; use std::fs; use std::str::from_utf8; +use super::die; pub trait Short { fn short(self) -> String; @@ -34,7 +35,7 @@ fn remove_git_from_path_buf(pathbuf: &mut PathBuf) -> PathBuf { pub fn get_repository_and_root_directory(dir: &PathBuf) -> (Repository, PathBuf) { let repo = match Repository::discover(dir) { - Err(e) => panic!("Failed to find or open repository from {} - {}", dir.display(), e), + Err(e) => die!("Failed to find or open repository from {} - {}", dir.display(), e), Ok(repo) => repo, }; @@ -177,7 +178,7 @@ pub fn list_everything_under_tree( git2::ObjectType::Blob => { let blob = match t_obj.into_blob() { Ok(b) => b, - _ => panic!("failed to turn into blob"), + _ => die!("failed to turn into blob"), }; println!("{} B:{}", indent, blob.id()); } @@ -186,7 +187,7 @@ pub fn list_everything_under_tree( git2::ObjectType::Tree => { let t_next = match t_obj.into_tree() { Ok(tn) => tn, - _ => panic!("failed to turn into tree"), + _ => die!("failed to turn into tree"), }; let next_indent = format!("{} ", indent); list_everything_under_tree(repo, t_next, next_indent.as_str())?; @@ -369,7 +370,7 @@ pub fn remove_index_and_files( if ! file_removed { let result = fs::remove_file(f); if result.is_err() { - panic!("Failed to remove file {}. Stopping operation without modifying index", f.display()); + die!("Failed to remove file {}. Stopping operation without modifying index", f.display()); } file_removed = true; } else { @@ -444,7 +445,7 @@ pub fn merge<'a>( } } } else { - panic!("cannot fast-forward. Alternate merge strategies not implements yet"); + die!("cannot fast-forward. Alternate merge strategies not implements yet"); } Ok(()) @@ -472,7 +473,7 @@ pub fn pull( repo, &[remote_branch], &mut remote, - ).unwrap(); + )?; merge(repo, fetched_commit, None) } diff --git a/src/main.rs b/src/main.rs index fd2c052..4305120 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,6 +33,35 @@ fn get_cli_input<'a>() -> ArgMatches<'a> { return base_app.get_matches(); } +// in debug mode, use panic so we get a stack trace +#[cfg(debug_assertions)] +#[macro_export] +macro_rules! die { + () => (::std::process::exit(1)); + ($x:expr; $($y:expr),+) => ({ + panic!($($y),+); + }); + ($($y:expr),+) => ({ + panic!($($y),+); + }); +} + +// in release mode, use print so its not ugly +#[cfg(not(debug_assertions))] +#[macro_export] +macro_rules! die { + () => (::std::process::exit(1)); + ($x:expr; $($y:expr),+) => ({ + println!($($y),+); + ::std::process::exit($x) + }); + ($($y:expr),+) => ({ + println!($($y),+); + ::std::process::exit(1) + }); +} + + fn main() { let matches = get_cli_input(); diff --git a/src/repo_file.rs b/src/repo_file.rs index 02bd576..53c6ff2 100644 --- a/src/repo_file.rs +++ b/src/repo_file.rs @@ -1,6 +1,7 @@ use std::path::Path; use std::fs::File; use std::io::{BufRead, BufReader}; +use super::die; #[derive(Debug, PartialEq)] pub struct RepoFile { @@ -245,16 +246,16 @@ fn parse_variable(variable: &mut RepoFileVariable, text: &String, line_num: usiz } if variable.name == VarUnknown { - panic!("Invalid variable name found on line {}:\n\"{}\"", line_num, text); + die!("Invalid variable name found on line {}:\n\"{}\"", line_num, text); } if variable.var_type == TypeUnknown { - panic!("Failed to parse line {}:\n\"{}\"", line_num, text); + die!("Failed to parse line {}:\n\"{}\"", line_num, text); } let strings = get_all_strings(&text); if let None = strings { - panic!("Failed to parse variable at line {}:\n\"{}\"", line_num, text); + die!("Failed to parse variable at line {}:\n\"{}\"", line_num, text); } match variable.var_type { @@ -315,12 +316,12 @@ fn should_parse_line(text: &String) -> bool { pub fn parse_repo_file(filename: &str) -> RepoFile { let repo_file_path = Path::new(filename); if !repo_file_path.exists() { - panic!("Failed to find repo_file: {}", filename); + die!("Failed to find repo_file: {}", filename); } let file = File::open(repo_file_path); if let Err(file_error) = file { - panic!("Failed to open file: {}, {}", filename, file_error); + die!("Failed to open file: {}, {}", filename, file_error); } let file_contents = file.unwrap(); diff --git a/src/split.rs b/src/split.rs index 49353f7..970da7b 100644 --- a/src/split.rs +++ b/src/split.rs @@ -16,6 +16,7 @@ use super::repo_file; use super::repo_file::RepoFile; use super::git_helpers; use super::exec_helpers; +use super::die; pub struct Runner<'a> { pub repo_file_path: Option<&'a str>, @@ -45,8 +46,8 @@ impl<'a> Runner<'a> { pub fn new(matches: &'a ArgMatches) -> Runner<'a> { let is_verbose = matches.is_present(VERBOSE_ARG[0]); let is_dry_run = matches.is_present(DRY_RUN_ARG[0]); - let is_rebase = matches.is_present(REBASE_ARG[0]); - let is_topbase = matches.is_present(TOPBASE_ARG[0]); + let is_rebase = matches.occurrences_of(REBASE_ARG[0]) > 0; + let is_topbase = matches.occurrences_of(TOPBASE_ARG[0]) > 0; let output_branch = matches.value_of(OUTPUT_BRANCH_ARG[0]); let repo_file_path = matches.value_of(REPO_FILE_ARG); Runner { @@ -106,16 +107,16 @@ impl<'a> Runner<'a> { r, ).is_ok(); if ! success { - panic!("Failed to checkout orphan branch"); + die!("Failed to checkout orphan branch"); } // on a new orphan branch our existing files appear in the stage // we need to essentially do "git rm -rf ." let success = git_helpers::remove_index_and_files(r).is_ok(); if ! success { - panic!("Failed to remove git indexed files after making orphan"); + die!("Failed to remove git indexed files after making orphan"); } }, - _ => panic!("Something went horribly wrong!"), + _ => die!("Something went horribly wrong!"), }; if self.verbose { println!("{}created and checked out orphan branch {}", self.log_p, orphan_branch); @@ -135,9 +136,9 @@ impl<'a> Runner<'a> { let output = match exec_helpers::execute(&args) { Ok(o) => match o.status { 0 => o.stdout, - _ => panic!("Failed to run ls-files: {}", o.stderr), + _ => die!("Failed to run ls-files: {}", o.stderr), }, - Err(e) => panic!("Failed to run ls-files: {}", e), + Err(e) => die!("Failed to run ls-files: {}", e), }; if ! output.is_empty() { exit_with_message_and_status( @@ -148,15 +149,45 @@ impl<'a> Runner<'a> { self } + pub fn get_remote_branch_from_args(&self) -> Option<&'a str> { + let topbase_branch_args = self.matches.occurrences_of(TOPBASE_ARG[0]); + let rebase_branch_args = self.matches.occurrences_of(REBASE_ARG[0]); + if topbase_branch_args <= 0 && rebase_branch_args <= 0 { + return None; + } + + let use_arg_str = if topbase_branch_args > 0 { + TOPBASE_ARG[0] + } else { + REBASE_ARG[0] + }; + + match &self.matches.value_of(use_arg_str) { + Some(s) => if *s != "" { + Some(*s) + } else { + None + }, + None => None, + } + } + pub fn populate_empty_branch_with_remote_commits(self) -> Self { let remote_repo = self.repo_file.remote_repo.clone(); let remote_branch: Option<&str> = match &self.repo_file.remote_branch { Some(branch_name) => Some(branch_name.as_str()), None => None, }; + // if user provided a remote_branch name + // on the command line, let that override what + // is present in the repo file: + let remote_branch = match self.get_remote_branch_from_args() { + None => remote_branch, + Some(new_remote_branch) => Some(new_remote_branch), + }; match self.repo { - None => panic!("Failed to find repo?"), + None => die!("Failed to find repo?"), Some(ref r) => { match (self.dry_run, &self.input_branch) { (true, Some(branch_name)) => println!("git merge {}", branch_name), @@ -166,8 +197,16 @@ impl<'a> Runner<'a> { git_helpers::merge_branches(&r, &branch_name[..], None); }, (false, None) => { - println!("{}Pulling from {} {}", self.log_p, remote_repo.clone().unwrap_or("?".into()), remote_branch.clone().unwrap_or("".into())); - git_helpers::pull(&r, &remote_repo.unwrap()[..], remote_branch); + let remote_repo_name = remote_repo.clone().unwrap_or("?".into()); + let remote_branch_name = remote_branch.clone().unwrap_or("".into()); + let remote_string = if remote_branch_name != "" { + format!("{}:{}", remote_repo_name, remote_branch_name) + } else { format!("{}", remote_repo_name) }; + println!("{}Pulling from {}", self.log_p, remote_string); + let res = git_helpers::pull(&r, &remote_repo.unwrap()[..], remote_branch); + if res.is_err() { + die!("Failed to pull remote repo {}", remote_string); + } }, }; }, @@ -234,7 +273,7 @@ impl<'a> Runner<'a> { // save this for later, as well as to find the repository self.current_dir = match env::current_dir() { Ok(pathbuf) => pathbuf, - Err(_) => panic!("Failed to find your current directory. Cannot proceed"), + Err(_) => die!("Failed to find your current directory. Cannot proceed"), }; if self.verbose { println!("{}saving current dir to return to later: {}", self.log_p, self.current_dir.display()); @@ -256,7 +295,7 @@ impl<'a> Runner<'a> { return self; } if ! changed_to_repo_root(&self.repo_root_dir) { - panic!("Failed to change to repository root: {:?}", &self.repo_root_dir); + die!("Failed to change to repository root: {:?}", &self.repo_root_dir); } if self.verbose { println!("{}changed to repository root {}", self.log_p, self.repo_root_dir.display()); @@ -267,10 +306,10 @@ impl<'a> Runner<'a> { // panic if all dependencies are not met pub fn verify_dependencies(self) -> Self { if ! exec_helpers::executed_successfully(&["git", "--version"]) { - panic!("Failed to run. Missing dependency 'git'"); + die!("Failed to run. Missing dependency 'git'"); } if ! exec_helpers::executed_successfully(&["git", "filter-repo", "--version"]) { - panic!("Failed to run. Missing dependency 'git-filter-repo'"); + die!("Failed to run. Missing dependency 'git-filter-repo'"); } self } @@ -290,7 +329,7 @@ impl<'a> Runner<'a> { Err(e) => Some(format!("{}", e)), }; if let Some(err) = err_msg { - panic!("Failed to execute: \"{}\"\n{}", arg_vec.join(" "), err); + die!("Failed to execute: \"{}\"\n{}", arg_vec.join(" "), err); } self @@ -395,7 +434,7 @@ pub fn try_get_repo_name_from_remote_repo(remote_repo: String) -> String { } if repo_name == "" { - panic!("Failed to parse repo_name from remote_repo: {}", remote_repo); + die!("Failed to parse repo_name from remote_repo: {}", remote_repo); } repo_name @@ -436,7 +475,7 @@ pub fn panic_if_array_invalid(var: &Option>, can_be_single: bool, va match var { Some(v) => { if ! include_var_valid(&v, can_be_single) { - panic!("{} is invalid. Must be either a single string, or an even length array of strings", varname); + die!("{} is invalid. Must be either a single string, or an even length array of strings", varname); } }, _ => (), @@ -450,6 +489,16 @@ pub fn changed_to_repo_root(repo_root: &PathBuf) -> bool { } } +pub fn has_both_topbase_and_rebase(matches: &ArgMatches) -> bool { + let rebase_args = matches.occurrences_of(REBASE_ARG[0]); + let topbase_args = matches.occurrences_of(TOPBASE_ARG[0]); + if rebase_args > 0 && topbase_args > 0 { + true + } else { + false + } +} + #[cfg(test)] mod test { diff --git a/src/split_in.rs b/src/split_in.rs index 4bdbb15..882df9e 100644 --- a/src/split_in.rs +++ b/src/split_in.rs @@ -5,10 +5,12 @@ use super::commands::AS_SUBDIR_ARG; use super::commands::REPO_URI_ARG; use super::split::panic_if_array_invalid; use super::split::Runner; +use super::split::has_both_topbase_and_rebase; use super::git_helpers; use super::exec_helpers; use super::split::try_get_repo_name_from_remote_repo; use super::repo_file::RepoFile; +use super::die; use std::convert::From; use std::fs; use std::path::Path; @@ -27,10 +29,10 @@ impl<'a> SplitIn for Runner<'a> { None => None, Some(branch_name) => { match &self.repo { - None => panic!("Failed to find repo for some reason"), + None => die!("Failed to find repo for some reason"), Some(ref repo) => { if ! git_helpers::branch_exists(branch_name, repo) { - panic!("You specified an input branch of {}, but that branch was not found", branch_name); + die!("You specified an input branch of {}, but that branch was not found", branch_name); } Some(branch_name.into()) }, @@ -46,11 +48,11 @@ impl<'a> SplitIn for Runner<'a> { let missing_include = self.repo_file.include.is_none(); if missing_remote_repo && missing_input_branch && ! missing_output_branch { - panic!("Must provide either repo_name in your repofile, or specify a --{} argument", INPUT_BRANCH_ARG); + die!("Must provide either repo_name in your repofile, or specify a --{} argument", INPUT_BRANCH_ARG); } if missing_include && missing_include_as { - panic!("Must provide either include or include_as in your repofile"); + die!("Must provide either include or include_as in your repofile"); } if missing_repo_name && !missing_remote_repo && missing_output_branch { @@ -120,10 +122,10 @@ pub fn path_is_ignored(path: &PathBuf) -> bool { pub fn get_commited_paths() -> Vec { let data = exec_helpers::execute(&["git", "ls-files"]); let data = match data { - Err(e) => panic!("Failed to list git committed files: {}", e), + Err(e) => die!("Failed to list git committed files: {}", e), Ok(d) => { if d.status != 0 { - panic!("Failed to list git committed files: {}", d.stderr); + die!("Failed to list git committed files: {}", d.stderr); } d.stdout }, @@ -289,7 +291,7 @@ pub fn gen_include_as_arg_files_from_folder( let all_files_recursively = get_files_recursively(src.clone(), &mut file_vec, should_ignore); if all_files_recursively.is_err() { - panic!("Error reading dir recursively: {:?}", src); + die!("Error reading dir recursively: {:?}", src); } let mut out_vec = vec![]; @@ -348,6 +350,10 @@ pub fn generate_split_out_arg_exclude(repofile: &RepoFile) -> Vec { } pub fn run_split_in(matches: &ArgMatches) { + if has_both_topbase_and_rebase(matches) { + die!("Cannot use both --topbase and --rebase"); + } + let runner = Runner::new(matches) .get_repo_file() .save_current_dir() @@ -391,6 +397,10 @@ pub fn run_split_in(matches: &ArgMatches) { } pub fn run_split_in_as(matches: &ArgMatches) { + if has_both_topbase_and_rebase(matches) { + die!("Cannot use both --topbase and --rebase"); + } + // should be safe to unwrap because its a required argument let include_as_src = matches.value_of(AS_SUBDIR_ARG).unwrap(); let repo_uri = matches.value_of(REPO_URI_ARG).unwrap(); diff --git a/src/split_out.rs b/src/split_out.rs index c1c808f..2040493 100644 --- a/src/split_out.rs +++ b/src/split_out.rs @@ -3,10 +3,12 @@ use clap::ArgMatches; use super::split::panic_if_array_invalid; use super::split::Runner; use super::split::try_get_repo_name_from_remote_repo; +use super::split::has_both_topbase_and_rebase; use super::repo_file::RepoFile; use super::git_helpers; use super::commands::AS_SUBDIR_ARG; use super::commands::OUTPUT_BRANCH_ARG; +use super::die; pub trait SplitOut { fn validate_repo_file(self) -> Self; @@ -25,11 +27,11 @@ impl<'a> SplitOut for Runner<'a> { let missing_include = self.repo_file.include.is_none(); if missing_remote_repo && missing_repo_name && missing_output_branch { - panic!("Must provide either repo_name or remote_repo in your repofile"); + die!("Must provide either repo_name or remote_repo in your repofile"); } if missing_include && missing_include_as { - panic!("Must provide either include or include_as in your repofile"); + die!("Must provide either include or include_as in your repofile"); } if missing_output_branch && missing_repo_name && !missing_remote_repo { @@ -87,11 +89,11 @@ impl<'a> SplitOut for Runner<'a> { ) { Ok(_) => (), Err(e) => { - panic!("Failed to checkout branch {}", e); + die!("Failed to checkout branch {}", e); } }; }, - _ => panic!("Something went horribly wrong!"), + _ => die!("Something went horribly wrong!"), }; if self.verbose { println!("{} checked out branch {}", self.log_p, output_branch_name); @@ -114,10 +116,10 @@ impl<'a> SplitOut for Runner<'a> { output_branch_name.as_str(), ).is_ok(); if ! success { - panic!("Failed to checkout new branch"); + die!("Failed to checkout new branch"); } }, - _ => panic!("Something went horribly wrong!"), + _ => die!("Something went horribly wrong!"), }; if self.verbose { println!("{}created and checked out new branch {}", self.log_p, output_branch_name); @@ -233,6 +235,10 @@ pub fn generate_split_out_arg_exclude(repofile: &RepoFile) -> Vec { } pub fn run_split_out(matches: &ArgMatches) { + if has_both_topbase_and_rebase(matches) { + die!("Cannot use both --topbase and --rebase"); + } + let runner = Runner::new(matches) .get_repo_file() .verify_dependencies() @@ -288,6 +294,10 @@ pub fn run_split_out(matches: &ArgMatches) { } pub fn run_split_out_as(matches: &ArgMatches) { + if has_both_topbase_and_rebase(matches) { + die!("Cannot use both --topbase and --rebase"); + } + // should be safe to unwrap because its a required argument let include_as_src = matches.value_of(AS_SUBDIR_ARG).unwrap(); let output_branch = matches.value_of(OUTPUT_BRANCH_ARG[0]).unwrap(); diff --git a/src/topbase.rs b/src/topbase.rs index 86b3226..9f72df5 100644 --- a/src/topbase.rs +++ b/src/topbase.rs @@ -9,6 +9,7 @@ use super::commands::TOPBASE_CMD_BASE; use super::commands::TOPBASE_CMD_TOP; use super::commands::VERBOSE_ARG; use super::commands::DRY_RUN_ARG; +use super::die; pub trait Topbase { fn topbase(self) -> Self; @@ -18,7 +19,7 @@ impl<'a> Topbase for Runner<'a> { fn topbase(mut self) -> Self { let repo = match self.repo { Some(ref r) => r, - None => panic!("failed to get repo?"), + None => die!("failed to get repo?"), }; // for split commands, we always use current ref, @@ -48,7 +49,7 @@ impl<'a> Topbase for Runner<'a> { let all_upstream_blobs = get_all_blobs_in_branch(upstream_branch.as_str()); let all_commits_of_current = match git_helpers::get_all_commits_from_ref(repo, current_branch.as_str()) { Ok(v) => v, - Err(e) => panic!("Failed to get all commits! {}", e), + Err(e) => die!("Failed to get all commits! {}", e), }; let num_commits_of_current = all_commits_of_current.len(); @@ -265,9 +266,9 @@ pub fn get_all_blobs_from_commit_with_callback( "--diff-filter=AMCD", "--pretty=oneline" ]; match exec_helpers::execute(&args) { - Err(e) => panic!("Failed to get blobs from commit {} : {}", commit_id, e), + Err(e) => die!("Failed to get blobs from commit {} : {}", commit_id, e), Ok(out) => { - if out.status != 0 { panic!("Failed to get blobs from commit {} : {}", commit_id, out.stderr); } + if out.status != 0 { die!("Failed to get blobs from commit {} : {}", commit_id, out.stderr); } for l in out.stdout.lines() { // lines starting with colons are the lines // that contain blob ids @@ -334,9 +335,9 @@ pub fn get_all_blobs_in_branch(branch_name: &str) -> HashSet { // lives outside the match let mut out_stdout = "".into(); let commit_ids = match exec_helpers::execute(&args) { - Err(e) => panic!("Failed to get all blobs of {} : {}", branch_name, e), + Err(e) => die!("Failed to get all blobs of {} : {}", branch_name, e), Ok(out) => { - if out.status != 0 { panic!("Failed to get all blobs of {} : {}", branch_name, out.stderr); } + if out.status != 0 { die!("Failed to get all blobs of {} : {}", branch_name, out.stderr); } out_stdout = out.stdout; out_stdout.split_whitespace().collect::>() }, diff --git a/test/run_tests.sh b/test/run_tests.sh index e8132ba..833c434 100755 --- a/test/run_tests.sh +++ b/test/run_tests.sh @@ -11,7 +11,7 @@ fi build_program() { - cargo_output=$(cargo build --release 2>&1) + cargo_output=$(cargo build 2>&1) if [[ $? != "0" ]]; then echo "$cargo_output" echo "" @@ -19,8 +19,7 @@ build_program() { echo "Tests will not run" exit 1 fi - # this should output to ./target/release/my-git-tools - PROGRAM_PATH="./target/release/mgt" + PROGRAM_PATH="./target/debug/mgt" PROGRAM_PATH="$(realpath $PROGRAM_PATH)" if [[ ! -f $PROGRAM_PATH ]]; then diff --git a/test/splitin/end-to-end.bats b/test/splitin/end-to-end.bats index a916d3b..a2dad96 100644 --- a/test/splitin/end-to-end.bats +++ b/test/splitin/end-to-end.bats @@ -140,7 +140,7 @@ function teardown() { echo "$output" echo "$(git status)" echo "$(find . -not -path '*/\.*')" - [[ $status == 1 ]] + [[ $status != "0" ]] [[ "$(git branch --show-current)" == "master" ]] [[ "$git_log_before" == "$git_log_after" ]] [[ $output == *"modified changes"* ]] @@ -542,8 +542,8 @@ function teardown() { run $PROGRAM_PATH split-in repo_file.sh --rebase --verbose echo "$output" echo "$(git status)" - [[ $status == "1" ]] - [[ "$output" != "Success!" ]] + [[ $status != "0" ]] + [[ "$output" != *"Success!"* ]] [[ "$(git status)" == *"rebase in progress"* ]] } @@ -572,11 +572,56 @@ function teardown() { run $PROGRAM_PATH split-in repo_file.sh --topbase --verbose echo "$output" echo "$(git status)" - [[ $status == "1" ]] - [[ "$output" != "Success!" ]] + [[ $status != "0" ]] + [[ "$output" != *"Success!"* ]] [[ "$(git status)" == *"rebase in progress"* ]] } +@test 'can specify a branch to topbase from' { + # save current dir to cd back to later + curr_dir="$PWD" + # setup the test remote repo: + cd "$BATS_TMPDIR/test_remote_repo2" + mkdir -p lib + echo "abc" > abc.txt && git add abc.txt && git commit -m "abc" + echo "123" > abc.txt && git add abc.txt && git commit -m "abc-123" + git checkout -b b456 + echo "456" > abc.txt && git add abc.txt && git commit -m "commit_456" + git checkout - + cd "$curr_dir" + + repo_file_contents=" + remote_repo=\"..$SEP$test_remote_repo2\" + include_as=(\"lib/\" \" \") + " + echo "$repo_file_contents" > repo_file.sh + + mkdir -p lib + # this is where it aligns: + echo "abc" > lib/abc.txt && git add lib/abc.txt && git commit -m "abc" + + run $PROGRAM_PATH split-in repo_file.sh --topbase b456 --verbose + echo "$output" + echo "$(git status)" + [[ $status == "0" ]] + [[ "$output" == *"Success!"* ]] + [[ "$(git log --oneline)" == *"commit_456"* ]] +} + +@test 'should not be able to use --topbase with --rebase' { + repo_file_contents=" + remote_repo=\"..$SEP$test_remote_repo2\" + include_as=(\"lib/\" \" \") + " + echo "$repo_file_contents" > repo_file.sh + + run $PROGRAM_PATH split-in repo_file.sh --rebase --topbase --verbose + echo "$output" + [[ $status != "0" ]] + [[ "$output" != *"Success!"* ]] + [[ "$output" == *"Cannot use both"* ]] +} + @test '--topbase should not say success if there were rebase merge conflicts (if take all remote)' { # save current dir to cd back to later curr_dir="$PWD" @@ -603,8 +648,8 @@ function teardown() { run $PROGRAM_PATH split-in repo_file.sh --topbase --verbose echo "$output" echo "$(git status)" - [[ $status == "1" ]] - [[ "$output" != "Success!" ]] + [[ $status != "0" ]] + [[ "$output" != *"Success!"* ]] [[ "$(git status)" == *"rebase in progress"* ]] } diff --git a/test/splitout/end-to-end.bats b/test/splitout/end-to-end.bats index a4c394e..2bc0f2b 100644 --- a/test/splitout/end-to-end.bats +++ b/test/splitout/end-to-end.bats @@ -158,7 +158,7 @@ function teardown() { echo "$output" echo "$(git status)" echo "$(find . -not -path '*/\.*')" - [[ $status == 1 ]] + [[ $status != "0" ]] [[ "$(git branch --show-current)" == "master" ]] [[ "$git_log_before" == "$git_log_after" ]] [[ $output == *"modified changes"* ]] @@ -554,6 +554,165 @@ function teardown() { [[ "$output_log" == *"initial commit for test_remote_repo2"* ]] } +@test 'can rebase onto specific remote branch' { + repo_file_contents=" + remote_repo=\"..$SEP$test_remote_repo2\" + remote_branch=\"specific-branch\" + include=(\"lib/\" \"test_remote_repo.txt\") + " + echo "$repo_file_contents" > repo_file.sh + + # checkout to a specific branch + # so we can test that we can rebase from that specific + # remote branch instead of default of remote HEAD + curr_dir="$PWD" + cd "$BATS_TMPDIR/test_remote_repo2" + git checkout -b specific-branch + echo "a" > a.txt && git add a.txt && git commit -m "a_commit" + git checkout - + cd "$curr_dir" + + mkdir -p lib/ + echo "libfile1.txt" > lib/libfile1.txt + git add lib/libfile1.txt && git commit -m "libfile1" + + run $PROGRAM_PATH split-out repo_file.sh -r --verbose + echo "$output" + echo "$(git branch -v)" + echo -e "\n$(git branch --show-current):" + echo "$(git log --oneline)" + [[ $status == "0" ]] + [[ "$(git branch --show-current)" == "test_remote_repo2" ]] + output_log="$(git log --oneline)" + output_commits="$(git log --oneline | wc -l)" + echo "" + + # we test that the number of commits is now the number that we made in our local + # repo (2: the original, and the libfile) plus the initial commit of test_remote_repo2 + # and plus one (a) that we made on specific-branch, for total of 4 + [[ "$output_commits" == "4" ]] + [[ "$output_log" == *"a_commit"* ]] +} + +@test 'can rebase onto specific remote branch via cli arg' { + # here we also test that passing the cli arg will override + # whats defined in the repo file + repo_file_contents=" + remote_repo=\"..$SEP$test_remote_repo2\" + remote_branch=\"specific-branch\" + include=(\"lib/\" \"test_remote_repo.txt\") + " + echo "$repo_file_contents" > repo_file.sh + + # checkout to a specific branch + # so we can test that we can rebase from that specific + # remote branch instead of default of remote HEAD + curr_dir="$PWD" + cd "$BATS_TMPDIR/test_remote_repo2" + git checkout -b specific-branch + echo "a" > a.txt && git add a.txt && git commit -m "a_commit" + # then we checkout to sb2 which is + # what we will use in the cli args, to test + # that mgt uses sb2 instead of specific-branch + git checkout - + git checkout -b sb2 + echo "b" > b.txt && git add b.txt && git commit -m "b_commit" + git checkout - + cd "$curr_dir" + + mkdir -p lib/ + echo "libfile1.txt" > lib/libfile1.txt + git add lib/libfile1.txt && git commit -m "libfile1" + + run $PROGRAM_PATH split-out repo_file.sh --rebase sb2 --verbose + echo "$output" + echo "$(git branch -v)" + echo -e "\n$(git branch --show-current):" + echo "$(git log --oneline)" + [[ $status == "0" ]] + [[ "$(git branch --show-current)" == "test_remote_repo2" ]] + output_log="$(git log --oneline)" + output_commits="$(git log --oneline | wc -l)" + echo "" + + # we test that the number of commits is now the number that we made in our local + # repo (2: the original, and the libfile) plus the initial commit of test_remote_repo2 + # and plus one (b) that we made on specific-branch, for total of 4 + # it should not have (a) because a was made on a different + # branch than the one we want + [[ "$output_commits" == "4" ]] + [[ "$output_log" == *"b_commit"* ]] + [[ "$output_log" != *"a_commit"* ]] +} + +@test 'can topbase onto specific remote branch via cli arg' { + # here we also test that passing the cli arg will override + # whats defined in the repo file + repo_file_contents=" + remote_repo=\"..$SEP$test_remote_repo2\" + remote_branch=\"specific-branch\" + include=(\"lib/\" \"test_remote_repo.txt\") + " + echo "$repo_file_contents" > repo_file.sh + + # checkout to a specific branch + # so we can test that we can rebase from that specific + # remote branch instead of default of remote HEAD + curr_dir="$PWD" + cd "$BATS_TMPDIR/test_remote_repo2" + git checkout -b specific-branch + echo "a" > a.txt && git add a.txt && git commit -m "a_commit" + # then we checkout to sb2 which is + # what we will use in the cli args, to test + # that mgt uses sb2 instead of specific-branch + git checkout - + git checkout -b sb2 + echo "b" > b.txt && git add b.txt && git commit -m "b_commit" + git checkout - + cd "$curr_dir" + + mkdir -p lib/ + echo "libfile1.txt" > lib/libfile1.txt + git add lib/libfile1.txt && git commit -m "libfile1" + + run $PROGRAM_PATH split-out repo_file.sh --topbase sb2 --verbose + echo "$output" + echo "$(git branch -v)" + echo -e "\n$(git branch --show-current):" + echo "$(git log --oneline)" + [[ $status == "0" ]] + [[ "$(git branch --show-current)" == "test_remote_repo2" ]] + output_log="$(git log --oneline)" + output_commits="$(git log --oneline | wc -l)" + echo "" + + # we test that the number of commits is now the number that we made in our local + # repo (2: the original, and the libfile) plus the initial commit of test_remote_repo2 + # and plus one (b) that we made on specific-branch, for total of 4 + # it should not have (a) because a was made on a different + # branch than the one we want + [[ "$output_commits" == "4" ]] + [[ "$output_log" == *"b_commit"* ]] + [[ "$output_log" != *"a_commit"* ]] +} + +@test 'gives proper error if failed to find remote_branch' { + repo_file_contents=" + remote_repo=\"..$SEP$test_remote_repo2\" + remote_branch=\"specific-branch\" + include=(\"lib/\" \"test_remote_repo.txt\") + " + echo "$repo_file_contents" > repo_file.sh + + # in this test, we dont make a specific-branch + # so the fetch for specific-branch should fail, + # and we should detect that + run $PROGRAM_PATH split-out repo_file.sh -r --verbose + echo "$output" + [[ $status != "0" ]] + [[ $output == *"Failed to pull remote repo"* ]] +} + @test 'rebasing new branch onto original should not leave temporary branch' { repo_file_contents=" remote_repo=\"..$SEP$test_remote_repo2\" @@ -651,8 +810,8 @@ function teardown() { run $PROGRAM_PATH split-out repo_file.sh --topbase --verbose echo "$output" echo "$(git status)" - [[ $output != "Success" ]] - [[ $status == "1" ]] + [[ $output != *"Success"* ]] + [[ $status != "0" ]] [[ "$(git status)" == *"rebase in progress"* ]] } @@ -677,8 +836,8 @@ function teardown() { run $PROGRAM_PATH split-out repo_file.sh --topbase --verbose echo "$output" echo "$(git status)" - [[ $output != "Success" ]] - [[ $status == "1" ]] + [[ $output != *"Success"* ]] + [[ $status != "0" ]] [[ "$(git status)" == *"rebase in progress"* ]] } @@ -749,7 +908,7 @@ function teardown() { run $PROGRAM_PATH split-out repo_file.sh --rebase --verbose echo "$output" echo "$(git status)" - [[ $output != "Success" ]] - [[ $status == "1" ]] + [[ $output != *"Success"* ]] + [[ $status != "0" ]] [[ "$(git status)" == *"rebase in progress"* ]] } diff --git a/test/test.yml b/test/test.yml index 38766a5..bb1029f 100644 --- a/test/test.yml +++ b/test/test.yml @@ -19,9 +19,9 @@ run_e2e_test: series: - name: build series: - - run: cargo build --release 2>&1 + - run: cargo build name: cargo build - - run: realpath ./target/release/mgt + - run: realpath ./target/debug/mgt capture_stdout: program_path name: cargo build cleanup