【问题标题】:Git alias with positional parameters带有位置参数的 Git 别名
【发布时间】:2011-03-20 06:53:37
【问题描述】:

基本上我正在尝试别名:

git files 9fa3

...执行命令:

git diff --name-status 9fa3^ 9fa3

但 git 似乎没有将位置参数传递给 alias 命令。我试过了:

[alias]
    files = "!git diff --name-status $1^ $1"
    files = "!git diff --name-status {1}^ {1}"

...还有一些其他的,但这些都不起作用。

退化的情况是:

$ git echo_reverse_these_params a b c d e
e d c b a

...我怎样才能完成这项工作?

【问题讨论】:

  • 请注意,在 git 1.8.2.1 中,可以在没有 shell 功能的情况下执行此操作(您使用 $1 的原始方法应该可以工作)。
  • @Eimantas 您愿意详细说明答案吗?它对我不起作用,我找不到任何关于它的文档。
  • @Eimantas 不过在release notes 中没有这方面的内容。
  • 我可以确认我可以在 Git 2.11 中运行带有参数的 shell 命令,而无需任何恶作剧。

标签: git command parameters position alias


【解决方案1】:

shell 函数可以帮助解决这个问题:

[alias]
    files = "!f() { git diff --name-status \"$1^\" \"$1\"; }; f"

没有! 的别名被视为Git 命令;例如commit-all = commit -a.

使用!,它在shell 中作为自己的命令运行,让您使用这样更强大的魔法。

UPD
因为命令是在存储库的根目录执行的,所以在引用命令中的文件名时可以使用${GIT_PREFIX} 变量

【讨论】:

  • 谢谢,这看起来完全正确: [alias] files = "!f() { echo $3 $2 $1; }; f" ; $ git 文件 a b c => c b a
  • @KohányiRóbert:这实际上不是一个 shell 脚本问题。这是 git config 的特殊之处。没有! 的别名被视为Git 命令;例如commit-all = commit -a。使用 !,它在 shell 中作为自己的命令运行,让您使用更强大的魔法。
  • 小心,! 将在存储库的根目录运行,因此在调用别名时使用相对路径不会产生您所期望的结果。
  • @RobertDailey 它没有破坏它,它只是没有实现它。添加方法见stackoverflow.com/questions/342969/…
  • 注意:这不会引用参数(这通常很危险)。另外,不需要函数。更多解释见my answer
【解决方案2】:

也可以直接引用sh(不用创建函数):

[alias]
        files = !sh -c 'git diff --name-status $1^ $1' -

(注意行尾的破折号——你需要它。)

【讨论】:

  • 如果你共享命令,你可能想使用sh,因为它本身就是一个shell,它在绝大多数系统上都可用。仅当命令按照为所有 shell 编写的方式工作时,使用默认 shell 才有效。
  • 我更喜欢-- 而不是-,因为它更熟悉并且在某些时候不太可能意外地表示标准输入。 (在 bash(1) 中,“- 的参数等同于 --”是无法搜索到的)
  • 结尾“-”的确切含义是什么?它在哪里记录?
  • 注意:这不会引用参数(这通常很危险)。也不需要创建子外壳(使用sh -c)。请参阅my answer 了解替代方案。
【解决方案3】:

您要查找的别名是:

files = "!git diff --name-status \"$1\"^ \"$1\" #"

带参数验证:

files = "!cd -- \"${GIT_PREFIX:-.}\" && [ x$# != x1 ] && echo commit-ish required >&2 || git diff --name-status \"$1\"^ \"$1\" #"

最终的# 很重要 - 它可以防止所有用户提供的参数被 shell 处理(它会将它们排除在外)。

注意:git 将所有用户提供的参数放在命令行的末尾。要查看实际情况,请尝试:GIT_TRACE=2 git files a b c d

转义(由于嵌套)引号对于包含空格或"; rm -rf --no-preserve-root /;的文件名很重要)

