Bagaimana cara saya menentukan secara program apakah ada perubahan yang tidak dikomit?

226

Dalam Makefile, saya ingin melakukan tindakan tertentu jika ada perubahan yang tidak dikomit (baik di pohon kerja atau indeks). Apa cara paling bersih dan paling efisien untuk melakukan itu? Perintah yang keluar dengan nilai pengembalian nol dalam satu kasus dan bukan-nol di yang lain akan sesuai dengan tujuan saya.

Saya dapat menjalankan git statusdan menyalurkan output grep, tetapi saya merasa harus ada cara yang lebih baik.

Daniel Stutzbach
sumber

Jawaban:

289

UPDATE : OP Daniel Stutzbach menunjukkan dalam komentar bahwa perintah sederhana ini git diff-indexbekerja untuknya:

git update-index --refresh 
git diff-index --quiet HEAD --

( nornagon menyebutkan dalam komentar bahwa, jika ada file yang telah disentuh, tetapi yang isinya sama dengan dalam indeks, Anda harus menjalankannya git update-index --refreshsebelumnya git diff-index, jika tidak diff-indexsecara tidak benar akan melaporkan bahwa pohon itu kotor)

Anda kemudian dapat melihat " Bagaimana cara memeriksa apakah suatu perintah berhasil? " Jika Anda menggunakannya dalam skrip bash:

git diff-index --quiet HEAD -- || echo "untracked"; // do something about it

Catatan: sebagai dikomentari oleh Anthony Sottile

git diff-index HEAD ...akan gagal pada cabang yang tidak memiliki komitmen (seperti repositori yang baru diinisialisasi).
Satu solusi yang saya temukan adalahgit diff-index $(git write-tree) ...

Dan haridsvmenunjukkan dalam komentar bahwa git diff-filespada file baru tidak mendeteksinya sebagai diff.
Pendekatan yang lebih aman tampaknya adalah menjalankan git addspesifikasi file terlebih dahulu dan kemudian gunakan git diff-indexuntuk melihat apakah ada yang ditambahkan ke indeks sebelum dijalankangit commit .

git add ${file_args} && \
git diff-index --cached --quiet HEAD || git commit -m '${commit_msg}'

Dan 6502 laporan dalam komentar:

Satu masalah yang saya temui adalah yang git diff-indexakan memberi tahu bahwa ada perbedaan ketika memang tidak ada kecuali cap waktu file.
Larigit diff sekali memecahkan masalah (cukup mengejutkan, git diffsebenarnya mengubah isi kotak pasir, artinya di sini .git/index)

Masalah cap waktu ini juga dapat terjadi jika git berjalan di buruh pelabuhan .


Jawaban asli:

"Secara terprogram" berarti tidak pernah bergantung pada perintah porselen .
Selalu mengandalkan perintah pipa ledeng .

Lihat juga " Memeriksa indeks kotor atau file yang tidak dilacak dengan Git " untuk alternatif (seperti git status --porcelain)

Anda dapat mengambil inspirasi dari " require_clean_work_treefungsi " baru yang ditulis saat kita berbicara ;) (awal Oktober 2010)

