use super::*;
use crate::test_support::isolated_overlay;
use crate::transaction_git::GitContext;
fn context_with_aliases(pairs: &[(&str, &str)]) -> GitContext {
let mut overlay: Vec<(OsString, OsString)> = isolated_overlay();
overlay.push((OsString::from("GIT_CONFIG_COUNT"), OsString::from(pairs.len().to_string())));
for (index, (name, value)) in pairs.iter().enumerate() {
overlay.push((
OsString::from(format!("GIT_CONFIG_KEY_{index}")),
OsString::from(format!("alias.{name}")),
));
overlay.push((
OsString::from(format!("GIT_CONFIG_VALUE_{index}")),
OsString::from(*value),
));
}
return GitContext {
real_git: std::path::PathBuf::from(crate::test_support::REAL_GIT),
overlay,
global_prefix: Vec::new(),
};
}
fn args(words: &[&str]) -> Vec<OsString> {
return words.iter().map(OsString::from).collect();
}
fn word_bytes(word: &&str) -> Vec<u8> {
return word.as_bytes().to_vec();
}
fn words(list: &[&str]) -> Option<Vec<Vec<u8>>> {
return Some(list.iter().map(word_bytes).collect());
}
#[test]
fn alias_values_split_as_git_splits_them() {
assert_eq!(split_alias_command(b"commit -a"), words(&["commit", "-a"]));
assert_eq!(split_alias_command(b"a \t b"), words(&["a", "b"]));
assert_eq!(split_alias_command(b" a"), words(&["", "a"]));
assert_eq!(split_alias_command(b"'a b' c"), words(&["a b", "c"]));
assert_eq!(split_alias_command(b"\"x y\""), words(&["x y"]));
assert_eq!(split_alias_command(b"a\\ b"), words(&["a b"]));
assert_eq!(split_alias_command(b"'a\\b'"), words(&["a\\b"]));
assert_eq!(split_alias_command(b"\"a\\\"b\""), words(&["a\"b"]));
assert_eq!(split_alias_command(b"''"), words(&[""]));
}
#[test]
fn empty_and_unfinished_values_split_to_one_empty_word_or_fail() {
assert_eq!(split_alias_command(b""), words(&[""]));
assert_eq!(split_alias_command(b" "), words(&[""]));
assert_eq!(split_alias_command(b"'a"), None);
assert_eq!(split_alias_command(b"\"a"), None);
assert_eq!(split_alias_command(b"a\\"), None);
}
#[test]
fn alias_values_are_trimmed_before_splitting() {
assert_eq!(trim_alias_value(b" commit \n"), b"commit".to_vec());
assert_eq!(trim_alias_value(b" \t "), Vec::<u8>::new());
assert_eq!(trim_alias_value(&[0xff, b' ', b'x', b' ']), vec![0xff, b' ', b'x']);
assert_eq!(trim_alias_value(&[b' ', 0xff]), vec![0xff]);
}
#[test]
fn a_direct_builtin_resolves_past_its_global_options() {
let context: GitContext = context_with_aliases(&[]);
let resolved: ResolvedCommand = resolve_forwarded_command(&context, &args(&["-C", "/x", "status"]));
assert_eq!(resolved.route, Route::Direct);
assert_eq!(resolved.word(), b"status");
assert_eq!(resolved.subcommand_index, 2);
assert!(resolved.is_builtin());
}
#[test]
fn a_query_has_no_subcommand() {
let context: GitContext = context_with_aliases(&[]);
let resolved: ResolvedCommand = resolve_forwarded_command(&context, &args(&["--version"]));
assert_eq!(resolved.subcommand, None);
assert_eq!(resolved.word(), b"");
assert!(!resolved.is_builtin());
}
#[test]
fn an_alias_expands_to_its_words_then_the_rest() {
let context: GitContext = context_with_aliases(&[("fixturecommit", "commit -v")]);
let resolved: ResolvedCommand =
resolve_forwarded_command(&context, &args(&["fixturecommit", "-m", "x"]));
assert_eq!(resolved.route, Route::Alias);
assert_eq!(resolved.word(), b"commit");
assert_eq!(resolved.arguments, args(&["commit", "-v", "-m", "x"]));
assert!(resolved.is_builtin());
}
#[test]
fn chained_aliases_resolve_to_the_builtin_at_the_end() {
let context: GitContext = context_with_aliases(&[("fixturex", "fixturey -a"), ("fixturey", "commit")]);
let resolved: ResolvedCommand = resolve_forwarded_command(&context, &args(&["fixturex"]));
assert_eq!(resolved.route, Route::Alias);
assert_eq!(resolved.arguments, args(&["commit", "-a"]));
}
#[test]
fn a_builtin_name_is_not_shadowed_by_an_alias() {
let context: GitContext = context_with_aliases(&[("commit", "status")]);
let resolved: ResolvedCommand = resolve_forwarded_command(&context, &args(&["commit"]));
assert_eq!(resolved.route, Route::Direct);
assert_eq!(resolved.word(), b"commit");
}
#[test]
fn a_shell_alias_is_never_a_builtin() {
let context: GitContext = context_with_aliases(&[("fixtureshell", "!git commit -a")]);
let resolved: ResolvedCommand = resolve_forwarded_command(&context, &args(&["fixtureshell"]));
assert_eq!(resolved.route, Route::ShellAlias);
assert!(!resolved.is_builtin());
assert!(!creates_or_moves_worktrees(&resolved));
}
#[test]
fn an_alias_loop_is_unresolved() {
let context: GitContext = context_with_aliases(&[("fixtureloopa", "fixtureloopb"), ("fixtureloopb", "fixtureloopa")]);
let resolved: ResolvedCommand = resolve_forwarded_command(&context, &args(&["fixtureloopa"]));
assert_eq!(resolved.route, Route::Unresolved);
assert!(!resolved.is_builtin());
}
#[test]
fn an_unknown_name_is_unresolved() {
let context: GitContext = context_with_aliases(&[]);
let resolved: ResolvedCommand = resolve_forwarded_command(&context, &args(&["fixture-no-such-command"]));
assert_eq!(resolved.route, Route::Unresolved);
assert!(!resolved.is_builtin());
}
#[test]
fn a_malformed_alias_value_is_unresolved() {
let context: GitContext = context_with_aliases(&[("fixturebad", "'commit")]);
let resolved: ResolvedCommand = resolve_forwarded_command(&context, &args(&["fixturebad"]));
assert_eq!(resolved.route, Route::Unresolved);
}
#[test]
fn an_empty_alias_value_is_unresolved() {
let context: GitContext = context_with_aliases(&[("fixtureempty", " ")]);
let resolved: ResolvedCommand = resolve_forwarded_command(&context, &args(&["fixtureempty"]));
assert_eq!(resolved.route, Route::Unresolved);
}
fn chain_context(length: usize) -> (GitContext, OsString) {
let mut names: Vec<String> = Vec::new();
for index in 0..length {
names.push(format!("fixturechain{index}"));
}
let mut pairs: Vec<(String, String)> = Vec::new();
for index in 0..length {
let target: String = if index + 1 < length {
names[index + 1].clone()
} else {
String::from("commit")
};
pairs.push((names[index].clone(), target));
}
let mut pair_refs: Vec<(&str, &str)> = Vec::new();
for (name, target) in &pairs {
pair_refs.push((name.as_str(), target.as_str()));
}
return (context_with_aliases(pair_refs.as_slice()), OsString::from(names[0].as_str()));
}
#[test]
fn sixteen_chained_aliases_reach_the_builtin() {
assert_eq!(MAX_ALIAS_EXPANSIONS, 16);
let (context, first): (GitContext, OsString) = chain_context(16);
let resolved: ResolvedCommand = resolve_forwarded_command(&context, &[first]);
assert_eq!(resolved.route, Route::Alias);
assert_eq!(resolved.word(), b"commit");
}
#[test]
fn seventeen_chained_aliases_are_unresolved() {
let (context, first): (GitContext, OsString) = chain_context(17);
let resolved: ResolvedCommand = resolve_forwarded_command(&context, &[first]);
assert_eq!(resolved.route, Route::Unresolved);
assert!(!resolved.is_builtin());
}
#[test]
fn only_worktree_add_and_move_create_or_move_worktrees() {
let context: GitContext = context_with_aliases(&[]);
let add: ResolvedCommand = resolve_forwarded_command(&context, &args(&["worktree", "add", "x"]));
let moved: ResolvedCommand = resolve_forwarded_command(&context, &args(&["worktree", "move", "a", "b"]));
let verbose_add: ResolvedCommand =
resolve_forwarded_command(&context, &args(&["worktree", "--verbose", "add", "x"]));
let list: ResolvedCommand = resolve_forwarded_command(&context, &args(&["worktree", "list"]));
let bare: ResolvedCommand = resolve_forwarded_command(&context, &args(&["worktree"]));
let other: ResolvedCommand = resolve_forwarded_command(&context, &args(&["status", "add"]));
assert!(creates_or_moves_worktrees(&add));
assert!(creates_or_moves_worktrees(&moved));
assert!(creates_or_moves_worktrees(&verbose_add));
assert!(!creates_or_moves_worktrees(&list));
assert!(!creates_or_moves_worktrees(&bare));
assert!(!creates_or_moves_worktrees(&other));
}