Bagaimana cara grep (mencari) kode yang dilakukan dalam sejarah Git

1435

Saya telah menghapus file atau beberapa kode dalam suatu file di masa lalu. Bisakah saya memahami konten (bukan dalam pesan komit)?

Solusi yang sangat buruk adalah dengan mengambil log:

git log -p | grep <pattern>

Namun, ini tidak mengembalikan hash komit langsung. Saya bermain-main dengan git greptidak berhasil.

Ortwin Gentz
sumber
2
Posting blog ini oleh Junio ​​C Hamano (pengelola git) mungkin menarik bagi Anda: * Alat pelacak konten utama Linus (tentang pencarian beliung git log -Sdan kesalahan) * [Bersenang-senang dengan "git log --grep"] [2] (mencari pesan komit ) * [Bersenang-senang dengan "git grep"] [3] [2]: gitster.livejournal.com/30195.html [3]: gitster.livejournal.com/27674.html
Jakub Narębski
jawaban dari kemungkinan duplikat benar-benar berfungsi: stackoverflow.com/a/1340245/492
CAD bloke
masalah dengan ini adalah bahwa hal itu tidak memberikan konteks perubahan .. yaitu siapa / kapan
Sonic Soul

Jawaban:

1890

Untuk mencari konten komit (yaitu, baris sumber aktual, sebagai lawan dari komit pesan dan sejenisnya), Anda perlu melakukan:

git grep <regexp> $(git rev-list --all)

git rev-list --all | xargs git grep <expression> akan berfungsi jika Anda mengalami kesalahan "Daftar argumen terlalu panjang".

Jika Anda ingin membatasi pencarian hingga beberapa subtree (misalnya, "lib / util"), Anda harus meneruskannya ke rev-listsubkomand dan grepjuga:

git grep <regexp> $(git rev-list --all -- lib/util) -- lib/util

Ini akan mencakup semua teks komit Anda untuk regexp.

Alasan untuk melewati jalan di kedua perintah adalah karena rev-listakan mengembalikan daftar revisi tempat semua perubahan lib/utilterjadi, tetapi Anda juga harus meneruskan ke grepsehingga hanya akan mencari lib/util.

Bayangkan saja skenario berikut ini: grepmungkin menemukan hal yang sama <regexp>pada file lain yang terkandung dalam revisi yang sama dikembalikan oleh rev-list(bahkan jika tidak ada perubahan pada file pada revisi itu).

Berikut ini beberapa cara lain yang berguna untuk mencari sumber Anda:

Telusuri pohon kerja untuk pencocokan teks regexp ekspresi reguler:

git grep <regexp>

Telusuri pohon kerja untuk baris teks yang cocok dengan ekspresi reguler regexp1 atau regexp2:

git grep -e <regexp1> [--or] -e <regexp2>

Telusuri bagan yang berfungsi untuk baris teks yang cocok dengan ekspresi reguler regexp1 dan regexp2, hanya melaporkan jalur file:

git grep -l -e <regexp1> --and -e <regexp2>

Cari pohon yang berfungsi untuk file yang memiliki baris teks yang cocok dengan ekspresi reguler regexp1 dan baris teks yang cocok dengan ekspresi reguler regexp2:

git grep -l --all-match -e <regexp1> -e <regexp2>

Telusuri pohon kerja untuk perubahan garis pola pencocokan teks:

git diff --unified=0 | grep <pattern>

Cari semua revisi untuk pencocokan teks reguler regexp ekspresi:

git grep <regexp> $(git rev-list --all)

Cari semua revisi antara rev1 dan rev2 untuk teks yang cocok dengan regexp ekspresi reguler:

git grep <regexp> $(git rev-list <rev1>..<rev2>)
Jeet
sumber
61
Terima kasih, berhasil! Sangat menyedihkan bahwa "$ (git rev-list --all)" diperlukan dan tidak ada saklar yang mudah untuk menentukan pencarian di seluruh sejarah cabang.
Ortwin Gentz
3
Luar biasa. +1. GitBook menambahkan beberapa detail ( book.git-scm.com/4_finding_with_git_grep.html ), dan Junio ​​C Hamano mengilustrasikan beberapa poin Anda: gitster.livejournal.com/27674.html
VonC
18
Sayangnya, saya tidak bisa menjalankannya dengan msysgit-1.7.4. Itu memberitahuku sh.exe": /bin/git: Bad file number. Jawaban VonC juga berfungsi dengan msysgit.
eckes
4
Jika Anda mendapatkan kesalahan "tidak dapat membaca pohon" ketika Anda memohon git grep history dengan rev-list, Anda mungkin perlu membereskannya. Coba git gcatau periksa: stackoverflow.com/questions/1507463/…
Anthony Panozzo
8
Ya, ini sepertinya gagal pada Windows juga, sayangnya.
mlissner
552

