Bagaimana cara menemukan komit berikutnya di git? (anak / anak-anak ref)

236

ref^mengacu pada komit sebelumnya ref, bagaimana dengan mendapatkan komit setelah ref ?

Misalnya, jika saya git checkout 12345bagaimana cara memeriksa komit berikutnya?

Terima kasih.

PS Ya, git adalah pohon pointer DAG pointer struct apa pun. Bagaimana saya menemukan komit setelah ini?

Schwern
sumber
2
Lihat juga " git children-of"!
VonC

Jawaban:

185

Untuk membuat daftar semua komit, mulai dari yang sekarang, dan kemudian anaknya, dan seterusnya - pada dasarnya git log standar, tetapi sebaliknya, gunakan sesuatu seperti

git log --reverse --ancestry-path 894e8b4e93d8f3^..master

di mana 894e8b4e93d8f3 adalah komit pertama yang ingin Anda tunjukkan.

Tim Hunt
sumber
3
Untuk kasus tertentu dalam pertanyaan awal, hanya pengganti HEAD^untuk 894e8b4e93d8f3^.
Søren Løvborg
2
Mungkin, tambahkan --oneline adalah hasil keluaran singkat yang lebih baik.
firo
1
Saya perlu menggunakan ..., ^..gagal diam
TankorSmash
1
Memperoleh fatal: unrecognized argument: --ancestry-pathgit versi 1.7.1
user151841
6
Ini hanya akan berfungsi jika masterberada di jalur leluhur dari komit saat ini. Lihat cuplikan kode kedua jawaban saya untuk solusi yang akan berfungsi dalam semua kasus.
Tom Hale
37

Pembuat Hudson (sekarang Jenkins) Kohsuke Kawaguchi baru saja menerbitkan (November 2013):
kohsuke / git-children-of :

Diberi komitmen, cari anak-anak langsung dari komitmen itu.

#!/bin/bash -e
# given a commit, find immediate children of that commit.
for arg in "$@"; do
  for commit in $(git rev-parse $arg^0); do
    for child in $(git log --format='%H %P' --all | grep -F " $commit" | cut -f1 -d' '); do
      git describe $child
    done
  done
done

Seperti yang diilustrasikan oleh utas ini , dalam VCS berdasarkan pada sejarah yang diwakili oleh DAG (Directed Acyclic Graph) , tidak ada "satu orangtua" atau "satu anak".

        C1 -> C2 -> C3
      /               \
A -> B                  E -> F
      \               /
        D1 -> D2 ----/

Pemesanan komit dilakukan oleh "topo-order" atau "date-order" (lihat buku GitPro )

Tetapi sejak Git1.6.0 , Anda dapat mendaftar anak-anak dari sebuah komit.

git rev-list --children
git log --children

Catatan: untuk komit induk , Anda memiliki masalah yang sama, dengan akhiran ^ke parameter revisi yang berarti induk pertama dari objek komit tersebut. ^<n>berarti <n>induk th (yaitu rev^ setara dengan rev^1).

Jika Anda berada di cabang foodan mengeluarkan " git merge bar" maka fooakan menjadi orang tua pertama.
Yaitu: Orang tua pertama adalah cabang tempat Anda bergabung saat Anda bergabung, dan yang kedua adalah komit di cabang tempat Anda bergabung.

VONC
sumber
6
git rev-list --childrententu terlihat seperti apa yang saya inginkan, tetapi tidak DWIM. Tampaknya daftar semua orang tua dan anak-anak mereka. Saya kira saya bisa daftar mereka semua dan menguraikannya ... bleh, tapi itu sesuatu.
Schwern
@ Schwern: benar, git rev-list --childrenbukan untuk daftar anak-anak saja, tetapi untuk daftar orang tua dengan anak-anak mereka ... Anda selalu perlu menguraikan.
VonC
Saya mencoba kode itu pada komitmen dengan dua anak: $ git children0of 9dd5932 fatal: No annotated tags can describe '71d7b5dd89d241072a0a078ff2c7dfec05d52e1f'. However, there were unannotated tags: try --tags. Output apa yang Anda dapatkan?
Tom Hale
@ TomHale Saya tidak bisa mengujinya sekarang, tetapi ajukan pertanyaan baru (dengan OS dan versi Git), dengan begitu semua orang bisa mengujinya.
VonC
1
Satu-satunya masalah dengan git-children-ofitu menggunakan git menggambarkan bahwa upaya untuk memformat SHA sebagai yang dapat dibaca manusia, yang dapat gagal dengan kesalahan @ TomHale, dan memberikan hasil seperti v1.0.4-14-g2414721yang membingungkan jika Anda mengharapkan SHA. Menggantinya dengan yang sederhana echomenjadikan alat ini luar biasa, terima kasih!
Nickolay
18