【讨论】:

  • 对于最简单的情况,这是正确的答案,实际上没有必要通过将其包装在函数或 sh -c 中来复杂化。
  • 是的,! 已经暗示了sh -c(在添加GIT_TRACE=2 时显示),因此无需运行另一个子shell。您在更复杂的情况下看到哪些问题?
  • 如果您想设置默认参数,这是否有效?例如我想这样做来获取 Github PR:fp = "! 1=${1:-$(git headBranch)}; 2=${2:-up}; git fetch -fu $2 pull/$1/head:$1; git checkout $1; git branch -u $2 #"。如果没有前两个语句,这很好用,但如果你使用它们就会失败。 (我也有headBranch = symbolic-ref --short HEAD)。
  • 解决了,如果你设置了新的参数就可以了,所以这很好:fp = "! a=${1:-$(git headBranch)}; b=${2:-up}; git fetch -fu $b pull/$a/head:$a; git checkout $a; git branch -u $b #".
  • @EugenKonkov 引号是必需的,因为变量的扩展可能包含空格,我们希望将它们保留为单个 shell 标记。
【解决方案4】:

使用 git 手册页中描述的 GIT_TRACE=1 使别名处理透明化:

$ git config alias.files
!git diff --name-status $1^ $1
$ GIT_TRACE=1 git files 1d49ec0
trace: exec: 'git-files' '1d49ec0'
trace: run_command: 'git-files' '1d49ec0'
trace: run_command: 'git diff --name-status $1^ $1' '1d49ec0'
trace: exec: '/bin/sh' '-c' 'git diff --name-status $1^ $1 "$@"' 'git diff --name-status $1^ $1' '1d49ec0'
trace: built-in: git 'diff' '--name-status' '1d49ec0^' '1d49ec0' '1d49ec0'
trace: run_command: 'less -R'
trace: exec: '/bin/sh' '-c' 'less -R' 'less -R'
MM      TODO

您的原始命令适用于 git 版本 1.8.3.4(Eimantas 注意到这在 1.8.2.1 中有所更改)。

sh -c '..' --f() {..}; f 选项都以不同的方式干净地处理“$@”参数(参见 GIT_TRACE)。将“#”附加到别名也将允许位置参数而不留下尾随参数。

【讨论】:

  • 感谢您的解释:这些命令在原始问题上对我有用,遵循您的建议:files = "!git diff --name-status $1^ $1 #"files = "!git diff --name-status $1^"
【解决方案5】:

正如 Drealmer above 所说:

« 小心,!将在存储库的根目录运行,因此在调用别名时使用相对路径不会给出您可能期望的结果。 – Drealmer 2013 年 8 月 8 日 16:28 »

GIT_PREFIX 被 git 设置为您所在的子目录,您可以通过先更改目录来规避此问题:

git config --global alias.ls '!cd "${GIT_PREFIX:-.}"; ls -al'

