Alias ​​Git avec paramètres positionnels

261

Fondamentalement, j'essaie d'alias:

git files 9fa3

... pour exécuter la commande:

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

mais git ne semble pas transmettre de paramètres de position à la commande d'alias. J'ai essayé:

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

... et quelques autres mais ça n'a pas marché.

Le cas dégénéré serait:

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

... comment puis-je faire fonctionner cela?

user400575
la source
17
Notez que dans git 1.8.2.1, il est possible de le faire sans la fonction shell (votre approche originale avec $1devrait fonctionner).
Eimantas
7
@Eimantas Souhaitez-vous élaborer une réponse? Cela ne fonctionne pas pour moi et je ne trouve aucune documentation à ce sujet.
pavon
@Eimantas cependant, il n'y a rien à ce sujet dans les notes de version .
Knu
1
je peux confirmer que je peux exécuter des commandes shell avec des arguments sans aucun shenanigans dans Git 2.11.
anarcat

Réponses:

365

La façon la plus évidente est d'utiliser une fonction shell:

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

Un alias sans !est traité comme une commande Git; par exemple commit-all = commit -a.

Avec le !, il est exécuté comme sa propre commande dans le shell, vous permettant d'utiliser une magie plus forte comme celle-ci.

UPD
Parce que les commandes sont exécutées à la racine du référentiel, vous pouvez utiliser une ${GIT_PREFIX}variable lorsque vous vous référez aux noms de fichiers dans les commandes

Cascabel
la source
8
Merci, cela semble parfaitement correct: [alias] files = "! F () {echo $ 3 $ 2 $ 1;}; f"; $ git files abc => cba
user400575
1
@ KohányiRóbert: Ce n'est en fait pas une question de script shell; c'est une particularité de git config. Un alias sans !est traité comme une commande Git; par exemple commit-all = commit -a. Avec le !, il est exécuté comme sa propre commande dans le shell, vous permettant d'utiliser une magie plus forte comme celle-ci.
Cascabel
40
Attention, !s'exécutera à la racine du référentiel, donc l'utilisation de chemins relatifs lors de l'appel de votre alias ne donnera pas les résultats que vous attendez.
Drealmer
4
@RobertDailey Il ne le casse pas, il ne l'implémente pas. Voir stackoverflow.com/questions/342969/… pour savoir comment l'ajouter.
Cascabel
3
Remarque : cela ne cite pas d'arguments (ce qui est dangereux en général). De plus, une fonction n'est pas nécessaire. Voir ma réponse pour plus d'explications.
Tom Hale
96

Vous pouvez également référencer shdirectement (au lieu de créer une fonction):

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

(Notez le tiret à la fin de la ligne - vous en aurez besoin.)

mipadi
la source
8
Si vous partagez la commande, vous voudrez probablement l'utiliser sh, car il s'agit en soi d'un shell, et il est disponible sur la grande majorité des systèmes. L'utilisation du shell par défaut ne fonctionne que si la commande fonctionne comme écrite pour tous les shells.
nomothetis
12
Je préfère --de -car il est plus familier et moins susceptibles de stdin accidentellement moyenne à un moment donné. ("Un argument de - équivaut à -" dans bash (1) est invariable)
bsb
5
Quelle est la signification exacte de la fin «-» et où est-elle documentée?
Zitrax
5
Remarque : cela ne cite pas d'arguments (ce qui est dangereux en général). La création d'un sous-shell (avec sh -c) est également inutile. Voir ma réponse pour une alternative.
Tom Hale
81

L'alias que vous recherchez est:

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

Avec validation d'argument:

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

La finale# est importante - elle empêche tous les arguments fournis par l'utilisateur d'être traités par le shell (il les commente).

Remarque: gitplace tous les arguments fournis par l'utilisateur à la fin de la ligne de commande. Pour voir cela en action, essayez:GIT_TRACE=2 git files a b c d