apa yang saya temukan adalah

git rev-list --ancestry-path commit1..commit2

di mana saya menetapkan commit1sebagai komit saat ini dan commit2ke kepala saat ini. Ini mengembalikan saya daftar semua komit yang membangun jalur antara commit1dan commit2.

Baris terakhir dari output adalah anak dari commit1 (di jalan menuju commit2).

white_gecko
sumber
3
Jadi tambahkan saja | tail -1untuk mendapatkan anak.
Jesse Glick
8

Aku tahu apa yang kamu maksud. Sangat frustasi memiliki sintaks yang berlimpah untuk pergi ke komit sebelumnya, tetapi tidak ada yang pergi ke yang berikutnya. Dalam sejarah yang kompleks, masalah "apa komitmen selanjutnya" menjadi agak sulit, tetapi kemudian dalam penggabungan yang rumit kekerasan yang sama muncul dengan komitmen 'sebelumnya' juga. Dalam kasus sederhana, di dalam cabang tunggal dengan sejarah linier (bahkan hanya secara lokal untuk beberapa komit) akan lebih baik dan masuk akal untuk maju dan mundur.

Masalah sebenarnya dengan ini, bagaimanapun, adalah bahwa anak-anak yang melakukan tidak dirujuk, itu hanya daftar yang terhubung ke belakang. Menemukan komit anak membutuhkan pencarian, yang tidak terlalu buruk, tetapi mungkin bukan sesuatu yang git ingin masukkan ke dalam logika refspec.

Bagaimanapun, saya menemukan pertanyaan ini karena saya hanya ingin melangkah maju dalam sejarah satu komitmen pada satu waktu, melakukan tes, dan kadang-kadang Anda harus melangkah maju dan tidak mundur. Nah, dengan lebih banyak pemikiran saya datang dengan solusi ini:

Pilih komitmen di depan di mana Anda berada. Ini mungkin bisa menjadi kepala cabang. Jika Anda berada di cabang ~ 10, maka "git checkout branch ~ 9" lalu "git checkout branch ~ 8" untuk mendapatkan yang berikutnya setelah itu, kemudian "git checkout branch ~ 7" dan seterusnya.

Mengurangi angka harus benar-benar mudah dalam sebuah skrip jika Anda membutuhkannya. Jauh lebih mudah daripada mem-parsing daftar git.

vontrapp
sumber
Meskipun bermanfaat, itu tidak menemukan komit berikutnya. Berjalan menuju komit saat ini.
Schwern
1
Kalau begitu saya kira Anda bisa melakukannya: " BRANCH=master; git co $BRANCH~$[ $(git rev-list HEAD..$BRANCH | wc -l) - 1 ]" Anda harus pergi ke cabang, tidak ada jalan lain.
vontrapp
8

Dua jawaban praktis:

Satu anak

Berdasarkan jawaban @ Michael , saya meretas childalias di blog saya .gitconfig.

Ini berfungsi seperti yang diharapkan pada case default, dan juga serbaguna.

# Get the child commit of the current commit.
# Use $1 instead of 'HEAD' if given. Use $2 instead of curent branch if given.
child = "!bash -c 'git log --format=%H --reverse --ancestry-path ${1:-HEAD}..${2:\"$(git rev-parse --abbrev-ref HEAD)\"} | head -1' -"

Secara default memberikan anak HEAD (kecuali argumen komit-ish lain diberikan) dengan mengikuti leluhur satu langkah menuju ujung cabang saat ini (kecuali komit-ish lain diberikan sebagai argumen kedua).

Gunakan %hsebagai ganti%H jika Anda menginginkan bentuk hash pendek.

Banyak anak

Dengan KEPALA terpisah (tidak ada cabang) atau untuk mendapatkan semua anak terlepas dari cabang:

# For the current (or specified) commit-ish, get the all children, print the first child 
children = "!bash -c 'c=${1:-HEAD}; set -- $(git rev-list --all --not \"$c\"^@ --children | grep $(git rev-parse \"$c\") ); shift; echo $1' -"

