Git alias dengan parameter posisi

261

Pada dasarnya saya mencoba alias:

git files 9fa3

... untuk menjalankan perintah:

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

tetapi git tampaknya tidak memberikan parameter posisi ke perintah alias. Saya telah mencoba:

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

... dan beberapa yang lain tetapi itu tidak berhasil.

Kasus degenerasi adalah:

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

... bagaimana saya bisa membuat ini berfungsi?

pengguna400575
sumber
17
Perhatikan bahwa pada git 1.8.2.1 dimungkinkan untuk melakukannya tanpa fungsi shell (pendekatan awal Anda $1seharusnya bekerja).
Eimantas
7
@ Eimantas Apakah Anda ingin menguraikan jawaban? Itu tidak berfungsi untuk saya, dan saya tidak dapat menemukan dokumentasi tentang hal itu.
pavon
@ Eimantas tidak ada tentang ini dalam catatan rilisnya .
Knu
1
saya dapat mengonfirmasi bahwa saya dapat menjalankan perintah shell dengan argumen tanpa shenanigans di Git 2.11.
anarcat

Jawaban:

365

Cara yang paling jelas adalah dengan menggunakan fungsi shell:

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

Alias ​​tanpa !diperlakukan sebagai perintah Git; mis commit-all = commit -a.

Dengan !itu, dijalankan sebagai perintahnya sendiri di shell, membiarkan Anda menggunakan sihir yang lebih kuat seperti ini.

UPD
Karena perintah dijalankan di root repositori, Anda dapat menggunakan ${GIT_PREFIX}variabel saat merujuk ke nama file dalam perintah

Cascabel
sumber
8
Terima kasih, ini terlihat sangat benar: [alias] files = "! F () {echo $ 3 $ 2 $ 1;}; f"; $ git file abc => cba
user400575
1
@ KohányiRóbert: Itu sebenarnya bukan pertanyaan skrip shell; itu adalah konfigurasi git. Alias ​​tanpa !diperlakukan sebagai perintah Git; mis commit-all = commit -a. Dengan !itu, dijalankan sebagai perintahnya sendiri di shell, membiarkan Anda menggunakan sihir yang lebih kuat seperti ini.
Cascabel
40
Hati-hati, !akan berjalan di root repositori, jadi menggunakan jalur relatif saat memanggil alias Anda tidak akan memberikan hasil yang Anda harapkan.
Drealmer
4
@RobertDailey Tidak merusaknya, itu hanya tidak mengimplementasikannya. Lihat stackoverflow.com/questions/342969/… untuk cara menambahkannya.
Cascabel
3
Catatan : Ini tidak mengutip argumen (yang umumnya berbahaya). Juga, suatu fungsi tidak perlu. Lihat jawaban saya untuk penjelasan lebih lanjut.
Tom Hale
96

Anda juga dapat merujuk shlangsung (alih-alih membuat fungsi):

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

(Perhatikan tanda hubung di akhir baris - Anda akan membutuhkannya.)

mipadi
sumber
8
Jika Anda berbagi perintah, Anda mungkin ingin menggunakannya sh, karena itu sendiri merupakan sebuah shell, dan itu tersedia di sebagian besar sistem. Menggunakan shell default hanya berfungsi jika perintah berfungsi seperti yang ditulis untuk semua shell.
nomothetis
12
Saya lebih suka --untuk -karena lebih familiar dan cenderung sengaja berarti stdin di beberapa titik. ("Argumen dari - setara dengan -" di bash (1) tidak dapat diubah)
bsb
5
Apa arti sebenarnya dari akhiran '-' dan di mana itu didokumentasikan?
Zitrax
5
Catatan : Ini tidak mengutip argumen (yang umumnya berbahaya). Membuat sub-shell (with sh -c) juga tidak perlu. Lihat jawaban saya untuk alternatif.
Tom Hale
81

Alias ​​yang Anda cari adalah:

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

Dengan validasi argumen:

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

Itu akhir# ini penting - mencegah semua argumen yang disediakan pengguna dari yang diproses oleh shell (itu komentar mereka keluar).

catatan: git menempatkan semua argumen yang disediakan pengguna di akhir baris perintah. Untuk melihat ini dalam tindakan, coba:GIT_TRACE=2 git files a b c d

