Alias ​​do Git com parâmetros posicionais

261

Basicamente, eu estou tentando alias:

git files 9fa3

... para executar o comando:

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

mas o git não parece passar parâmetros posicionais para o comando alias. Eu tentei:

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

... e alguns outros, mas esses não funcionaram.

O caso degenerado seria:

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

... como posso fazer isso funcionar?

user400575
fonte
17
Observe que no git 1.8.2.1 é possível fazer isso sem a função shell (sua abordagem original $1deve funcionar).
Eimantas
7
@Eimantas Você gostaria de elaborar uma resposta? Não funciona para mim e não consigo encontrar nenhuma documentação sobre isso.
pavon 18/08/14
@Eimantas, não há nada sobre isso nas notas de lançamento .
Knu
1
posso confirmar que posso executar comandos do shell com argumentos sem nenhuma desvantagem no Git 2.11.
anarcat

Respostas:

365

A maneira mais óbvia é usar uma função shell:

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

Um alias sem !é tratado como um comando Git; por exemplocommit-all = commit -a .

Com o !, ele é executado como seu próprio comando no shell, permitindo que você use uma magia mais forte como esta.

UPD
Como os comandos são executados na raiz do repositório, você pode usar a ${GIT_PREFIX}variável ao se referir aos nomes dos arquivos nos comandos

Cascabel
fonte
8
Obrigado, isso parece exatamente correto: [alias] files = "! F () {echo $ 3 $ 2 $ 1;}; f"; $ git files abc => cba
user400575
1
@ KohányiRóbert: Na verdade, essa não é uma questão de script de shell; isso é um particular da configuração do git. Um alias sem !é tratado como um comando Git; por exemplo commit-all = commit -a. Com o !, ele é executado como seu próprio comando no shell, permitindo que você use uma magia mais forte como esta.
Cascabel
40
Cuidado, ele !será executado na raiz do repositório, portanto, usar caminhos relativos ao chamar seu alias não fornecerá os resultados que você pode esperar.
Drealmer
4
@RobertDailey Não quebra, simplesmente não o implementa. Consulte stackoverflow.com/questions/342969/… para saber como adicioná-lo.
Cascabel
3
Nota : Isso não cita argumentos (o que é perigoso em geral). Além disso, uma função é desnecessária. Veja minha resposta para mais explicações.
Tom Hale
96

Você também pode fazer referência shdiretamente (em vez de criar uma função):

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

(Observe o traço no final da linha - você precisará disso.)

mipadi
fonte
8
Se você estiver compartilhando o comando, provavelmente desejará usá-lo sh, já que ele é um shell e está disponível na grande maioria dos sistemas. O uso do shell padrão funciona apenas se o comando funcionar como escrito para todos os shells.
Nomothetis
12
Eu prefiro --a -como é mais familiar e menos propensos a stdin acidentalmente média em algum ponto. ( "Um argumento - é equivalente a -" em bash (1) é ungoogleable)
bsb
5
Qual é o significado exato do final '-' e onde está documentado?
Zitrax 21/04
5
Nota : Isso não cita argumentos (o que é perigoso em geral). Criar um sub-shell (com sh -c) também é desnecessário. Veja minha resposta para uma alternativa.
Tom Hale
81

O alias que você está procurando é:

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

Com validação de argumento:

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

A final# é importante - impede que todos os argumentos fornecidos pelo usuário sejam processados ​​pelo shell (os comenta).

Nota: git coloca todos os argumentos fornecidos pelo usuário no final da linha de comando. Para ver isso em ação, tente:GIT_TRACE=2 git files a b c d

As aspas escapadas (devido ao aninhamento) são importantes para nomes de arquivos que contêm espaços ou "; rm -rf --no-preserve-root /;)

Tom Hale
fonte
Para os casos mais simples, essa é a resposta certa, não há realmente nenhuma complicação envolvendo-a em uma função ou sh -c.
Ed Randall
4
Sim, isso !já implica sh -c(mostrado ao anexar GIT_TRACE=2), portanto não há necessidade de executar outro sub-shell. Que problemas você vê nos casos mais complicados?
Tom Hale #
Isso funciona se você deseja definir argumentos padrão? por exemplo, eu quero fazer isso para buscar uma 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 #". Isso funciona muito bem sem as duas primeiras instruções, mas cai se você as usar. (Eu também tenho headBranch = symbolic-ref --short HEAD).
Gib #
2
Trabalhou com isso, ele funciona se você definir novos parâmetros, então isso é bom: fp = "! a=${1:-$(git headBranch)}; b=${2:-up}; git fetch -fu $b pull/$a/head:$a; git checkout $a; git branch -u $b #".
Gib #
por que "aspas são necessárias?
Eugen Konkov
28

Use GIT_TRACE = 1 descrito na página de manual do git para tornar transparente o processamento do alias:

$ 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

Seus comandos originais funcionam com a versão 1.8.3.4 do git (o Eimantas notou que isso foi alterado na 1.8.2.1).

As opções sh -c '..' --e f() {..}; fmanipulam de maneira limpa os parâmetros "$ @" de diferentes maneiras (veja em GIT_TRACE). Anexar "#" a um alias também permitiria parâmetros posicionais sem sair dos finais.

bsb
fonte
1
obrigado pelas explicações: os comandos trabalham para mim sobre o problema original, seguindo o seu conselho:files = "!git diff --name-status $1^ $1 #" files = "!git diff --name-status $1^"
user2291758
20

