Git alias con parámetros posicionales

261

Básicamente estoy tratando de alias:

git files 9fa3

... para ejecutar el comando:

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

pero git no parece pasar parámetros posicionales al comando alias. Yo he tratado:

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

... y algunos otros pero esos no funcionaron.

El caso degenerado sería:

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

... ¿Cómo puedo hacer que esto funcione?

usuario400575
fuente
17
Tenga en cuenta que en git 1.8.2.1 es posible hacerlo sin la función de shell (su enfoque original $1debería funcionar).
Eimantas
77
@Eimantas ¿Te gustaría elaborar una respuesta? No funciona para mí y no puedo encontrar ninguna documentación al respecto.
Pavón
@Eimantas no hay nada de esto en las notas de lanzamiento .
Knu
1
Puedo confirmar que puedo ejecutar comandos de shell con argumentos sin travesuras en Git 2.11.
anarcat

Respuestas:

365

La forma más obvia es usar una función de shell:

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

Un alias sin !se trata como un comando Git; por ej commit-all = commit -a.

Con el !, se ejecuta como su propio comando en el shell, lo que le permite usar magia más fuerte como esta.

UPD
Debido a que los comandos se ejecutan en la raíz del repositorio, puede usar ${GIT_PREFIX}variables al referirse a los nombres de archivo en los comandos

Cascabel
fuente
8
Gracias, esto se ve exactamente correcto: [alias] files = "! F () {echo $ 3 $ 2 $ 1;}; f"; $ git files abc => cba
user400575
1
@ KohányiRóbert: Esa en realidad no es una pregunta de script de shell; ese es un particular de git config. Un alias sin !se trata como un comando Git; por ej commit-all = commit -a. Con el !, se ejecuta como su propio comando en el shell, lo que le permite usar magia más fuerte como esta.
Cascabel
40
Tenga cuidado, !se ejecutará en la raíz del repositorio, por lo que el uso de rutas relativas al llamar a su alias no le dará los resultados que puede esperar.
Drealmer
44
@RobertDailey No lo rompe, simplemente no lo implementa. Consulte stackoverflow.com/questions/342969/… para saber cómo agregarlo.
Cascabel
3
Nota : Esto no cita argumentos (lo cual es peligroso en general). Además, una función es innecesaria. Vea mi respuesta para más explicaciones.
Tom Hale
96

También puede hacer referencia shdirectamente (en lugar de crear una función):

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

(Tenga en cuenta el guión al final de la línea; lo necesitará).

mipadi
fuente
8
Si está compartiendo el comando, probablemente quiera usarlo sh, ya que es en sí mismo un shell y está disponible en la gran mayoría de los sistemas. El uso del shell predeterminado solo funciona si el comando funciona como está escrito para todos los shells.
nomothetis
12
Yo prefiero --a -, ya que es más familiar y menos propensos a la entrada estándar accidentalmente media en algún momento. ("Un argumento de - es equivalente a -" en bash (1) es ingoogleable)
bsb
55
¿Cuál es el significado exacto de la terminación '-' y dónde está documentado?
Zitrax
55
Nota : Esto no cita argumentos (lo cual es peligroso en general). Crear un sub-shell (con sh -c) también es innecesario. Vea mi respuesta para una alternativa.
Tom Hale
81

El alias que estás buscando es:

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

Con validación de argumento:

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

El final# es importante: evita que el shell procese todos los argumentos proporcionados por el usuario (los comenta).

Nota: gitcoloca todos los argumentos proporcionados por el usuario al final de la línea de comando. Para ver esto en acción, intente:GIT_TRACE=2 git files a b c d

Las comillas escapadas (debido a la anidación) son importantes para los nombres de archivo que contienen espacios o "; rm -rf --no-preserve-root /;)

Tom Hale
fuente
Para los casos más simples, esta es la respuesta correcta, realmente no hay necesidad de complicarlo envolviéndolo en una función o sh -c.
Ed Randall
44
Sí, !ya implica sh -c(se muestra al anteponer GIT_TRACE=2), por lo que no hay necesidad de ejecutar otro sub-shell. ¿Qué problemas ves en casos más complicados?
Tom Hale
¿Funciona esto si desea establecer argumentos predeterminados? por ejemplo, yo quiero hacer esto en busca de un 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 #". Esto funciona muy bien sin las dos primeras declaraciones, pero se cae si las usa. (Yo headBranch = symbolic-ref --short HEADtambién).
gib
2
Trabajado a cabo, funciona si establece nuevos parametros, por lo que esto está muy bien: 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 qué "se requieren cotizaciones?
Eugen Konkov
28

Use GIT_TRACE = 1 descrito en la página de manual de git para hacer que el procesamiento de alias sea transparente:

$ 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

Sus comandos originales funcionan con git versión 1.8.3.4 (Eimantas notó que esto cambió en 1.8.2.1).

Las opciones sh -c '..' --y f() {..}; fmanejan limpiamente los parámetros "$ @" de diferentes maneras (ver con GIT_TRACE). Agregar "#" a un alias también permitiría parámetros posicionales sin dejar los finales.

bsb
fuente
1
gracias por las explicaciones: esos comandos funcionan para mí en el problema original, siguiendo su consejo:files = "!git diff --name-status $1^ $1 #" files = "!git diff --name-status $1^"
user2291758
20

Como lo indicó Drealmer anteriormente :