Kutipan yang lolos (karena bersarang) penting untuk nama file yang mengandung spasi atau "; rm -rf --no-preserve-root /;)

Tom Hale
sumber
Untuk kasus yang paling sederhana, ini adalah jawaban yang benar, benar-benar tidak perlu menyulitkan dengan membungkusnya dalam fungsi atau sh -c.
Ed Randall
4
Ya, !sudah tersirat sh -c(diperlihatkan saat prabanding GIT_TRACE=2), jadi tidak perlu menjalankan sub-shell lain. Masalah apa yang Anda lihat dalam kasus yang lebih rumit?
Tom Hale
Apakah ini berfungsi jika Anda ingin mengatur argumen default? misalnya saya ingin melakukan ini untuk mengambil sebuah 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 #". Ini berfungsi baik tanpa dua pernyataan pertama, tetapi jatuh jika Anda menggunakannya. (Saya juga punya headBranch = symbolic-ref --short HEAD).
gib
2
Bekerja itu, ia bekerja jika Anda menetapkan params baru, jadi ini baik-baik saja: fp = "! a=${1:-$(git headBranch)}; b=${2:-up}; git fetch -fu $b pull/$a/head:$a; git checkout $a; git branch -u $b #".
gib
mengapa "kutipan diperlukan?
Eugen Konkov
28

Gunakan GIT_TRACE = 1 yang dijelaskan pada halaman manual git untuk membuat pemrosesan alias transparan:

$ 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

Perintah asli Anda berfungsi dengan git versi 1.8.3.4 (Eimantas mencatat ini berubah pada 1.8.2.1).

The sh -c '..' --dan f() {..}; fpilihan kedua bersih menangani "$ @" parameter dalam cara yang berbeda (lihat dengan GIT_TRACE). Menambahkan "#" ke alias juga akan memungkinkan parameter posisi tanpa meninggalkan yang tertinggal.

bsb
sumber
1
terima kasih atas penjelasannya: perintah-perintah itu bekerja untuk saya pada masalah aslinya, mengikuti saran Anda:files = "!git diff --name-status $1^ $1 #" files = "!git diff --name-status $1^"
user2291758
20

Seperti yang dinyatakan oleh Drealmer di atas :

" Hati-hati, ! akan berjalan di root repositori, jadi menggunakan jalur relatif saat memanggil alias Anda tidak akan memberikan hasil yang Anda harapkan. - Drealmer 8 Agustus '13 pada 16:28 »

GIT_PREFIX diset oleh git ke subdirektori tempat Anda berada, Anda dapat mengelak dari ini dengan terlebih dahulu mengubah direktori:

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

Pierre-Olivier Vares
sumber
Saya juga mengalami masalah dengan ini (perintah dijalankan di root repositori) tetapi solusi ini sepertinya tidak melakukan apa-apa. (Jika itu penting, saya menggunakan OS X.)
waldyrious
Ups ... git alias adalah alias yang saya buat.
Pierre-Olivier Vares
(sejak git 1.8.2) git config --set alias.alias = '! git config --global alias. $ 1 "$ 2" '
Pierre-Olivier Vares
Inilah yang akhirnya bekerja untuk saya: "awali alias git Anda (yang menjalankan perintah shell dan memerlukan pwd kanan) dengan cd ${GIT_PREFIX:-.} &&." (sumber: stackoverflow.com/a/21929373/266309 )
waldyrious
Jangan mengutip ini. !cd "${GIT_PREFIX:-.}" && ls -al
mirabilos
8

Saya ingin melakukan ini dengan alias yang melakukan ini:

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

Pada akhirnya, saya membuat skrip shell bernama git-m yang memiliki konten ini:

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

Ini memiliki manfaat yang jauh lebih mudah dibaca karena ada di banyak baris. Ditambah lagi, aku suka bisa memanggil bash dengan -xdanset -e . Anda mungkin dapat melakukan semua ini sebagai alias, tetapi itu akan sangat jelek dan sulit untuk dipertahankan.

Karena file tersebut bernama git-mAnda dapat menjalankannya seperti ini:git m foo bar