Anda harus menggunakan opsi beliung ( -S) dari git log.

Untuk mencari Foo:

git log -SFoo -- path_containing_change
git log -SFoo --since=2009.1.1 --until=2010.1.1 -- path_containing_change

Lihat Riwayat Git - temukan baris yang hilang berdasarkan kata kunci untuk lebih banyak.


Seperti yang Jakub Narębski berkomentar:

  • ini mencari perbedaan yang memperkenalkan atau menghapus instance dari<string> . Biasanya berarti "revisi di mana Anda menambahkan atau menghapus baris dengan 'Foo'".

  • yang --pickaxe-regexmemungkinkan anda untuk menggunakan diperpanjang POSIX regex bukan mencari string. Contoh (dari git log):git log -S"frotz\(nitfol" --pickaxe-regex


Seperti yang dikomentari Rob , pencarian ini peka terhadap huruf besar-kecil - ia membuka pertanyaan lanjutan tentang bagaimana mencari tidak peka huruf besar-kecil.

VONC
sumber
3
Terima kasih, saya tidak mengetahui opsi ini. Sepertinya ini adalah solusi terbaik jika Anda tertarik pada pesan komit dan solusi Jeet paling tepat jika Anda memerlukan perilaku grep UNIX tradisional pencocokan garis murni.
Ortwin Gentz
@Ortwin: setuju (dan saya telah memutakhirkan solusi yang dipilih). yang git logsedikit dalam pertanyaan Anda telah saya bingung;)
VonC
12
Gabungkan dengan -pflag untuk juga mengeluarkan diff.
Sander
Apakah ada cara untuk mengecualikan semua direktori yang cocok dengan pola tertentu menggunakan git log -S?
BakaKuna
3
@Anentropic Anda memerlukan --branches --allopsi untuk mencari semua repo.
VonC
249

Cara favorit saya untuk melakukannya adalah dengan git log's -Gpilihan (ditambahkan pada versi 1.7.4).

-G<regex>
       Look for differences whose added or removed line matches the given <regex>.

Ada perbedaan tipis antara cara -Gdan -Sopsi menentukan apakah komit cocok:

  • The -Spilihan dasarnya menghitung jumlah kali pertandingan pencarian Anda dalam file sebelum dan setelah komit. Komit ditampilkan dalam log jika jumlah sebelum dan sesudah berbeda. Ini tidak akan, misalnya, menunjukkan komit di mana baris yang cocok dengan pencarian Anda dipindahkan.
  • Dengan -Gopsi tersebut, komit ditampilkan dalam log jika pencarian Anda cocok dengan baris apa pun yang ditambahkan, dihapus, atau diubah.

Ambil komit ini sebagai contoh:

diff --git a/test b/test
index dddc242..60a8ba6 100644
--- a/test
+++ b/test
@@ -1 +1 @@
-hello hello
+hello goodbye hello

Karena berapa kali "halo" muncul dalam file adalah sama sebelum dan setelah komit ini, itu tidak akan cocok dengan menggunakan -Shello. Namun, karena ada perubahan pada pencocokan garis hello, komit akan ditampilkan menggunakan -Ghello.

Tyler Holien
sumber
2
Apakah ada cara untuk menunjukkan konteks perubahan yang cocok dalam output log git?
Thilo-Alexander Ginkel
13
@ Thilo-AlexanderGinkel - Saya biasanya hanya menambahkan -popsi untuk menampilkan diff untuk setiap commit. Kemudian ketika log dibuka di pager saya, saya mencari apa pun yang saya cari. Jika pager lessAnda dan Anda git log -Ghello -p, Anda dapat mengetik /hello, tekan Enter, dan gunakan ndan Nuntuk menemukan "hello" kejadian berikutnya / sebelumnya.
Tyler Holien
Saya menemukan masalah yang menarik dengan -Gdan Regex: Jika baris perintah menggunakan UTF-8 dan file yang Anda lihat menggunakan beberapa pengkodean ISO-Latin (8 bit), .*gagal. Misalnya, saya punya perubahan Vierter Entwurf-> Fünfter Entwurf, dan saat 'V.*ter Entwurf'menghasilkan kecocokan, 'F.*ter Entwurf'tidak.
U. Windl
51

Jika Anda ingin menelusuri perubahan kode (lihat apa yang sebenarnya telah diubah dengan kata yang diberikan di seluruh riwayat) gunakan patchmode - Saya menemukan kombinasi yang sangat berguna untuk melakukan:

git log -p
# Hit '/' for search mode.
# Type in the word you are searching.
# If the first search is not relevant, hit 'n' for next (like in Vim ;) )
Bartek Skwira
sumber
11
Solusi yang diterima tidak bekerja untuk saya dan juga git log -S. Yang ini!
rodvlopes
29