require_clean_work_tree () {
    # Update the index
    git update-index -q --ignore-submodules --refresh
    err=0

    # Disallow unstaged changes in the working tree
    if ! git diff-files --quiet --ignore-submodules --
    then
        echo >&2 "cannot $1: you have unstaged changes."
        git diff-files --name-status -r --ignore-submodules -- >&2
        err=1
    fi

    # Disallow uncommitted changes in the index
    if ! git diff-index --cached --quiet HEAD --ignore-submodules --
    then
        echo >&2 "cannot $1: your index contains uncommitted changes."
        git diff-index --cached --name-status -r --ignore-submodules HEAD -- >&2
        err=1
    fi

    if [ $err = 1 ]
    then
        echo >&2 "Please commit or stash them."
        exit 1
    fi
}
VONC
sumber
12
Prinsip "plumbing vs porcelain for scripting" adalah pelajaran yang Jakub Narębski berulang kali katakan kepada saya: " Bagaimana cara membuat daftar semua log untuk proyek saat ini di git? ", " Git: changelog hari demi hari ", ...
VonC
18
Setelah mengklik beberapa link Anda sarankan, saya menemukan apa yang saya cari: git diff-index --quiet HEAD.
Daniel Stutzbach
11
@DanielStutzbach: Itu mungkin gagal jika Anda memiliki file yang dipanggil HEADdi direktori kerja. Lebih baik digunakan git diff-index --quiet HEAD --.
David Ongaro
7
Namun manual di git status --helpnegara: --porcelain Berikan output dalam format skrip yang mudah diurai. Ini mirip dengan keluaran pendek, tetapi akan tetap stabil di versi Git dan terlepas dari konfigurasi pengguna. Lihat di bawah untuk detailnya.
Ed Randall
7
@VonC benar-benar tidak masuk akal. Dengan cara ini Anda dapat memutar semuanya menjadi terbalik. --porcelain memberi Anda kesan itu akan segera hancur. Jika tidak, itu harus disebut pipa ledeng, bukan porselen. Menggunakan --porcelain menyebabkan skrip Anda tidak rusak, yang membuatnya BUKAN skrip porselen ;-). Jika Anda ingin skrip Anda rusak, jangan gunakan --porcelain !!. Jadi ini benar-benar tidak bisa dipahami dan membuat semua orang bingung.
Xennex81
104

Sementara solusi lain sangat menyeluruh, jika Anda menginginkan sesuatu yang sangat cepat dan kotor, coba sesuatu seperti ini:

[[ -z $(git status -s) ]]

Itu hanya memeriksa apakah ada output dalam ringkasan status.