【讨论】:

  • 我也遇到了这个问题(命令在存储库的根目录下运行),但这个解决方案似乎没有做任何事情。 (如果重要的话,我使用的是 OS X。)
  • 糟糕... git alias 是我创建的别名。
  • (从 gi​​t 1.8.2 开始) git config --set alias.alias = '! git config --global alias.$1 "$2"'
  • 这最终对我有用:“使用 cd ${GIT_PREFIX:-.} &&. 为你的 git 别名(运行 shell 命令并需要正确的密码)添加前缀”(来源:stackoverflow.com/a/21929373/266309
  • 引用这个。 !cd "${GIT_PREFIX:-.}" && ls -al
【解决方案6】:

我想用一个别名来做到这一点:

git checkout $1;
git merge --ff-only $2;
git branch -d $2;

最后,我创建了一个名为 git-m 的 shell 脚本,其中包含以下内容:

#!/bin/bash -x
set -e

#by naming this git-m and putting it in your PATH, git will be able to run it when you type "git m ..."

if [ "$#" -ne 2 ]
then
  echo "Wrong number of arguments. Should be 2, was $#";
  exit 1;
fi

git checkout $1;
git merge --ff-only $2;
git branch -d $2;

这样做的好处是它更加更清晰,因为它位于多行上。另外,我喜欢使用-xset -e 调用bash。您可能可以将整个事情作为别名来完成,但它会非常丑陋且难以维护。

因为文件名为git-m,所以可以这样运行:git m foo bar

【讨论】:

  • 我也更喜欢这个,但我无法弄清楚如何通过这种方法使用我想要的自动完成功能。在别名上你可以这样做:'!f() { : git branch ; ... }; f' 它会自动完成别名作为一个超级方便的分支。
  • 是的,我想我更喜欢将不平凡的事情作为路径上的单独脚本文件完成。不利的一面是肯定的,您会失去自动完成诸如参考之类的事情。您可以通过手动配置自己的自动完成来解决此问题。尽管如此,我还是喜欢你可以将脚本放到路径上的文件夹中,它就会开始工作,但是为了自动完成,你需要“加载”它,所以通常它在我的 .bashrc 文件中资源。但我认为我不会像脚本本身那样更改自动完成脚本参数的方式,而且只会在开发期间发生。
【解决方案7】:

刚刚碰到类似的东西;希望可以发布我的笔记。让我对 git 别名和参数感到困惑的一件事可能来自 git help config(我有 git 版本 1.7.9.5):

如果别名扩展以感叹号为前缀,它将被视为 shell 命令。例如定义“alias.new = !gitk --all --not ORIG_HEAD”,调用“git new”相当于运行shell命令 “gitk --all --not ORIG_HEAD”。请注意,shell 命令将从存储库的顶级目录执行, 这可能不一定是当前目录。 [...]

我的看法 - 如果别名“将被视为 shell 命令”时以感叹号为前缀 - 为什么我需要使用函数,或者 sh -c 带参数;为什么不按原样编写我的命令?

我仍然不知道答案 - 但我认为实际上结果略有不同。这是一个小测试 - 将其放入您的 .git/config~/.gitconfig

[alias]
  # ...
  ech = "! echo rem: "
  shech = "! sh -c 'echo rem:' "
  fech = "! f() { echo rem: ; }; f " # must have ; after echo!
  echargs = "! echo 0[[\"$0\"]] 1-\"$1\"/ A-"$@"/ "
  fechargs = "! f() { echo 0[[\"$0\"]] 1-\"$1\"/ A-"$@"/ ; }; f "

这是我运行这些别名的结果:

$ git ech word1 word2
rem: word1 word2

$ git shech word1 word2
rem:

$ git fech word1 word2
rem:

$ git echargs word1 word2
0[[ echo 0[["$0"]] 1-"$1"/ A-$@/ ]] 1-word1/ A-word1 word2/ word1 word2

$ git fechargs word1 word2
0[[ f() { echo 0[["$0"]] 1-"$1"/ A-$@/ ; }; f ]] 1-word1/ A-word1 word2/

... 或者:当您在 git 别名中的 ! “原样”之后使用“普通”命令时 - 然后 git 会自动将参数列表附加到该命令!实际上,避免它的一种方法是将脚本作为函数调用 - 或作为 sh -c 的参数。

这里(对我而言)另一件有趣的事情是,在 shell 脚本中,人们通常期望自动变量 $0 是脚本的文件名。但是对于 git 别名函数,$0 参数基本上是指定该命令的 整个 字符串的内容(在配置文件中输入)。

这就是为什么,我猜,如果你碰巧引用错误 - 在下面的情况下,那将是转义外部双引号:

[alias]
  # ...
  fail = ! \"echo 'A' 'B'\"

... - 然后git 将失败(至少对我而言)有些神秘的信息:

$ git fail
 "echo 'A' 'B'": 1: echo 'A' 'B': not found
fatal: While expanding alias 'fail': ' "echo 'A' 'B'"': No such file or directory

我认为,因为git “看到”了整个字符串作为! 的一个参数 - 它试图将其作为可执行文件运行;相应地,它未能找到 "echo 'A' 'B'" 作为文件。

无论如何,在上述git help config 引用的上下文中,我推测这样表述更准确:“ ...调用“git new”相当于运行shell命令“gitk --all --not ORIG_HEAD $@”,其中 $@ 是在运行时从命令行传递给 git 命令别名的参数。...“。我认为这也可以解释为什么 OP 中的“直接”方法不适用于位置参数。

【讨论】:

  • 不错的测试。检查所有可能性的快速方法!
  • fail 正在尝试运行一个名为“echo 'A' 'B”的命令(即 10 个字符长)。来自sh -c "'echo a b'" 的相同错误和相同的原因,引号层太多
猜你喜欢
  • 1970-01-01
  • 2020-07-19
  • 1970-01-01
  • 2021-12-18
  • 1970-01-01
  • 1970-01-01
  • 2021-02-01
  • 1970-01-01
相关资源
最近更新 更多