mirror of
https://github.com/facebook/sapling.git
synced 2025-01-07 14:10:42 +03:00
cliparser: support shell alias
Summary: Alias starting with `!` are considered shell commands. The entire command string should be passed roughly as-is to the shell. The current alias handling uses shlex::split to split the alias into arguments, then replaces things like `$1` in arguments. The problem is, escaping shlex::split a complex shell alias, then unesape (shlex::quote) it is not loseless. To maintain compatibility for existing complex shell alias configuration, add a new code path that imitates the Python code behavior. Reviewed By: sfilipco Differential Revision: D16814144 fbshipit-source-id: 0e5e95f99bf8b8b16bd8d0cbcadd6760f7f77ebb
This commit is contained in:
parent
399556389a
commit
7b96c8d292
@ -32,6 +32,7 @@ mutationstore = { path = "../../../../lib/mutationstore" }
|
||||
nodemap = { path = "../../../../lib/nodemap" }
|
||||
pathmatcher = { path = "../../../../lib/pathmatcher" }
|
||||
revisionstore = { path = "../../../../lib/revisionstore" }
|
||||
shlex = "0.1"
|
||||
stackdesc = { path = "../../../../lib/stackdesc/" }
|
||||
treestate = { path = "../../../../lib/treestate" }
|
||||
types = { path = "../../../../lib/types" }
|
||||
|
@ -111,6 +111,7 @@ fn parse_command(
|
||||
|
||||
Ok((arguments, options))
|
||||
}
|
||||
|
||||
fn expand_args(
|
||||
py: Python,
|
||||
config: config,
|
||||
|
@ -40,8 +40,17 @@ pub fn expand_aliases<S: ToString>(
|
||||
}
|
||||
replaced.push(command_name.clone());
|
||||
|
||||
let alias_args: Vec<String> = split(&alias).ok_or_else(bad_alias)?;
|
||||
args = expand_alias_args(&args, alias_args);
|
||||
args = if alias.starts_with("!") {
|
||||
// Alias starting with "!" is "shell alias". It is a string that should
|
||||
// be passed "as-is" to the shell (after $1/$2/$@ substitutions).
|
||||
// Round-trip through shlex::split and shlex::quote is not lossless.
|
||||
// Therefore use a different alias handling function that does not
|
||||
// use shlex::split.
|
||||
expand_shell_alias_args(&args, &alias[1..])
|
||||
} else {
|
||||
let alias_args: Vec<String> = split(&alias).ok_or_else(bad_alias)?;
|
||||
expand_alias_args(&args, alias_args)
|
||||
};
|
||||
|
||||
let next_command_name = args.first().cloned().ok_or_else(bad_alias)?;
|
||||
if next_command_name == command_name {
|
||||
@ -101,6 +110,77 @@ fn expand_alias_args(command_args: &[String], alias_args: Vec<String>) -> Vec<St
|
||||
args
|
||||
}
|
||||
|
||||
/// Expand a single shell alias.
|
||||
///
|
||||
/// This is similar to `expand_alias_args`, but the "shell alias" is not split,
|
||||
/// and the rest of `command_args` is not appeneded to the expanded result
|
||||
/// automatically.
|
||||
///
|
||||
/// In theory, this is incorrect in corner cases. Unfortunately it is the only
|
||||
/// way to preserve Mercurial's behavior.
|
||||
///
|
||||
/// Return ["debugrunshell", "--cmd=<shell command>"].
|
||||
fn expand_shell_alias_args(command_args: &[String], shell_alias: &str) -> Vec<String> {
|
||||
// Imitates "aliasinterpolate" in mercurial/dispatch.py
|
||||
|
||||
let mut cmd = String::new();
|
||||
let mut buf = String::new();
|
||||
let mut arg_index = 1;
|
||||
|
||||
for ch in shell_alias.chars() {
|
||||
match (buf.as_ref(), ch) {
|
||||
// "$@"
|
||||
("", '"') | ("\"", '$') | ("\"$", '@') => {
|
||||
buf.push(ch);
|
||||
}
|
||||
("\"$@", '"') => {
|
||||
cmd += &command_args
|
||||
.iter()
|
||||
.skip(1)
|
||||
.map(|s| shlex::quote(s))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
buf.clear();
|
||||
}
|
||||
|
||||
// $@, $$, $0 ... $9
|
||||
// XXX: Does not support $10 or larger indexes.
|
||||
("", '$') => {
|
||||
buf.push(ch);
|
||||
}
|
||||
("$", '@') => {
|
||||
cmd += &command_args
|
||||
.iter()
|
||||
.skip(1)
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
buf.clear();
|
||||
}
|
||||
("$", '$') => {
|
||||
cmd.push('$');
|
||||
buf.clear();
|
||||
}
|
||||
("$", i) if i.is_digit(10) => {
|
||||
let i: usize = i.to_string().parse().unwrap();
|
||||
cmd += &command_args.get(i).cloned().unwrap_or_default();
|
||||
buf.clear();
|
||||
arg_index = arg_index.max(i);
|
||||
}
|
||||
|
||||
// other cases
|
||||
_ => {
|
||||
cmd += &buf;
|
||||
cmd.push(ch);
|
||||
buf.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
cmd += &buf;
|
||||
|
||||
vec!["debugrunshell".into(), format!("--cmd={}", cmd)]
|
||||
}
|
||||
|
||||
/// Prefix match commands to their full command name. If a prefix is not unique an Error::AmbiguousCommand
|
||||
/// will be returned with a vector of possibilities to choose from.
|
||||
///
|
||||
@ -378,4 +458,38 @@ mod tests {
|
||||
let expanded = expand_aliases(|x| cfg.get(x), &["a", "x"]).unwrap().0;
|
||||
assert_eq!(expanded, vec!["$2", "c", "d", "x"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expand_shell_alias() {
|
||||
let expand = |alias: &str, mut args: Vec<&str>| -> String {
|
||||
let mut cfg = BTreeMap::new();
|
||||
cfg.insert("aliasname", format!("!{}", alias));
|
||||
args.insert(0, "aliasname");
|
||||
let args = expand_aliases(|x| cfg.get(x), &args).unwrap().0;
|
||||
// args = ["debugrunshell", "--cmd=command", ...]
|
||||
// Mostly interested in the "--cmd" part.
|
||||
args[1][6..].to_string()
|
||||
};
|
||||
|
||||
assert_eq!(expand("echo \"foo\"", vec!["bar"]), "echo \"foo\"");
|
||||
assert_eq!(expand("echo \"$@\"", vec!["a b", "c"]), "echo \"a b\" c");
|
||||
assert_eq!(expand("echo $@", vec!["a b", "c"]), "echo a b c");
|
||||
assert_eq!(
|
||||
expand("$0 $1 $2", vec!["echo -n", "a b", "c d"]),
|
||||
"aliasname echo -n a b"
|
||||
);
|
||||
|
||||
assert_eq!(expand(
|
||||
r#"cat /etc/mercurial/hgrc /etc/mercurial/hgrc.d/*.rc ~/.hgrc "`hg root`/.hg/hgrc" 2>/dev/null"#, vec![]),
|
||||
r#"cat /etc/mercurial/hgrc /etc/mercurial/hgrc.d/*.rc ~/.hgrc "`hg root`/.hg/hgrc" 2>/dev/null"#);
|
||||
assert_eq!(expand(
|
||||
r#"for x in `hg log -r 'only(descendants(.) and bookmark(), master)' --template "{node}\n"`;
|
||||
do echo "Changing to $(hg log -r $x -T '{rev} {desc|firstline}')" && hg up $x && $@;
|
||||
if [ "$?" != "0" ]; then break; fi;
|
||||
done"#, vec!["echo", "1"]),
|
||||
r#"for x in `hg log -r 'only(descendants(.) and bookmark(), master)' --template "{node}\n"`;
|
||||
do echo "Changing to $(hg log -r $x -T '{rev} {desc|firstline}')" && hg up $x && echo 1;
|
||||
if [ "$?" != "0" ]; then break; fi;
|
||||
done"#);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user