Memeriksa indeks yang kotor atau file yang tidak dilacak dengan Git

243

Bagaimana saya bisa mengecek apakah saya memiliki perubahan yang tidak dikomit di repositori git saya:

  1. Perubahan ditambahkan ke indeks tetapi tidak dilakukan
  2. File yang tidak dilacak

dari naskah?

git-status tampaknya selalu mengembalikan nol dengan git versi 1.6.4.2.

Robert Munteanu
sumber
3
status git akan mengembalikan 1 jika ada file yang dimodifikasi tidak dipentaskan. Tetapi secara umum, saya menemukan bahwa alat git tidak terlalu teliti dengan status pengembalian. EG git diff menghasilkan 0 apakah ada perbedaan atau tidak.
intuited
11
@intuited: Jika Anda perlu diffmenunjukkan ada atau tidaknya perbedaan daripada keberhasilan menjalankan perintah maka Anda perlu menggunakan --exit-codeatau --quiet. Perintah git umumnya sangat konsisten dengan mengembalikan kode keluar nol atau tidak nol untuk menunjukkan keberhasilan perintah.
CB Bailey
1
@ Charles Bailey: Hai, bagus, saya melewatkan opsi itu. Terima kasih! Kurasa aku tidak pernah benar-benar membutuhkannya untuk melakukan itu, atau aku mungkin akan menjelajahi halaman manual untuk itu. Senang Anda memperbaiki saya :)
intuited
1
robert - ini adalah topik yang sangat membingungkan bagi masyarakat (tidak membantu 'porselen' digunakan secara berbeda dalam konteks yang berbeda). Jawaban yang Anda pilih mengabaikan jawaban 2,5 kali lebih tinggi yang lebih kuat, dan mengikuti desain git. Sudahkah Anda melihat jawaban @ChrisJ?
mikrofon
1
@ Robert - Saya berharap Anda dapat mempertimbangkan untuk mengubah jawaban yang Anda terima. Yang Anda pilih bergantung pada perintah 'porselen' git yang lebih rapuh; jawaban yang benar sebenarnya (juga dengan 2x upvotes) bergantung pada perintah 'plumbing' git yang benar. Jawaban yang benar itu awalnya oleh Chris Johnsen. Saya membahas hal ini karena hari ini adalah yang ketiga kalinya saya harus merujuk seseorang ke halaman ini, tetapi saya tidak bisa hanya menunjuk ke jawabannya, saya telah menjelaskan mengapa jawaban yang diterima salah / batas yang salah. Terima kasih!
mike

Jawaban:

173

Waktu yang tepat! Saya menulis posting blog tentang hal ini beberapa hari yang lalu, ketika saya menemukan cara untuk menambahkan informasi status git ke prompt saya.

Inilah yang saya lakukan:

  1. Untuk status kotor:

    # Returns "*" if the current git branch is dirty.
    function evil_git_dirty {
      [[ $(git diff --shortstat 2> /dev/null | tail -n1) != "" ]] && echo "*"
    }
  2. Untuk file yang tidak dilacak (Perhatikan --porcelaintanda git statusyang memberi Anda hasil parse -able yang bagus):

    # Returns the number of untracked files
    
    function evil_git_num_untracked_files {
      expr `git status --porcelain 2>/dev/null| grep "^??" | wc -l` 
    }

Meskipun git diff --shortstatlebih nyaman, Anda juga dapat menggunakan git status --porcelainuntuk mendapatkan file kotor:

# Get number of files added to the index (but uncommitted)
expr $(git status --porcelain 2>/dev/null| grep "^M" | wc -l)

# Get number of files that are uncommitted and not added
expr $(git status --porcelain 2>/dev/null| grep "^ M" | wc -l)

# Get number of total uncommited files
expr $(git status --porcelain 2>/dev/null| egrep "^(M| M)" | wc -l)

Catatan: 2>/dev/nullFilter menyaring pesan kesalahan sehingga Anda dapat menggunakan perintah ini pada direktori non-git. (Mereka hanya akan kembali 0untuk jumlah file.)

Edit :

Inilah pos-posnya:

Menambahkan Informasi Status Git ke Prompt Terminal Anda

Prompt Shell yang diaktifkan dengan Git