Ubah $1ke$* untuk mencetak semua anak.

Anda juga dapat mengubah --allke komit untuk menampilkan hanya anak-anak yang merupakan leluhur dari komit itu - dengan kata lain, untuk menampilkan hanya anak-anak "ke arah" komit yang diberikan. Ini dapat membantu Anda mempersempit hasil dari banyak anak menjadi hanya satu.

Tom Hale
sumber
7

Tidak ada "komitmen berikutnya" yang unik. Karena sejarah di Git adalah DAG, dan bukan garis, banyak komit dapat memiliki induk (cabang) yang sama, dan komit dapat memiliki lebih dari satu induk (gabungan).

Jika Anda memiliki cabang tertentu, Anda dapat melihat log-nya dan melihat komit mana yang mencantumkan yang sekarang sebagai induknya.

Phil Miller
sumber
37
Dengan logika itu tidak ada "komit sebelumnya" juga, tetapi ada banyak sintaks untuk mendapatkan orang tua.
Schwern
7
@Schwern: Tidak ada "komit sebelumnya" juga; <rev>^adalah "parent commit" ('parent pertama' untuk gabungan komit).
Jakub Narębski
6

Saya sudah mencoba berbagai solusi dan tidak ada yang berhasil untuk saya. Harus datang dengan milikku.

temukan komit berikutnya

function n() {
    git log --reverse --pretty=%H master | grep -A 1 $(git rev-parse HEAD) | tail -n1 | xargs git checkout
}

temukan komit sebelumnya

function p() {
    git checkout HEAD^1
}
MK
sumber
6

Dalam kasus di mana Anda tidak memiliki "tujuan" komit tertentu dalam pikiran, tetapi alih-alih ingin melihat commit anak yang mungkin ada di cabang mana pun , Anda dapat menggunakan perintah ini:

git rev-list --children --all | grep ^${COMMIT}

Jika Anda ingin melihat semua anak dan cucu , Anda harus menggunakan rev-list --childrensecara rekursif, seperti:

git rev-list --children --all | \
egrep ^\($(git rev-list --children --all | \
           grep ^${COMMIT} | \
           sed 's/ /|/g')\)

(Versi yang hanya memberi cucu akan menggunakan yang lebih kompleks seddan / atau cut.)

Akhirnya, Anda bisa memasukkannya ke dalam log --graphperintah untuk melihat struktur pohon, seperti:

git log --graph --oneline --decorate \
\^${COMMIT}^@ \
$(git rev-list --children --all | \
  egrep ^\($(git rev-list --children --all | \
             grep ^${COMMIT} | \
             sed 's/ /|/g')\))

Catatan : semua perintah di atas menganggap bahwa Anda telah menetapkan variabel shell ${COMMIT}ke beberapa referensi (cabang, tag, sha1) dari commit yang anak-anaknya Anda minati.

Matt McHenry
sumber
2
ini menjawab pertanyaan yang saya tidak tahu bagaimana merumuskan untuk google dan stackoverflow, tetapi sedang mencoba bertanya. terima kasih karena secara proaktif mengenali kebutuhan
Tommy Knowlton
bagi saya ${COMMIT}tidak esist, tetapi Anda bisa menggunakan $(git rev-parse HEAD) sebagai gantinya
Radon8472
maaf, menambahkan catatan tentang ${COMMIT}.
Matt McHenry
5

(Ini dimulai sebagai jawaban untuk pertanyaan rangkap. Saya telah melakukan sedikit penyuntingan ringan untuk membersihkannya.)

Semua panah internal Git adalah satu arah, menunjuk ke belakang. Karena itu tidak ada sintaks yang mudah digunakan untuk bergerak maju: itu tidak mungkin.

Hal ini dimungkinkan untuk "bergerak melawan panah", tetapi cara untuk melakukannya adalah mengejutkan jika Anda belum melihat itu sebelumnya, dan kemudian jelas sesudahnya. Katakanlah kita memiliki:

A <-B <-C <-D <-E   <-- last
        ^
        |
         \--------- middle

Menggunakan middle~2mengikuti panah dua kali dari Cbelakang ke A. Jadi bagaimana kita beralih dari Cke D? Jawabannya adalah: kita mulai dari E, menggunakan nama last, dan bekerja mundur sampai kita sampai middle, mencatat poin yang kita kunjungi di sepanjang jalan . Kemudian kita hanya bergerak sejauh yang kita inginkan ke arah last: pindah satu langkah ke D, atau dua ke E.