" Ten cuidado, ! se ejecutará en la raíz del repositorio, por lo que usar rutas relativas al llamar a su alias no le dará los resultados que podría esperar. - Drealmer 8 de agosto de 13 a 16:28 »

GIT_PREFIX siendo establecido por git en el subdirectorio en el que se encuentra, puede evitar esto cambiando primero el directorio:

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

Pierre-Olivier Vares
fuente
También tengo problemas con esto (los comandos se ejecutan en la raíz del repositorio) pero esta solución no parece hacer nada. (Si importa, estoy usando OS X.)
waldyrious
Oops ... git alias es un alias que hice.
Pierre-Olivier Vares
(desde git 1.8.2) git config --set alias.alias = '! git config - alias global. $ 1 "$ 2" '
Pierre-Olivier Vares
Esto es lo que terminó funcionando para mí: "prefija sus alias git (que ejecutan comandos de shell y necesitan el pwd correcto) con cd ${GIT_PREFIX:-.} &&." (fuente: stackoverflow.com/a/21929373/266309 )
waldyrious
Cita esto. !cd "${GIT_PREFIX:-.}" && ls -al
mirabilos
8

Quería hacer esto con un alias que haga esto:

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

Al final, creé un script de shell llamado git-m que tiene este contenido:

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

Esto tiene el beneficio de que es mucho más legible porque está en varias líneas. Además, me gusta poder llamar a bash con -xy set -e. Probablemente puedas hacer todo esto como un alias, pero sería muy feo y difícil de mantener.

Debido a que el archivo tiene un nombre git-m, puede ejecutarlo así:git m foo bar

Daniel Kaplan
fuente
1
También me gusta mucho más, pero no he podido descubrir cómo usar el autocompletado que quiero con este enfoque. En alias puede hacer esto: '!f() { : git branch ; ... }; f'y completará automáticamente el alias como una rama que es muy útil.
Hassek
Sí, creo que prefiero hacer las cosas no triviales como archivos de script individuales en el camino. Sin embargo, el inconveniente es que sí, pierde la finalización automática de cosas como referencias. Sin embargo, puede solucionar esto configurando manualmente su propia finalización automática. Sin embargo, una vez más, me gusta que simplemente puede soltar un script en una carpeta en la ruta y comenzará a funcionar, pero para la finalización automática, debe 'cargarlo', por lo que usualmente .bashrcestoy en mi archivo. Pero no creo que cambie la forma en que autocompleto los argumentos a un script tanto como al script en sí, y solo será durante el desarrollo.
thecoshman
4

Acabo de toparme con algo similar; Espero que esté bien publicar mis notas. Una cosa que me confunde sobre los gitalias con argumentos, probablemente proviene de git help config(tengo git versión 1.7.9.5):

Si la expansión del alias tiene el prefijo de un signo de exclamación, se tratará como un comando de shell. Por ejemplo, al definir "alias.new =! Gitk --all --not ORIG_HEAD", la invocación "git new" es equivalente a ejecutar el comando de shell "gitk --all --not ORIG_HEAD". Tenga en cuenta que los comandos de shell se ejecutarán desde el directorio de nivel superior de un repositorio, que puede no ser necesariamente el directorio actual. [...]

A mi modo de ver, si un alias "se tratará como un comando de shell" cuando se prefija con un signo de exclamación, ¿por qué necesitaría usar una función o sh -ccon argumentos? ¿Por qué no escribir mi comando tal cual?

Todavía no sé la respuesta, pero creo que en realidad hay una ligera diferencia en el resultado. Aquí hay una pequeña prueba: pon esto en tu .git/configo en tu ~/.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 "

Esto es lo que obtengo al ejecutar estos alias:

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

... o: cuando está usando un comando "simple" después del !"como está" en un gitalias, ¡entonces gitautomáticamente agrega la lista de argumentos a ese comando! Una forma de evitarlo es llamar a su script como una función o como un argumento para hacerlo sh -c.

Otra cosa interesante aquí (para mí), es que en un script de shell, uno normalmente espera que la variable automática $0sea ​​el nombre del archivo del script. Pero para una gitfunción de alias, el $0argumento es, básicamente, el contenido de toda la cadena que especifica ese comando (como se ingresó en el archivo de configuración).

Por eso, supongo, si sucede una cita errónea, en el siguiente caso, eso sería escapar de las comillas dobles externas:

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

... - entonces gitfallaría con (al menos para mí) un mensaje algo críptico:

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

Creo que, dado que git"vio" una cadena completa como un solo argumento !, intentó ejecutarla como un archivo ejecutable; y correspondientemente no se pudo encontrar "echo 'A' 'B'"como un archivo.

En cualquier caso, en el contexto de la git help configcita anterior, especularía que es más preciso decir algo como: " ... la invocación" git new "es equivalente a ejecutar el comando de shell" gitk --all --not ORIG_HEAD $ @ ", donde $ @ son los argumentos pasados ​​al alias del comando git desde la línea de comandos en tiempo de ejecución ... ". Creo que eso también explicaría por qué el enfoque "directo" en OP no funciona con parámetros posicionales.

sdaau
fuente
Buena prueba ¡Una forma rápida de comprobar todas las posibilidades!
albfan
failestá intentando ejecutar un comando llamado "echo 'A' 'B" (es decir, 10 caracteres de longitud). El mismo error sh -c "'echo a b'"y la misma causa, demasiadas capas de citas
bsb