Como afirmado por Drealmer acima :

" Seja cuidadoso, ! será executado na raiz do repositório, portanto, usar caminhos relativos ao chamar seu alias não fornecerá os resultados que você pode esperar. - Drealmer 8/08/13 às 16:28 »

GIT_PREFIX sendo definido pelo git para o subdiretório em que você está, você pode contornar isso alterando primeiro o diretório:

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

Pierre-Olivier Vares
fonte
Estou tendo problemas com isso também (comandos sendo executados na raiz do repositório), mas esta solução parece não fazer nada. (Se é importante, eu estou usando X. OS)
waldyrious
Opa ... git alias é um alias que eu criei.
Pierre-Olivier Vares
(desde git 1.8.2) git config --set alias.alias = '! git config --global alias. $ 1 "$ 2" '
Pierre-Olivier Vares
Isto é o que acabou trabalhando para mim: "prefixar seus aliases git (que comandos shell prazo e precisam da pwd direita) com cd ${GIT_PREFIX:-.} &&." (fonte: stackoverflow.com/a/21929373/266309 )
waldyrious
Cite isso. !cd "${GIT_PREFIX:-.}" && ls -al
mirabilos
8

Eu queria fazer isso com um alias que faz isso:

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

No final, criei um script de shell chamado git-m que tem este conteúdo:

#!/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;

Isso tem o benefício de ser muito mais legível porque está em várias linhas. Além disso, eu gosto de poder chamar o bash com -xeset -e . Você provavelmente pode fazer tudo isso como um apelido, mas seria super feio e difícil de manter.

Como o arquivo é nomeado, git-mvocê pode executá-lo assim:git m foo bar

Daniel Kaplan
fonte
1
Também gosto muito mais disso, mas não consegui descobrir como usar o preenchimento automático que desejo com essa abordagem. Em aliases, você pode fazer isso: '!f() { : git branch ; ... }; f'e ele completará automaticamente o alias como um ramo que é super útil.
Hassek
Sim, acho que prefiro fazer coisas não triviais como arquivos de script individuais no caminho. O lado negativo é que sim, você perde a conclusão automática de coisas como referências. Você pode corrigir isso configurando manualmente seu próprio preenchimento automático. Mais uma vez, eu gosto que você pode simplesmente soltar um script em uma pasta no caminho e ele começará a funcionar, mas para o preenchimento automático, você precisa 'carregá-lo'; normalmente, é no meu .bashrcarquivo que eu forneço. Mas acho que não mudei a maneira como preencho automaticamente os argumentos para um script, tanto quanto o próprio script, e seria apenas durante o desenvolvimento.
Thecoshman #
4

Apenas esbarrei em algo semelhante; espero que esteja tudo bem em publicar minhas anotações. Uma coisa que me confunde sobre gitaliases com argumentos, provavelmente vem do git help config(eu tenho a versão 1.7.9.5 do git):

Se a expansão do alias for prefixada com um ponto de exclamação, ela será tratada como um comando shell. Por exemplo, definindo "alias.new =! Gitk --all --not ORIG_HEAD", a chamada "git new" é equivalente a executar o comando do shell "gitk --all --not ORIG_HEAD". Observe que os comandos do shell serão executados no diretório de nível superior de um repositório, que pode não ser necessariamente o diretório atual. [...]

Do jeito que eu vejo - se um apelido "será tratado como um comando de shell" quando prefixado com ponto de exclamação - por que eu precisaria usar uma função ou sh -ccom argumentos; por que não apenas escrever meu comando como está?

Ainda não sei a resposta - mas acho que, na verdade, há uma pequena diferença no resultado. Aqui está um pequeno teste - jogue isso no seu .git/configou no seu ~/.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 "

Aqui está o que eu recebo executando esses aliases:

$ 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/

... ou: quando você estiver usando um comando "simples" após o !"no estado em que está" em um gitalias - gitanexará automaticamente a lista de argumentos a esse comando! Uma maneira de evitá-lo é chamar seu script como uma função - ou como argumento sh -c.

Outra coisa interessante aqui (para mim) é que, em um script de shell, normalmente se espera que a variável automática $0seja o nome do arquivo do script. Mas para uma gitfunção de alias, o $0argumento é, basicamente, o conteúdo de toda a cadeia que especifica esse comando (conforme digitado no arquivo de configuração).

Por isso, eu acho, se você citar incorretamente - no caso abaixo, isso estaria escapando das aspas duplas externas:

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

... - então gitfalharia com (pelo menos para mim) a mensagem um tanto enigmática:

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

Eu acho que, uma vez que git"via" uma string inteira como apenas um argumento para !- tentava executá-la como um arquivo executável; e correspondentemente falhou ao encontrar "echo 'A' 'B'"como um arquivo.

De qualquer forma, no contexto da git help configcitação acima, eu especularia que é mais preciso afirmar algo como: " ... a invocação" git new "é equivalente a executar o comando shell" gitk --all --not ORIG_HEAD $ @ ", onde $ @ são os argumentos passados ​​para o alias do comando git na linha de comando no tempo de execução. ... ". Eu acho que isso também explicaria por que a abordagem "direta" no OP não funciona com parâmetros posicionais.

sdaau
fonte
bom teste. Uma maneira rápida de verificar todas as possibilidades!
Albfan
failestá tentando executar um comando chamado "eco 'A' 'B" (ou seja, 10 caracteres). Mesmo erro de sh -c "'echo a b'"e mesma causa, muitas camadas de citações
bsb