Daniel Kaplan
sumber
1
Saya juga lebih menyukai ini, tetapi saya belum dapat menemukan cara menggunakan autocomplete yang saya inginkan dengan pendekatan ini. Pada alias Anda dapat melakukan ini: '!f() { : git branch ; ... }; f'dan ini akan melengkapi secara otomatis alias sebagai cabang yang sangat berguna.
Hassek
Ya, saya pikir saya lebih suka melakukan hal-hal non-sepele dilakukan sebagai file skrip individual di jalan. Sisi buruknya adalah ya, Anda kehilangan penyelesaian otomatis hal-hal seperti referensi. Anda dapat memperbaikinya dengan mengkonfigurasi secara otomatis penyelesaian otomatis Anda sendiri. Sekali lagi, saya suka Anda hanya bisa meletakkan skrip ke folder di jalan dan itu akan mulai berfungsi, tetapi untuk pelengkapan otomatis, Anda perlu 'memuatnya', jadi biasanya itu ada di .bashrcfile saya yang saya sumber. Tapi saya tidak berpikir saya mengubah cara saya melengkapi argumen ke skrip sebanyak skrip itu sendiri, dan itu hanya akan terjadi selama dev.
thecoshman
4

Hanya menabrak sesuatu yang serupa; Saya harap posting catatan saya. Satu hal yang membingungkan saya tentang gitalias dengan argumen, mungkin berasal dari git help config(saya punya git versi 1.7.9.5):

Jika ekspansi alias diawali dengan tanda seru, itu akan diperlakukan sebagai perintah shell. Misalnya, mendefinisikan "alias.new =! Gitk --all --not ORIG_HEAD", doa "git new" setara dengan menjalankan perintah shell "gitk --all --not ORIG_HEAD". Perhatikan bahwa perintah shell akan dieksekusi dari direktori tingkat atas dari repositori, yang mungkin bukan direktori saat ini. [...]

Cara saya melihatnya - jika alias "akan diperlakukan sebagai perintah shell" ketika diawali dengan tanda seru - mengapa saya harus menggunakan fungsi, atau sh -cdengan argumen; mengapa tidak menulis perintah saya apa adanya?

Saya masih belum tahu jawabannya - tetapi saya pikir sebenarnya ada sedikit perbedaan dalam hasil. Inilah sedikit tes - lemparkan ini pada Anda .git/configatau Anda ~/.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 "

Inilah yang saya dapat menjalankan alias ini:

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

... atau: ketika Anda menggunakan perintah "biasa" setelah !"apa adanya" di gitalias - maka gitsecara otomatis menambahkan daftar argumen ke perintah itu! Memang cara untuk menghindarinya adalah dengan memanggil skrip Anda sebagai fungsi - atau sebagai argumen sh -c.

Hal lain yang menarik di sini (bagi saya), adalah bahwa dalam skrip shell, seseorang biasanya mengharapkan variabel otomatis $0menjadi nama file skrip. Tetapi untuk gitfungsi alias, $0argumennya adalah, pada dasarnya, isi dari seluruh string yang menentukan perintah itu (seperti yang dimasukkan dalam file konfigurasi).

Itulah sebabnya, saya kira, jika Anda salah mengutip - dalam kasus di bawah ini, itu akan menghindari kutip ganda luar:

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

... - maka gitakan gagal dengan (bagi saya, setidaknya) pesan yang agak samar:

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

Saya pikir, karena git"melihat" seluruh string sebagai hanya satu argumen untuk !- ia mencoba menjalankannya sebagai file yang dapat dieksekusi; dan karenanya gagal menemukan "echo 'A' 'B'"sebagai file.

Bagaimanapun, dalam konteks git help configkutipan di atas, saya berspekulasi bahwa lebih akurat untuk menyatakan sesuatu seperti: " ... doa" git baru "setara dengan menjalankan perintah shell" gitk --semua --tidak ORIG_HEAD $ @ ", di mana $ @ adalah argumen yang diteruskan ke alias perintah git dari baris perintah saat runtime ... ... ". Saya pikir itu juga akan menjelaskan, mengapa pendekatan "langsung" di OP tidak bekerja dengan parameter posisi.

sdaau
sumber
tes yang bagus. Cara cepat untuk memeriksa semua kemungkinan!
albfan
failsedang mencoba menjalankan perintah yang disebut "echo 'A' 'B" (mis. panjang 10 karakter). Kesalahan yang sama dari sh -c "'echo a b'"dan penyebab yang sama, terlalu banyak lapisan kutipan
bsb