Ini sangat penting ketika kita memiliki cabang:

          D--E   <-- feature1
         /
...--B--C   <-- master
         \
          F--G   <-- feature2

Komit mana yang merupakan satu langkah setelahnya C? Tidak ada jawaban yang benar sampai Anda menambahkan pertanyaan: ke arah fitur___ (isi bagian yang kosong).

Untuk menghitung komit antara C(tidak termasuk C) itu sendiri dan, katakanlah G, kami menggunakan:

git rev-list --topo-order --ancestry-path master..feature2

The --topo-ordermerek yakin bahwa bahkan di hadapan kompleks percabangan-dan-penggabungan, komit keluar agar topologically-diurutkan. Ini hanya diperlukan jika rantai tidak linier. The --ancestry-pathberarti kendala bahwa ketika kita bekerja mundur dari feature2, kita hanya daftar commit yang memiliki komit Csebagai salah satu nenek moyang mereka sendiri. Yaitu, jika grafik — atau potongan yang relevan — sebenarnya terlihat seperti ini:

A--B--C   <-- master
 \     \
  \     F--G--J   <-- feature2
   \         /
    H-------I   <-- feature3

permintaan sederhana dari formulir feature2..mastermenyebutkan melakukan J, Gdan I, Fdan Hdalam urutan tertentu. Dengan --ancestry-pathkita tersingkir Hdan I: mereka bukan keturunan C, hanya dari A. Dengan --topo-orderkami pastikan bahwa urutan enumerasi yang sebenarnya adalah J, lalu G, kemudian F.

The git rev-listperintah tumpahan ID hash ini keluar pada output standar, satu per baris. Untuk bergerak satu langkah maju ke arah feature2, maka, kami hanya ingin baris terakhir .

Itu mungkin (dan menggoda dan dapat bermanfaat) untuk menambahkan --reversesehingga git rev-listmencetak komit dalam urutan terbalik setelah membuatnya. Ini berfungsi, tetapi jika Anda menggunakannya dalam pipa seperti ini:

git rev-list --topo-order --ancestry-path --reverse <id1>...<id2> | head -1

hanya untuk mendapatkan "komit berikutnya ke arah id2", dan ada daftar komit yang sangat panjang, git rev-listperintah tersebut bisa mendapatkan pipa yang rusak ketika mencoba untuk menulis headyang telah berhenti membaca inputnya dan keluar. Karena kesalahan pipa rusak biasanya diabaikan oleh shell, ini sebagian besar bekerja. Pastikan mereka diabaikan dalam penggunaan Anda .

Itu juga menggoda untuk menambahkan -n 1ke git rev-listperintah, bersama dengan --reverse. Jangan lakukan itu! Itu membuat git rev-listberhenti setelah berjalan satu langkah ke belakang , dan kemudian membalikkan daftar (satu entri) dari komitmen yang dikunjungi. Jadi ini hanya menghasilkan <id2>setiap waktu.

Catatan penting

Perhatikan bahwa dengan fragmen grafik "berlian" atau "cincin benzena":

       I--J
      /    \
...--H      M--...  <-- last
      \    /
       K--L

memindahkan satu komit "maju" dari Hmenuju lastakan membuat Anda baik I atau K . Tidak ada yang dapat Anda lakukan tentang itu: kedua komitmen itu selangkah lebih maju! Jika Anda kemudian mulai dari komit yang dihasilkan dan melangkah ke langkah lain, Anda sekarang berkomitmen pada jalur mana pun yang Anda mulai.

Obat untuk ini adalah menghindari bergerak satu langkah pada satu waktu dan terkunci di rantai yang tergantung jalan. Sebaliknya, jika Anda berencana untuk mengunjungi seluruh rantai jalur leluhur, sebelum melakukan hal lain , buat daftar lengkap semua komitmen dalam rantai:

git rev-list --topo-order --reverse --ancestry-path A..B > /tmp/list-of-commits

Kemudian, kunjungi setiap komit dalam daftar ini, satu per satu, dan Anda akan mendapatkan seluruh rantai. The --topo-orderakan memastikan anda menekan I-dan- Jagar, dan K-dan- Lagar (meskipun tidak ada cara mudah untuk memprediksi apakah Anda akan melakukan pasangan IJ sebelum atau setelah pasangan KL).