0xfe
sumber
8
Perlu dicatat bahwa penyelesaian git bash dilengkapi dengan fungsi shell untuk melakukan hampir semua yang Anda lakukan dengan prompt Anda - __git_ps1. Ini menunjukkan nama-nama cabang, termasuk perlakuan khusus jika Anda sedang dalam proses rebase, sedang mendaftar, bergabung, atau membagi dua. Dan Anda dapat mengatur variabel lingkungan GIT_PS1_SHOWDIRTYSTATEuntuk mendapatkan tanda bintang untuk perubahan yang tidak dipentaskan dan plus untuk perubahan yang dipentaskan. (Saya pikir Anda juga bisa mendapatkannya untuk menunjukkan file yang tidak terlacak, dan memberi Anda beberapa git-describeoutput)
Cascabel
8
Peringatan: git diff --shortstatakan memberikan false negative jika perubahan sudah ada dalam indeks.
Marko Topolnik
4
git status --porcelainlebih disukai, karena git diff --shortstattidak akan menangkap file kosong yang baru dibuat. Anda dapat mencobanya di setiap pohon yang bersih:touch foo && git diff --shortstat
Campadrenalin
7
TIDAK - porselen berarti hasilnya untuk manusia dan mudah rusak !! lihat jawaban @ChrisJohnsen, yang dengan benar menggunakan opsi stabil, ramah skrip.
mike
7
@mike dari halaman manual git-status tentang opsi porselen: "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. "
itsadok
399

Kunci untuk “scripting” Git yang andal adalah menggunakan perintah 'plumbing'.

Pengembang berhati-hati ketika mengubah perintah plumbing untuk memastikan mereka menyediakan antarmuka yang sangat stabil (yaitu kombinasi yang diberikan keadaan repositori, stdin, opsi baris perintah, argumen, dll. Akan menghasilkan output yang sama di semua versi Git di mana perintah / opsi ada). Variasi keluaran baru dalam perintah plumbing dapat diperkenalkan melalui opsi baru, tetapi itu tidak dapat memperkenalkan masalah untuk program yang telah ditulis terhadap versi yang lebih lama (mereka tidak akan menggunakan opsi baru, karena tidak ada (atau setidaknya ada tidak digunakan) pada saat skrip ditulis).

