我的~/.gitconfig文件内容如下:
[alias]
commit = "!sh commit.sh"
然而,当我输入git commit时,脚本并没有被调用。
这是否可能实现,或者我必须使用另一个别名?
这是不可能的
这来自于我的git.git克隆:
static int run_argv(int *argcp, const char ***argv)
{
int done_alias = 0;
while (1) {
/* See if it's an internal command */
handle_internal_command(*argcp, *argv);
/* .. then try the external ones */
execv_dashed_external(*argv);
/* It could be an alias -- this works around the insanity
* of overriding "git log" with "git show" by having
* alias.log = show
*/
if (done_alias || !handle_alias(argcp, argv))
break;
done_alias = 1;
}
return done_alias;
}
handle_internal_command
会调用exit
)。handle_alias
在找到别名时调用exit
来修复这个问题。libexec/git-core/git-commit
。 - linquize我决定使用一个bash函数来解决这个问题。如果我调用git clone
,它会重定向到git cl
,这是我的别名,并带有一些额外的开关。
function git {
if [[ "$1" == "clone" && "$@" != *"--help"* ]]; then
shift 1
command git cl "$@"
else
command git "$@"
fi
}
cl
别名呢? - Gregory Pakosz如前所述,无法使用git别名来覆盖git命令。但是,可以使用shell别名覆盖git命令。对于任何POSIXy shell(即不是MS cmd
),编写一个简单的可执行脚本,执行所需的修改行为并设置一个shell别名。在我的.bashrc
(Linux)和.bash_profile
(Mac)中,我有:
export PATH="~/bin:$PATH"
...
alias git='my-git'
在我的 ~/bin
文件夹中,我有一个可执行的Perl脚本,名为 my-git
,它检查第一个参数(即git命令)是否为clone
。它的大致代码如下:
#!/usr/bin/env perl
use strict;
use warnings;
my $path_to_git = '/usr/local/bin/git';
exit(system($path_to_git, @ARGV))
if @ARGV < 2 or $ARGV[0] ne 'clone';
# Override git-clone here...
我的功能略微可配置,但你能理解大概的意思。
git
并不是一种可行的解决方案。 - tripleeePATH
早期的目录中将git
符号链接到您的包装脚本。显然要确保脚本不会执行自己而不是真正的git
。 - tripleeegit
可能会变得非常棘手或者根本无法维护,而一个简单的包装器可以让你方便地从上游拉取更新,通常是相当健壮的,并避免了分支的固有问题。 - tripleee不仅不可能,而且不修复
目前 git 不允许别名覆盖内置命令。我理解这背后的原因,但我想知道是否太过保守。
并不是。
大多数 shell 支持使用别名覆盖命令,我不确定为什么 git 需要比 shell 更加保守。
因为正常的 shell 在脚本中使用时不会扩展别名,并且还提供了一种方便的方法来从命令行打败别名。
$ alias ls='ls -aF'
$ echo ls >script
$ chmod +x script
并进行比较:
$ ./script
$ ls
$ /bin/ls
$ alias ls='ls -aF'
然后比较这些:$ ls
$ \ls
$ /bin/ls
$ builtin ls
第一个调用别名,指向shell内置的ls
或外部二进制文件。
第二个绕过别名,直接调用shell内置的ls
或外部二进制文件。
第三个通过明确调用外部二进制文件/bin/ls
来绕过别名。
第四个,假设有一个内置的ls
,则强制调用该内置命令。 - masta顺便说一下,我通过编写以下~/bin/git
包装器来解决这个问题(好吧,“绕过它”...),该包装器检查例如~/bin/git-clone
并调用那个而不是内置的。
[注意:对于任何“聪明”的bash主义,我表示歉意,但在你经过两个辅助函数(一个用于扩展符号链接,一个用于搜索被包装的可执行文件的$PATH)之后,实际脚本本身只有三行代码...所以我想我毕竟不需要道歉,呵呵!]
#!/usr/bin/env bash
###########################
### UTILITY FUNCTIONS ### ...from my .bashrc
###########################
#
# deref "/path/with/links/to/symlink"
# - Returns physical path for specified target
#
# __SUPER__
# - Returns next "$0" in $PATH (that isn't me, or a symlink to me...)
deref() {
( # Wrap 'cd's in a sub-shell
local target="$1"
local counter=0
# If the argument itself is a link [to a link, to a link...]
# NOTE: readlink(1) is not defined by POSIX, but has been shown to
# work on at least MacOS X, CentOS, Ubuntu, openSUSE, and OpenBSD
while [[ -L "$target" ]]; do
[[ $((++counter)) -ge 30 ]] && return 1
cd "${target%/*}"; target="$(readlink "$target")"
done
# Expand parent directory hierarchy
cd "${target%/*}" 2>/dev/null \
&& echo "$(pwd -P)/${target##*/}" \
|| echo "$([[ $target != /* ]] && echo "$(pwd -P)/")$target"
)
}
__SUPER__() {
local cmd="${1:-${0##*/}}"
local me="$(deref "$0")"
# NOTE: We only consider symlinks... We could check for hardlinks by
# comparing device+inode, but stat(1) has portability problems
local IFS=":"
for d in $PATH; do
[[ -x "$d/$cmd" ]] && [[ "$(deref "$d/$cmd")" != "$me" ]] \
&& { echo "$d/$cmd"; return; }
done
# else...
return 1
}
########################################################################
# (1) First, figure out which '$0' we *WOULD* have run...
GIT="$(__SUPER__)" || { echo "${0##*/}: command not found" >&2; exit 1; }
# (2) If we have a "~/bin/git-${command}" wrapper, then
# prepend '.../libexec/git-core' to $PATH and run it
[[ -f "${HOME}/bin/git-$1" ]] &&
PATH="$PATH:$( "$GIT" --exec-path )" \
exec "${HOME}/bin/git-$1" "${@:2}"
# (3) Else fall back to the regular 'git'
exec "$GIT" "$@"
co
别名为!sh checkout.sh
,这样你就不必输入所有内容(甚至不用按Tab键自动补全)。 - Cascabel