torek
sumber
" Tidak ada jawaban yang benar sampai Anda menambahkan pertanyaan: ke arah fitur___ " Ini adalah poin yang sangat bagus. Terima kasih atas jawaban anda.
Schwern
4

Saya memiliki alias ini di ~/.gitconfig

first-child = "!f() { git log  --reverse --ancestry-path --pretty=%H $1..${2:-HEAD} | head -1; }; f"
lemah
sumber
apa f()? Dan itu harus head -1menjadi anak pertama, kalau tidak ini hanya akan melaporkan KEPALA.
Xerus
@ Xerus Karena ia menggunakan beberapa sintaks shell "kompleks", dan git tidak akan mengenalinya jika tidak dibungkus f(). Ya, head -1itu dugaan berani.
lemah
Bagus! Tweaked mine to: nextref = "!f() { git log --reverse --ancestry-path --pretty=%H $1..HEAD | head -${2:-1} | tail -1; }; f"sehingga Anda dapat memilih seberapa jauh ke depan, secara opsional
Z. Khullah
2

Saya berhasil menemukan anak berikutnya dengan cara berikut:

git log --reverse --children -n1 HEAD (where 'n' is the number of children to show)
DevRQ
sumber
1
bagi saya ini menunjukkan bukan anak pertama, itu menunjukkan komit saat ini
Radon8472
2

Jika anak melakukan semua pada beberapa cabang, Anda dapat menggunakan gitk --all commit^.., di mana "komit" adalah sesuatu yang mengidentifikasi komit. Misalnya, jika komit yang disingkat SHA-1 adalah c6661c5, maka ketikkangitk --all c6661c5^..

Anda mungkin perlu memasukkan SHA-1 lengkap ke dalam sel "SHA1 ID:" gitk. Anda memerlukan SHA-1 lengkap, yang untuk contoh ini dapat diperoleh viagit rev-parse c6661c5

Atau, git rev-list --all --children | grep '^c6661c5883bb53d400ce160a5897610ecedbdc9d'akan menghasilkan garis yang berisi semua anak dari komit ini, mungkin apakah ada cabang yang terlibat atau tidak.

pengguna339589
sumber
0

Setiap komit menyimpan pointer ke induknya (orang tua, jika komit gabungan (standar)).

Jadi, tidak ada cara untuk menunjuk ke seorang anak melakukan (jika ada) dari orang tua.

Lakshman Prasad
sumber
Commit tidak dapat menyimpan pointer ke anak-anaknya, karena tambahan commit anak (titik percabangan) dapat ditambahkan pada poin selanjutnya.
Jakub Narębski
@ Yakub saya tidak benar-benar mengikuti. Mereka tidak dapat ditambahkan nanti?
Schwern
1
Jakub: Itulah yang saya katakan. "Setiap komit hanya menyimpan pointer ke induknya."
Lakshman Prasad
@Schwern: Komit di git tidak dapat diubah (yang memiliki konsekuensi akuntabilitas yang bagus), jadi petunjuk untuk anak-anak tidak dapat "ditambahkan nanti". Salah satu alasannya adalah bahwa pengidentifikasi komit (digunakan misalnya dalam tautan "induk") bergantung pada konten komit; ini adalah satu-satunya solusi dalam sistem terdistribusi, tanpa otoritas penomoran pusat. Juga "anak-anak" dari komit tergantung pada cabang yang Anda miliki, dan ini pada gilirannya dapat berbeda dari repositori ke repositori (dan komit adalah sama di setiap repositori).
Jakub Narębski
4
@becomingGuru Aku turun memilihnya. Mungkin benar, tetapi itu tidak menjawab pertanyaan saya. Pertanyaannya adalah "bagaimana cara menemukan commit selanjutnya di git?" Ini bukan "apakah git commit menyimpan pointer ke anak-anaknya?"
Schwern
0

Jawaban yang ada mengasumsikan bahwa Anda memiliki cabang yang berisi komit yang Anda cari.

Dalam kasus saya, komit yang saya cari tidak ada git rev-list --all karena tidak ada cabang yang memuatnya.

Saya akhirnya mencari gitk --reflogsecara manual.

Jika Anda tidak dapat menemukan komit Anda di reflog, cobalah:

  • git fsck --full untuk membuat daftar dangling (mis. tidak di cabang mana pun) berkomitmen, atau
  • git fsck --lost-found untuk membuat referensi yang menunjuk ke menggantung komitmen untuk menerapkan teknik di jawaban lain.
snipsnipsnip
sumber