Sayangnya perintah Git 'sehari-hari' adalah perintah 'porselen', sehingga sebagian besar pengguna Git mungkin tidak terbiasa dengan perintah pipa ledeng. Perbedaan antara perintah porselen dan pipa ledeng dibuat di halaman utama git (lihat subbagian berjudul Perintah tingkat tinggi (porselen) dan perintah tingkat rendah (pipa ledeng) .


Untuk mengetahui tentang perubahan yang tidak dikomit, Anda mungkin perlu git diff-index(membandingkan indeks (dan mungkin melacak bit pohon kerja) dengan beberapa pohon lain (misalnya HEAD)), mungkin git diff-files(membandingkan pohon kerja dengan indeks), dan mungkin git ls-files(daftar file, misalnya daftar tidak terlacak , file tidak ditandai).

(Perhatikan bahwa dalam perintah di bawah ini, HEAD --digunakan sebagai ganti HEADkarena jika tidak, perintah gagal jika ada file bernama HEAD.)

Untuk memeriksa apakah repositori telah melakukan perubahan (belum berkomitmen) gunakan ini:

git diff-index --quiet --cached HEAD --
  • Jika keluar dengan 0maka tidak ada perbedaan ( 1berarti ada perbedaan).

Untuk memeriksa apakah pohon yang bekerja memiliki perubahan yang dapat dipentaskan:

git diff-files --quiet
  • Kode keluar sama dengan untuk git diff-index( 0== tidak ada perbedaan; 1== perbedaan).

Untuk memeriksa apakah kombinasi indeks dan file yang dilacak di pohon kerja memiliki perubahan sehubungan dengan HEAD:

git diff-index --quiet HEAD --
  • Ini seperti kombinasi dari dua sebelumnya. Satu perbedaan utama adalah bahwa itu masih akan melaporkan "tidak ada perbedaan" jika Anda memiliki perubahan bertahap bahwa Anda telah "membatalkan" di pohon kerja (kembali ke konten yang ada di HEAD). Dalam situasi yang sama ini, kedua perintah yang terpisah akan menghasilkan laporan "perbedaan yang ada".

Anda juga menyebutkan file yang tidak dilacak. Anda mungkin berarti "tidak terlacak dan tidak ditandai", atau Anda mungkin berarti "tidak terlacak" (termasuk file yang diabaikan). Apa pun itu, git ls-filesadalah alat untuk pekerjaan itu:

Untuk "tidak terlacak" (akan menyertakan file yang diabaikan, jika ada):

git ls-files --others

Untuk "tidak terlacak dan tidak ditandai":

git ls-files --exclude-standard --others

Pikiran pertama saya adalah memeriksa apakah perintah ini memiliki output:

test -z "$(git ls-files --others)"
  • Jika keluar dengan 0maka tidak ada file yang tidak terlacak. Jika keluar 1maka ada file yang tidak terlacak.

Ada kemungkinan kecil bahwa ini akan menerjemahkan keluar abnormal dari git ls-filesmenjadi "tidak ada file terlacak" laporan (keduanya menghasilkan keluar tidak nol dari perintah di atas). Versi yang sedikit lebih kuat mungkin terlihat seperti ini:

u="$(git ls-files --others)" && test -z "$u"
  • Idenya sama dengan perintah sebelumnya, tetapi memungkinkan kesalahan yang tidak terduga git ls-filesuntuk menyebar. Dalam hal ini, jalan keluar yang tidak nol dapat berarti “ada file yang tidak dilacak” atau itu bisa berarti kesalahan terjadi. Jika Anda ingin hasil "kesalahan" dikombinasikan dengan hasil "tidak ada file yang tidak terlacak", gunakan test -n "$u"(di mana keluar 0berarti "beberapa file yang tidak terlacak", dan bukan-nol berarti kesalahan atau "tidak ada file yang tidak dilacak").

Gagasan lain adalah menggunakan --error-unmatchuntuk menyebabkan keluar non-nol ketika tidak ada file yang tidak terlacak. Ini juga berisiko menyatukan "tidak ada file yang tidak dilacak" (keluar 1) dengan "terjadi kesalahan" (keluar bukan nol, tapi mungkin 128). Tetapi memeriksa untuk kode keluar 0vs. 1vs bukan nol mungkin cukup kuat:

git ls-files --others --error-unmatch . >/dev/null 2>&1; ec=$?
if test "$ec" = 0; then
    echo some untracked files
elif test "$ec" = 1; then
    echo no untracked files
else
    echo error from ls-files
fi

Salah satu git ls-filescontoh di atas dapat diambil --exclude-standardjika Anda ingin mempertimbangkan hanya file yang tidak terlacak dan tidak di-retensi.

Chris Johnsen
sumber
5
Saya ingin menunjukkan bahwa git ls-files --othersmemberikan file lokal tidak terlacak, sedangkan git status --porcelainjawaban yang diterima memberikan semua file tidak terlacak yang berada di bawah repositori git. Saya bukan yang mana dari poster asli yang diinginkan, tetapi perbedaan antara keduanya menarik.
Eric O Lebigot
1
@ phunehehe: Anda perlu menyediakan pathspec --error-unmatch. Coba (misalnya) git ls-files --other --error-unmatch --exclude-standard .(catat periode trailing, ini merujuk pada cwd; jalankan ini dari direktori tingkat atas pohon yang bekerja).
Chris Johnsen
8
@ phs: Anda mungkin perlu melakukan git update-index -q --refreshsebelum diff-indexuntuk menghindari beberapa "false positive" yang disebabkan oleh ketidakcocokan stat (2) informasi.
Chris Johnsen
1
Saya digigit oleh git update-indexkebutuhan! Ini penting jika ada sesuatu yang menyentuh file tanpa membuat modifikasi.
Nakedible
2
@RobertSiemer Dengan "local", saya maksudkan file di bawah direktori Anda saat ini , yang mungkin di bawah repositori git utamanya. The --porcelaindaftar solusi semua file yang tidak terlacak ditemukan di seluruh git repositori (file dikurangi git-diabaikan), bahkan jika Anda berada di dalam salah satu subdirektorinya.
Eric O Lebigot
139

Dengan asumsi Anda menggunakan git 1.7.0 atau lebih baru ...

Setelah membaca semua jawaban di halaman ini dan beberapa percobaan, saya pikir metode yang menyentuh kombinasi yang tepat antara kebenaran dan singkatnya adalah:

test -n "$(git status --porcelain)"

Sementara git memungkinkan banyak nuansa antara apa yang dilacak, abaikan, tidak terlacak tetapi tidak ditandai, dan seterusnya, saya percaya kasus penggunaan yang umum adalah untuk mengotomatiskan skrip pembuatan, di mana Anda ingin menghentikan semuanya jika checkout Anda tidak bersih.

Dalam hal ini, masuk akal untuk mensimulasikan apa yang akan dilakukan programmer: ketik git statusdan lihat outputnya. Tetapi kami tidak ingin mengandalkan kata-kata tertentu yang muncul, jadi kami menggunakan --porcelainmode yang diperkenalkan di 1.7.0; ketika diaktifkan, direktori bersih tidak menghasilkan output.

Kemudian kita gunakan test -nuntuk melihat apakah ada output atau tidak.

Perintah ini akan mengembalikan 1 jika direktori kerja bersih dan 0 jika ada perubahan yang harus dilakukan. Anda dapat mengubah -nke -zjika Anda menginginkan yang sebaliknya. Ini berguna untuk merantai ini ke perintah dalam skrip. Sebagai contoh:

test -z "$(git status --porcelain)" || red-alert "UNCLEAN UNCLEAN"

Ini secara efektif mengatakan "baik tidak ada perubahan yang dibuat atau mematikan alarm"; one-liner ini mungkin lebih disukai daripada jika-pernyataan tergantung pada skrip yang Anda tulis.

benzado
sumber
1
Bagi saya semua perintah lain memberikan hasil berbeda pada rep yang sama antara Linux dan windows. Perintah ini memberi saya output yang sama di keduanya.
Adarsha
6
Terima kasih telah menjawab pertanyaan dan tidak terus mengoceh, tidak pernah memberikan jawaban yang jelas.
NateS
Untuk skrip penerapan manual, gabungkan ini dengan test -n "$(git diff origin/$branch)", untuk membantu mencegah komitmen lokal agar tidak diizinkan dalam penerapan
Erik Aronesty
14

Implementasi dari jawaban VonC :

if [[ -n $(git status --porcelain) ]]; then echo "repo is dirty"; fi
Dean Sebaliknya
sumber
8

Telah melihat beberapa jawaban ini ... (dan memiliki berbagai masalah pada * nix dan windows, yang merupakan persyaratan yang saya miliki) ... menemukan berikut ini berfungsi dengan baik ...

git diff --no-ext-diff --quiet --exit-code

Untuk memeriksa kode keluar di * nix

echo $?   
#returns 1 if the repo has changes (0 if clean)

Untuk memeriksa kode keluar di jendela $

echo %errorlevel% 
#returns 1 if the repos has changes (0 if clean) 

Bersumber dari https://github.com/sindresorhus/pure/issues/115 Terima kasih kepada @paulirish pada pos itu untuk berbagi

mlo55
sumber
4

Mengapa tidak merangkum ' git statusdengan skrip yang:

  • akan menganalisis output dari perintah itu
  • akan mengembalikan kode kesalahan yang sesuai berdasarkan apa yang Anda butuhkan

Dengan begitu, Anda dapat menggunakan status 'yang disempurnakan' itu dalam skrip Anda.


Seperti yang 0xfe sebutkan dalam jawabannya yang luar biasa , git status --porcelainsangat berperan dalam solusi berbasis skrip apa pun

--porcelain

Berikan hasil dalam format skrip yang stabil dan mudah diurai.
Saat ini identik dengan --short output, tetapi dijamin tidak berubah di masa depan, membuatnya aman untuk skrip.

VONC
sumber
Karena saya malas, mungkin. Saya pikir ada built-in untuk ini, karena tampaknya kasus penggunaan yang cukup sering ditemui.
Robert Munteanu
Saya memposting solusi berdasarkan saran Anda, meskipun saya tidak terlalu puas dengannya.
Robert Munteanu
4

Satu kemungkinan DIY, diperbarui untuk mengikuti saran 0xfe

#!/bin/sh
exit $(git status --porcelain | wc -l) 

Seperti dicatat oleh Chris Johnsen , ini hanya bekerja pada Git 1.7.0 atau lebih baru.

Robert Munteanu
sumber
6
Masalahnya adalah Anda tidak dapat mengharapkan string 'direktori kerja bersih' dalam versi yang akan datang. Bendera --porcelain dimaksudkan untuk penguraian, jadi solusi yang lebih baik adalah: keluar $ (status git --porcelain | wc -l)
0xfe
@ 0xfe - Tahukah Anda kapan --porcelainbendera ditambahkan? Tidak bekerja dengan 1.6.4.2.
Robert Munteanu
@ Robert: coba kalau git status --shortbegitu.
VonC
3
git status --porcelaindan git status --shortkeduanya diperkenalkan pada 1.7.0. --porcelainItu diperkenalkan khusus untuk memungkinkan git status --shortuntuk memvariasikan formatnya di masa depan. Jadi, git status --shortakan mengalami masalah yang sama seperti git status(output dapat berubah sewaktu-waktu karena itu bukan perintah 'plumbing').
Chris Johnsen
@ Chris, terima kasih atas informasi latar belakangnya. Saya telah memperbarui jawaban untuk mencerminkan cara terbaik untuk melakukan ini pada Git 1.7.0.
Robert Munteanu
2

Saya cukup sering membutuhkan cara sederhana untuk gagal membangun jika pada akhir eksekusi ada file yang dilacak yang dimodifikasi atau file yang tidak terlacak yang tidak diabaikan.

Ini cukup penting untuk menghindari kasus di mana build menghasilkan sisa makanan.

Sejauh ini, perintah terbaik yang saya gunakan adalah:

 test -z "$(git status --porcelain | tee /dev/fd/2)" || \
     {{ echo "ERROR: git unclean at the end, failing build." && return 1 }}

Ini mungkin terlihat agak rumit dan saya akan sangat menghargai jika ada yang menemukan varian singkat bahwa ini mempertahankan perilaku yang diinginkan:

  • tidak ada output dan kode keluar sukses jika semuanya beres
  • kode 1 jika gagal
  • pesan kesalahan pada stderr menjelaskan mengapa gagal
  • tampilkan daftar file yang menyebabkan kegagalan, stderr lagi.
Sorin
sumber
2

@ eduard-wirch jawabannya cukup lengkap, tetapi karena saya ingin memeriksa keduanya sekaligus, inilah varian terakhir saya.

        set -eu

        u="$(git ls-files --others)"
        if ! git diff-index --name-only --quiet HEAD -- || [ -z "${u:-}" ]; then
            dirty="-dirty"
        fi

Ketika tidak mengeksekusi menggunakan set -e atau yang setara, kita malah bisa melakukan u="$(git ls-files --others)" || exit 1(atau mengembalikan jika ini berfungsi untuk fungsi yang digunakan)

Jadi untracked_files, hanya disetel jika perintah berhasil dengan benar.

setelah itu, kita dapat memeriksa kedua properti, dan mengatur variabel (atau apa pun).

oliver
sumber
1

Ini adalah variasi yang lebih ramah shell untuk mengetahui apakah ada file yang tidak dilacak ada di repositori:

# Works in bash and zsh
if [[ "$(git status --porcelain 2>/dev/null)" = *\?\?* ]]; then
  echo untracked files
fi

Ini tidak memotong proses kedua grep,, dan tidak perlu memeriksa apakah Anda berada di repositori git atau tidak. Yang berguna untuk prompt shell, dll.

docwhat
sumber
1

Anda juga bisa melakukannya

git describe --dirty

. Ini akan menambahkan kata "-dirty" di akhir jika mendeteksi pohon yang bekerja kotor. Menurut git-describe(1):

   --dirty[=<mark>]
       Describe the working tree. It means describe HEAD and appends <mark> (-dirty by default) if
       the working tree is dirty.

. Peringatan: file yang tidak dilacak tidak dianggap "kotor", karena, seperti yang dinyatakan dalam manual, itu hanya peduli pada pohon yang berfungsi.

Linus Arver
sumber
0

Mungkin ada kombinasi yang lebih baik dari jawaban dari thread ini .. tapi karya-karya ini bagi saya ... untuk Anda .gitconfig's [alias]bagian ...

          # git untracked && echo "There are untracked files!"
untracked = ! git status --porcelain 2>/dev/null | grep -q "^??"
          # git unclean && echo "There are uncommited changes!"
  unclean = ! ! git diff --quiet --ignore-submodules HEAD > /dev/null 2>&1
          # git dirty && echo "There are uncommitted changes OR untracked files!"
    dirty = ! git untracked || git unclean
Alex Gray
sumber
0

Tes otomatis paling sederhana yang saya gunakan untuk mendeteksi keadaan kotor = perubahan apa pun termasuk file yang tidak terlacak :

git add --all
git diff-index --exit-code HEAD

CATATAN:

  • Tanpa add --all diff-indextidak melihat file yang tidak terlacak.
  • Biasanya, saya menjalankan git resetsetelah menguji kode kesalahan untuk kembali panggung semuanya.
uvsmtid
sumber
Pertanyaannya secara khusus "dari skrip" ... itu bukan ide yang baik untuk mengubah indeks hanya untuk menguji keadaan kotor.
ScottJ
@ Esccott, ketika itu memecahkan masalah, tidak semua orang harus tegas apakah indeks dapat dimodifikasi atau tidak. Pertimbangkan kasus pekerjaan otomatis tentang sumber tambalan otomatis dengan nomor versi dan buat tag - yang perlu Anda pastikan adalah sama sekali tidak ada modifikasi lokal lain yang menghalangi (terlepas apakah mereka ada dalam indeks atau tidak). Sejauh ini, ini adalah tes yang dapat diandalkan untuk setiap perubahan termasuk file yang tidak terlacak .
uvsmtid
-3

Inilah cara terbaik dan terbersih. Jawaban yang dipilih tidak berfungsi untuk saya karena beberapa alasan, itu tidak mengambil perubahan yang dipentaskan yang merupakan file baru yang tidak dilakukan.

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