Les guillemets échappés (en raison de l'imbrication) sont importants pour les noms de fichiers contenant des espaces ou "; rm -rf --no-preserve-root /;)

Tom Hale
la source
Pour les cas les plus simples, c'est la bonne réponse, il n'y a vraiment pas besoin de compliquer en l'enveloppant dans une fonction ou sh -c.
Ed Randall
4
Oui, cela !implique déjà sh -c(affiché lors de l'ajout GIT_TRACE=2), il n'est donc pas nécessaire d'exécuter un autre sous-shell. Quels problèmes voyez-vous dans des cas plus compliqués?
Tom Hale
Est-ce que cela fonctionne si vous souhaitez définir des arguments par défaut? par exemple , je veux le faire pour aller chercher 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 #". Cela fonctionne très bien sans les deux premières déclarations, mais tombe en panne si vous les utilisez. (Moi headBranch = symbolic-ref --short HEADaussi).
gib
2
A travaillé dehors, cela fonctionne si vous définissez de nouveaux params, c'est donc 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
pourquoi des "devis sont requis?
Eugen Konkov
28

Utilisez GIT_TRACE = 1 décrit sur la page de manuel git pour rendre le traitement d'alias transparent:

$ 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

Vos commandes originales fonctionnent avec la version 1.8.3.4 de git (Eimantas a noté que cela avait changé dans 1.8.2.1).

Les sh -c '..' --et les f() {..}; foptions à la fois gérer proprement les paramètres « $ @ » de différentes façons (voir avec GIT_TRACE). L'ajout de "#" à un alias permettrait également des paramètres de position sans quitter les derniers.

bsb
la source
1
merci pour les explications: ces commandes fonctionnent pour moi sur le problème d'origine, en suivant vos conseils:files = "!git diff --name-status $1^ $1 #" files = "!git diff --name-status $1^"
user2291758
20

Comme indiqué par Drealmer ci - dessus :

" Faites attention, ! s'exécutera à la racine du référentiel, donc l'utilisation de chemins relatifs lors de l'appel de votre alias ne donnera pas les résultats attendus. - Drealmer 8 août 13 à 16:28 »

GIT_PREFIX étant défini par git dans le sous-répertoire dans lequel vous vous trouvez, vous pouvez contourner cela en changeant d'abord le répertoire:

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

Pierre-Olivier Vares
la source
J'ai aussi des problèmes avec cela (les commandes sont exécutées à la racine du référentiel) mais cette solution ne semble rien faire. (Si cela importe, j'utilise OS X.)
waldyrious
Oups ... l'alias git est un alias que j'ai créé.
Pierre-Olivier Vares
(depuis git 1.8.2) git config --set alias.alias = '! git config - alias global. $ 1 "$ 2" '
Pierre-Olivier Vares
C'est ce qui a fini par fonctionner pour moi: "préfixez vos alias git (qui exécutent les commandes shell et ont besoin du bon pwd) avec cd ${GIT_PREFIX:-.} &&." (source: stackoverflow.com/a/21929373/266309 )
waldyrious
Citez ceci. !cd "${GIT_PREFIX:-.}" && ls -al
mirabilos
8

Je voulais faire ça avec un alias qui fait ça:

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

Au final, j'ai créé un script shell nommé git-m qui a ce contenu:

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

Cela a l'avantage d'être beaucoup plus lisible car il est sur plusieurs lignes. De plus, j'aime pouvoir appeler bash avec -xet set -e. Vous pouvez probablement faire tout cela comme un alias, mais ce serait super laid et difficile à maintenir.

Parce que le fichier est nommé, git-mvous pouvez l'exécuter comme ceci:git m foo bar

Daniel Kaplan
la source
1
J'aime beaucoup cela aussi, mais je n'ai pas réussi à comprendre comment utiliser la saisie semi-automatique que je veux avec cette approche. Sur les alias, vous pouvez le faire: '!f() { : git branch ; ... }; f'et il remplira automatiquement l'alias en tant que branche, ce qui est très pratique.
Hassek
Oui, je pense que je préfère que les choses non triviales soient faites sous forme de fichiers de script individuels sur le chemin. L'inconvénient est que oui, vous perdez l'achèvement automatique de choses comme les références. Vous pouvez cependant résoudre ce problème en configurant manuellement votre propre saisie semi-automatique. Encore une fois, j'aime que vous puissiez simplement déposer un script dans un dossier sur le chemin et il commencera à fonctionner, mais pour l'auto-complétion, vous devez le `` charger '', donc c'est généralement dans mon .bashrcfichier que je source. Mais je ne pense pas que je change la façon dont je complète automatiquement les arguments d'un script autant que le script lui-même, et ce ne serait que pendant le développement.
thecoshman
4

Je suis juste tombé sur quelque chose de similaire; j'espère que c'est ok pour poster mes notes. Une chose qui me confond à propos des gitalias avec des arguments, vient probablement de git help config(j'ai la version 1.7.9.5 de git):

Si l'extension d'alias est précédée d'un point d'exclamation, elle sera traitée comme une commande shell. Par exemple, en définissant "alias.new =! Gitk --all --not ORIG_HEAD", l'invocation "git new" équivaut à exécuter la commande shell "gitk --all --not ORIG_HEAD". Notez que les commandes shell seront exécutées à partir du répertoire de niveau supérieur d'un référentiel, qui n'est pas nécessairement le répertoire courant. [...]

La façon dont je le vois - si un alias "sera traité comme une commande shell" lorsqu'il est préfixé avec un point d'exclamation - pourquoi aurais-je besoin d'utiliser une fonction, ou sh -cavec des arguments; pourquoi ne pas simplement écrire ma commande telle quelle?

Je ne connais toujours pas la réponse - mais je pense qu'il y a en fait une légère différence de résultat. Voici un petit test - jetez-le dans votre .git/configou votre ~/.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 "

Voici ce que j'obtiens en exécutant ces 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/

... ou: lorsque vous utilisez une commande "ordinaire" après le !"tel quel" dans un gitalias - puis gitajoute automatiquement la liste des arguments à cette commande! Un moyen de l'éviter est en effet d'appeler votre script soit comme une fonction, soit comme argument de sh -c.

Une autre chose intéressante ici (pour moi), c'est que dans un script shell, on s'attend généralement à ce que la variable automatique $0soit le nom de fichier du script. Mais pour une gitfonction d'alias, l' $0argument est, fondamentalement, le contenu de la chaîne entière spécifiant cette commande (telle qu'elle est entrée dans le fichier de configuration).

C'est pourquoi, je suppose, si vous vous trompez - dans le cas ci-dessous, cela échapperait aux doubles guillemets externes:

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

... - gitéchouerait alors avec (pour moi, au moins) un message quelque peu cryptique:

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

Je pense que, puisque git"vu" une chaîne entière comme un seul argument à !- il a essayé de l'exécuter comme un fichier exécutable; et en conséquence, il n'a pas réussi à trouver "echo 'A' 'B'"un fichier.

Dans tous les cas, dans le contexte de la git help configcitation ci-dessus, je suppose qu'il est plus précis de dire quelque chose comme: " ... l'invocation" git new "équivaut à exécuter la commande shell" gitk --all --not ORIG_HEAD $ @ ", où $ @ sont les arguments passés à l'alias de commande git depuis la ligne de commande lors de l'exécution. ... ". Je pense que cela expliquerait également pourquoi l'approche "directe" dans OP ne fonctionne pas avec les paramètres de position.

sdaau
la source
beau test. Un moyen rapide de vérifier toutes les possibilités!
albfan
failessaie d'exécuter une commande appelée "echo 'A' 'B" (c'est-à-dire 10 caractères). Même erreur sh -c "'echo a b'"et même cause, trop de couches de guillemets
bsb