具有位置参数的Git别名


261

基本上我想别名:

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

...我该如何进行这项工作?


17
请注意,在git 1.8.2.1中,可以不使用shell函数来执行此操作(您的原始方法$1应该可以使用)。
Eimantas

7
@Eimantas您是否愿意详细说明答案?它对我不起作用,而且我找不到有关它的任何文档。
pavon 2014年

@Eimantas在发行说明中没有关于此的任何内容。
克伦民族联盟

1
我可以确认我可以在Git 2.11中使用带参数的shell命令运行而无需任何假名。
anarcat

Answers:


365

最明显的方法是使用shell函数:

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

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

使用!,它可以在外壳程序中作为自己的命令运行,让您可以使用更强大的魔术。

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


8
谢谢,这看起来很正确:[alias] files =“!f(){echo $ 3 $ 2 $ 1;}; f”; $ git files abc => cba
user400575

1
@KohányiRóbert:这实际上不是一个shell脚本问题;这是git config的特殊之处。没有别名的别名!被视为Git命令;例如commit-all = commit -a。使用!,它可以在外壳程序中作为自己的命令运行,让您可以使用更强大的魔术。
卡斯卡贝尔2011年

40
请注意,!它将在存储库的根目录中运行,因此在调用别名时使用相对路径不会提供预期的结果。
Drealmer

4
@RobertDailey它不会破坏它,只是不会实现它。有关如何添加的信息,请参见stackoverflow.com/questions/342969/…
卡斯卡贝尔2014年

3
注意:这不引用参数(通常很危险)。另外,功能是不必要的。请参阅我的答案以获取更多说明。
汤姆·黑尔

96

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

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

(请注意行尾的破折号-您将需要它。)


8
如果共享该命令,则可能要使用sh,因为它本身就是一个外壳,并且在绝大多数系统上都可用。仅当命令对所有Shell均有效时,才使用默认Shell。
nomothetis

12
我更喜欢它---因为它更熟悉并且在某个时候不太可能偶然表示标准输入。(bash(1)中的“-的参数等于-”是不可谷歌的)
bsb


5
“-”结尾的确切含义是什么?在哪里记录?
Zitrax '16

5
注意:这不引用参数(通常很危险)。sh -c也无需创建子外壳(使用)。请参阅我的答案
汤姆·黑尔

81

您要查找的别名是:

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

使用参数验证:

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

最终#是重要-它可以防止所有的用户提供的参数从由所述外壳(它评论出来)被处理。

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

逃跑的(由于嵌套)引号是重要的,包含空格或文件名"; rm -rf --no-preserve-root /;


对于最简单的情况,这是正确的答案,实际上没有必要通过将其包装在函数或sh -c中来使其复杂化。
Ed Randall

4
是的,!已经暗示了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)。
gib

2
解决了这个问题,如果您设置了新的参数,它就可以工作,所以很好fp = "! a=${1:-$(git headBranch)}; b=${2:-up}; git fetch -fu $b pull/$a/head:$a; git checkout $a; git branch -u $b #"
gib

为什么"需要报价?
Eugen Konkov

27

使用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)。在别名后面加上“#”也将允许位置参数而不会留下结尾的参数。


1
感谢您的解释:按照您的建议,这些命令对我来说可以解决原始问题:files = "!git diff --name-status $1^ $1 #" files = "!git diff --name-status $1^"
user2291758 2015年

20

上述 Drealmer 所述

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

GIT_PREFIX 由git设置为您所在的子目录,您可以通过首先更改目录来避免这种情况:

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


我也遇到了麻烦(命令在存储库的根目录中运行),但是此解决方案似乎没有任何作用。(如果有关系,我正在使用OSX。)
waldyrious

糟糕... git alias是我创建的别名。
Pierre-Olivier Vares

(自git 1.8.2起)git config --set alias.alias ='!git config --global alias。$ 1“ $ 2”'
Pierre-Olivier Vares

这就是为我工作的结果:“在git别名(运行shell命令并需要正确的pwd)之前加上cd ${GIT_PREFIX:-.} &&.”(来源:stackoverflow.com/a/21929373/266309
waldyrious

引用这个。!cd "${GIT_PREFIX:-.}" && ls -al
mirabilos

8

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

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;

这有它的好处很多更清晰,因为它是多条线路上。另外,我喜欢能够通过-x和调用bash set -e。您可以将整个事情作为别名来完成,但是这将非常丑陋且难以维护。

因为文件是命名的,所以git-m您可以这样运行它:git m foo bar


1
我也很喜欢这种方式,但是我还无法弄清楚如何通过这种方式使用我想要的自动完成功能。在别名上,您可以执行以下操作:'!f() { : git branch ; ... }; f'并且它将自动完成别名作为超级方便的分支。
哈瑟克

是的,我想我更喜欢将不平凡的事情作为单独的脚本文件完成。不利的一面是,您会失去自动完成诸如引用之类的功能的能力。不过,您可以通过手动配置自己的自动完成来解决此问题。再说一次,我喜欢将脚本放到路径上的文件夹中,它将开始起作用,但是对于自动完成,您需要“加载”它,因此通常它是在我提供的.bashrc文件中。但是我认为我不会像脚本本身那样改变自动完成脚本参数的方式,而只是在开发期间。
thecoshman

4

刚碰到类似的东西;希望可以发表我的笔记。关于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中的“直接”方法不适用于位置参数。


不错的测试。快速检查所有可能性的方法!
albfan 2013年

fail正在尝试运行一个名为“ echo'A''B”的命令(即10个字符长)。相同的错误sh -c "'echo a b'"和相同的原因,引号层太多
bsb
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.