Nepthar
sumber
7
bekerja untukku. gunakan -n untuk kebalikannya (Anda memiliki perubahan) misalnya `if [[-n $ (git status -s)]]; kemudian ... fi`
aaron
Ini berfungsi, tetapi dapatkah Anda memberi tahu apa yang [[ ... ]]sebenarnya dilakukan sintaks? Saya belum pernah melihat yang seperti itu sebelumnya.
GMA
2
@ EM kode pengembalian git statussebenarnya diabaikan dalam tes ini. Ini hanya terlihat pada output. Lihat halaman terkait bash ini untuk info lebih lanjut tentang [, [[dan bagaimana pengujian bekerja di bash.
Nepthar
2
Ini jawaban yang hampir benar, tetapi untuk skrip lebih baik menggunakan --porcelainparameter seperti yang ditunjukkan di sini
Mariusz Pawelski
2
Anda mungkin ingin menggunakan git status -s -ualluntuk memasukkan file yang tidak terlacak.
barfuin
59

git diff --exit-codeakan mengembalikan nol jika ada perubahan; git diff --quietsama dengan tanpa output. Karena Anda ingin memeriksa pohon kerja dan indeks, gunakan

git diff --quiet && git diff --cached --quiet

Atau

git diff --quiet HEAD

Salah satu akan memberi tahu Anda jika ada perubahan yang tidak dikomit yang dipentaskan atau tidak.

Josh Lee
sumber
6
Itu tidak setara. Perintah tunggal git diff --quite HEADhanya akan memberi tahu Anda apakah pohon yang bekerja bersih, bukan apakah indeksnya bersih. Misalnya, jika filediubah antara HEAD ~ dan HEAD, maka setelah git reset HEAD~ -- fileitu, ia akan tetap keluar 0 meskipun ada perubahan bertahap yang ada dalam indeks (wt == HEAD, tetapi index! = HEAD).
Chris Johnsen
2
Peringatan, ini tidak akan menangkap file yang dihapus dari area pementasan dengan git rm, AFAICS.
nmr
24
File baru (tidak terlacak) tidak terdeteksi oleh git diff --quiet && git diff --cached --quiet.
4LegsDrivenCat
17

Memperluas jawaban @ Nepthar:

if [[ -z $(git status -s) ]]
then
  echo "tree is clean"
else
  echo "tree is dirty, please commit changes before running this"
  exit
fi
Travis Reeder
sumber
1
Ini bagus; Saya menggunakannya untuk autocommit file tunggal dengan menguji $(git status -s "$file")dan kemudian dalam elseklausagit add "$file"; git commit -m "your autocommit process" "$file"
toddkaufmann
Jika Anda membuat itu git status -ssebagai git status --porcelain ; git clean -ndgantinya, direktori sampah akan muncul di sini juga, yang tidak terlihat git status.
ecmanaut
4

Seperti yang ditunjukkan dalam jawaban lain, sesederhana perintah seperti itu sudah cukup:

git diff-index --quiet HEAD --

Jika Anda menghilangkan dua tanda hubung terakhir, perintah akan gagal jika Anda memiliki file bernama HEAD .

Contoh:

#!/bin/bash
set -e
echo -n "Checking if there are uncommited changes... "
trap 'echo -e "\033[0;31mFAILED\033[0m"' ERR
git diff-index --quiet HEAD --
trap - ERR
echo -e "\033[0;32mAll set!\033[0m"

# continue as planned...

Kata peringatan: perintah ini mengabaikan file yang tidak terlacak.

sanmai
sumber
2
Seperti yang ditunjukkan dalam komentar untuk jawaban itu, ini tidak mendeteksi file yang baru ditambahkan
minexew
Tidak, itu mendeteksi baru ditambahkan ke file indeks. Baru diperiksa.
sanmai
Lihat pertanyaan. File yang tidak dilacak bukan perubahan . git adddan git cleanuntuk menyelamatkan
sanmai
4

Saya membuat beberapa alias git yang berguna untuk membuat daftar file yang tidak dipentaskan dan dipentaskan:

git config --global alias.unstaged 'diff --name-only'
git config --global alias.staged 'diff --name-only --cached'

Maka Anda dapat dengan mudah melakukan hal-hal seperti:

[[ -n "$(git unstaged)" ]] && echo unstaged files || echo NO unstaged files
[[ -n "$(git staged)" ]] && echo staged files || echo NO staged files

Anda dapat membuatnya lebih mudah dibaca dengan menciptakan suatu tempat script pada Anda PATHdisebut git-has:

#!/bin/bash
[[ $(git "$@" | wc -c) -ne 0 ]]

Sekarang contoh di atas dapat disederhanakan menjadi:

git has unstaged && echo unstaged files || echo NO unstaged files
git has staged && echo staged files || echo NO staged files

Untuk kelengkapan di sini adalah alias yang serupa untuk file yang tidak dilacak dan diabaikan:

git config --global alias.untracked 'ls-files --exclude-standard --others'
git config --global alias.ignored 'ls-files --exclude-standard --others --ignored'
stk
sumber
2

Dengan python dan paket GitPython:

import git
git.Repo(path).is_dirty(untracked_files=True)

Mengembalikan True jika repositori tidak bersih

Pablo
sumber
Ini menghindari beberapa masalah "cap waktu" yang disebutkan dalam komentar lain
Jason
1
Perhatikan bahwa GitPython juga menggunakan git CLI. Jika Anda mengatur, LOGLEVEL=DEBUGAnda akan melihat semua perintah Popen yang digunakan untuk menjalankangit diff
Jason
-3

Inilah cara terbaik dan terbersih.

function git_dirty {
    text=$(git status)
    changed_text="Changes to be committed"
    untracked_files="Untracked files"

    dirty=false

    if [[ ${text} = *"$changed_text"* ]];then
        dirty=true
    fi

    if [[ ${text} = *"$untracked_files"* ]];then
        dirty=true
    fi

    echo $dirty
}
codyc4321
sumber
4
Tidak, ini bukan yang terbaik. git statusadalah perintah 'porselen'. Jangan gunakan perintah porselen dalam skrip karena skrip dapat berubah di antara versi git. Alih-alih menggunakan perintah 'plumbing'.
Pengamen
3
Saya pikir jika Anda memperbaruinya untuk menggunakan git status --porcelain(yang dimaksudkan untuk tujuan ini - format stabil yang dapat Anda parsing dalam skrip), mungkin juga dengan -z (null-separated bukan newline?), Anda dapat melakukan sesuatu yang berguna dengan ide ini . @ codyc4321 lihat stackoverflow.com/questions/6976473/... untuk detail
msouth