git log bisa menjadi cara yang lebih efektif untuk mencari teks di semua cabang, terutama jika ada banyak kecocokan, dan Anda ingin melihat perubahan yang lebih baru (relevan) terlebih dahulu.

git log -p --all -S 'search string'
git log -p --all -G 'match regular expression'

Daftar perintah log ini berkomitmen untuk menambah atau menghapus string pencarian / regex yang diberikan, (umumnya) lebih baru terlebih dahulu. The -ppilihan menyebabkan diff relevan untuk ditampilkan di mana pola ditambahkan atau dihapus, sehingga Anda dapat melihatnya dalam konteks.

Setelah menemukan komit yang relevan yang menambahkan teks yang Anda cari (misalnya, 8beeff00d), temukan cabang yang berisi komit:

git branch -a --contains 8beeff00d
Edward Anderson
sumber
Hai, garis-garis ini sepertinya tidak berfungsi sama sekali. Perintah saya adalah> git log -p --all -S public string DOB {get; set; } = string.Empty; ' dan setiap kali saya mencoba menjalankannya, saya mendapatkan> fatal: argumen 'string' yang mendua: revisi yang tidak diketahui atau jalur yang tidak ada di pohon kerja. > Gunakan '-' untuk memisahkan jalur dari revisi, seperti ini:> 'git <command> [<revision> ...] - [<file> ...]'
user216652
@ user216652 Untuk beberapa alasan, 'kutipan tidak mengelompokkan string pencarian Anda menjadi satu argumen. Sebaliknya, 'publicadalah argumen untuk -S, dan itu memperlakukan sisanya sebagai argumen terpisah. Saya tidak yakin lingkungan apa yang Anda jalankan, tetapi konteks itu diperlukan untuk membantu memecahkan masalah. Saya sarankan membuka pertanyaan StackOverflow terpisah jika diperlukan untuk membantu Anda memecahkan masalah, dengan semua konteks tentang bagaimana perintah git Anda dikirim ke shell. Sepertinya saya dikirim melalui perintah lain? Komentar di sini bukan tempat yang tepat untuk mencari tahu hal ini.
Edward Anderson
26

Saya mengambil jawaban Jeet dan mengadaptasinya ke Windows (terima kasih atas jawaban ini ):

FOR /F %x IN ('"git rev-list --all"') DO @git grep <regex> %x > out.txt

Perhatikan bahwa bagi saya, untuk beberapa alasan, komit aktual yang menghapus regex ini tidak muncul di output perintah, melainkan komit sebelumnya.

ripper234
sumber
2
+1 - dan jika Anda ingin menghindari memukul "q" setelah setiap ditemukan, tambahkan --no-pagerke perintah git di akhir
cgp
2
Saya juga mencatat bahwa menambahkan file teks memiliki keuntungan tambahan untuk benar-benar menampilkan teks yang cocok. (tambahkan ke file teks menggunakan >>results.txtuntuk mereka yang tidak berpengalaman dalam Windows piping ...
cgp
1
Dan saya pikir sintaks bash jelek :)
smido
23

Cari dalam revisi apa pun, file apa pun :

git rev-list --all | xargs git grep <regexp>

Cari hanya di beberapa file yang diberikan, misalnya file XML:

git rev-list --all | xargs -I{} git grep <regexp> {} -- "*.xml"

Baris hasil akan terlihat seperti ini: 6988bec26b1503d45eb0b2e8a4364afb87dde7af: bla.xml: teks dari baris yang ditemukan ...

Anda kemudian dapat memperoleh informasi lebih lanjut seperti penulis, tanggal, dan beda menggunakan git show:

git show 6988bec26b1503d45eb0b2e8a4364afb87dde7af
Christophe Roussy
sumber
11

Untuk kesederhanaan, saya sarankan menggunakan GUI: gitk - Peramban repositori Git . Cukup fleksibel

  1. Untuk mencari kode:

    Masukkan deskripsi gambar di sini
  2. Untuk mencari file:

    Masukkan deskripsi gambar di sini
  3. Tentu saja, ini juga mendukung ekspresi reguler:

    Masukkan deskripsi gambar di sini

Dan Anda dapat menavigasi hasil menggunakan panah atas / bawah.

watashiSHUN
sumber
6

Bagi siapa pun yang mencoba melakukan ini di Sourcetree , tidak ada perintah langsung di UI untuk itu (pada versi 1.6.21.0). Namun, Anda dapat menggunakan perintah yang ditentukan dalam jawaban yang diterima dengan membuka jendela Terminal (tombol yang tersedia di bilah alat utama) dan menyalin / menempelkannya di sana.

Catatan: Tampilan Pencarian Sourcetree sebagian dapat melakukan pencarian teks untuk Anda. Tekan Ctrl+ 3untuk pergi ke tampilan Pencarian (atau klik tab Cari yang tersedia di bagian bawah). Dari paling kanan, atur jenis Pencarian ke Perubahan File dan kemudian ketik string yang ingin Anda cari. Metode ini memiliki batasan berikut dibandingkan dengan perintah di atas:

  1. Sourcetree hanya menampilkan komit yang berisi kata pencarian di salah satu file yang diubah. Menemukan file persis yang berisi teks pencarian lagi merupakan tugas manual.
  2. RegEx tidak didukung.
dotNET
sumber
4

Setiap kali saya menemukan diri saya di tempat Anda, saya menggunakan baris perintah berikut:

git log -S "<words/phrases i am trying to find>" --all --oneline  --graph

Penjelasan:

  1. git log- Perlu saya tulis lebih banyak di sini; itu menunjukkan log dalam urutan kronologis.
  2. -S "<words/phrases i am trying to find>" - Ini menunjukkan semua komit Git di mana file apa pun (ditambahkan / diubah / dihapus) memiliki kata / frasa yang saya coba temukan tanpa simbol '<>'.
  3. --all - Untuk menegakkan dan mencari di semua cabang.
  4. --oneline - Ini memampatkan Git log dalam satu baris.
  5. --graph - Ini menciptakan grafik komit yang dipesan secara kronologis.
surajs1n
sumber
1
"Setiap kali aku menemukan diriku di tempatmu, aku merasa perlu menggunakan git!"
Sebi
1
Ini jawaban yang bagus!
Alf Eaton
@AlfEaton kesenangan saya!
surajs1n
2

Jawaban Jeet bekerja di PowerShell.

git grep -n <regex> $(git rev-list --all)

Berikut ini menampilkan semua file, dalam komit apa pun, yang berisi a password.

# Store intermediate result
$result = git grep -n "password" $(git rev-list --all)

# Display unique file names
$result | select -unique { $_ -replace "(^.*?:)|(:.*)", "" }
Shaun Luttin
sumber
1

Jadi, apakah Anda mencoba membaca versi kode yang lebih lama untuk melihat di mana ada sesuatu yang terakhir?

Jika saya melakukan ini, saya mungkin akan menggunakan git bisect . Dengan menggunakan dua bagian, Anda dapat menentukan versi yang dikenal baik, versi buruk yang diketahui, dan skrip sederhana yang melakukan pemeriksaan untuk melihat apakah versi tersebut baik atau buruk (dalam hal ini grep untuk melihat apakah kode yang Anda cari ada. ). Menjalankan ini akan menemukan ketika kode itu dihapus.

Rob Di Marco
sumber
2
Ya, tetapi "tes" Anda dapat berupa skrip yang memahami kode dan mengembalikan "benar" jika kode ada dan "salah" jika tidak.
Rob Di Marco
2
Nah, bagaimana jika kode buruk di revisi 10, menjadi baik di revisi 11 dan menjadi buruk lagi di revisi 15 ...
Paolo
2
Saya setuju dengan Paolo. Pencarian biner hanya sesuai untuk nilai "dipesan". Dalam kasus git bisect, ini berarti semua revisi "baik" muncul sebelum semua revisi "buruk", dimulai dari titik referensi, tetapi asumsi itu tidak dapat dibuat ketika mencari kode sementara. Solusi ini mungkin berfungsi dalam beberapa kasus, tetapi itu bukan solusi tujuan umum yang baik.
Kent
Saya pikir ini sangat tidak efisien karena seluruh pohon diperiksa beberapa kali untuk dua bagian.
U. Windl
0

Skenario: Anda melakukan pembersihan besar-besaran terhadap kode Anda dengan menggunakan IDE Anda. Masalah: IDE membersihkan lebih dari yang seharusnya dan sekarang kode Anda tidak dikompilasi (sumber daya hilang, dll.)

Larutan:

git grep --cached "text_to_find"

Ini akan menemukan file tempat "text_to_find" diubah.

Anda sekarang dapat membatalkan perubahan ini dan mengkompilasi kode Anda.

Garytech
sumber
0
git rev-list --all | xargs -n 5 git grep EXPRESSION

adalah tweak untuk solusi Jeet , jadi ini menunjukkan hasil saat pencarian dan bukan hanya di akhir (yang bisa memakan waktu lama dalam repositori besar).

laktak
sumber
-1

Dalam kasus saya, saya perlu mencari komit pendek dan sayangnya solusi yang tercantum tidak berfungsi.

Saya berhasil melakukannya dengan (ganti token REGEX ):

for commit in $(git rev-list --all --abbrev-commit)
do
    if [[ $commit =~ __REGEX__ ]]; then 
        git --no-pager show -s --format='%h %an - %s' $commit
    fi
done
pengguna9869932
sumber