Bagaimana cara mengembalikan beberapa komit git?

984

Saya memiliki repositori git yang terlihat seperti ini:

A -> B -> C -> D -> HEAD

Saya ingin kepala cabang menunjuk ke A, yaitu saya ingin B, C, D, dan HEAD menghilang dan saya ingin kepala bersinonim dengan A.

Sepertinya saya bisa mencoba rebase (tidak berlaku, karena saya sudah mendorong perubahan di antaranya), atau kembali. Tetapi bagaimana cara mengembalikan beberapa komitmen? Apakah saya mengembalikan satu per satu? Apakah urutannya penting?

Tagihan
sumber
3
Jika Anda hanya ingin mengatur ulang remote, Anda bisa menghajarnya dengan apa saja! Tetapi mari kita gunakan komit keempat yang lalu: git push -f HEAD~4:master(dengan anggapan cabang remote adalah master). Ya, Anda dapat mendorong komit seperti itu.
u0b34a0f6ae
21
Jika orang-orang telah menarik Anda harus membuat komit yang mengembalikan perubahan menggunakan git revert.
Jakub Narębski
1
Gunakan git show HEAD ~ 4 untuk memastikan Anda mendorong ke kanan ke remote
Mâtt Frëëman
1
Kemungkinan duplikat dari Cara membatalkan komitmen terakhir di Git?
Jim Fell
5
"Apakah urutannya penting?" Ya, jika komit memengaruhi baris yang sama dalam file yang sama. Maka Anda harus mulai mengembalikan komit terbaru, dan bekerja kembali.
avandeursen

Jawaban:

1336

Memperluas apa yang saya tulis dalam komentar

Aturan umum adalah bahwa Anda tidak boleh menulis ulang (mengubah) sejarah yang telah Anda terbitkan, karena seseorang mungkin telah mendasari pekerjaan mereka di atasnya. Jika Anda menulis ulang (mengubah) riwayat, Anda akan membuat masalah dengan menggabungkan perubahan mereka dan dengan memperbarui untuk mereka.

Jadi solusinya adalah untuk membuat komit baru yang muallaf perubahan yang Anda ingin menyingkirkan. Anda dapat melakukan ini menggunakan perintah git revert .

Anda memiliki situasi berikut:

A <- B <- C <- D <- master <- HEAD

(panah di sini merujuk ke arah penunjuk: referensi "induk" dalam kasus komit, komit atas dalam kasus kepala cabang (cabang ref), dan nama cabang dalam kasus referensi KEPALA).

Yang perlu Anda buat adalah sebagai berikut:

A <- B <- C <- D <- [(BCD) ^ - 1] <- master <- HEAD

di mana "[(BCD) ^ - 1]" berarti komit yang mengembalikan perubahan dalam komit B, C, D. Matematika memberi tahu kita bahwa (BCD) ^ - 1 = D ^ -1 C ^ -1 B ^ -1, jadi Anda bisa mendapatkan situasi yang diperlukan menggunakan perintah berikut:

$ git revert --no-commit D
$ git revert --no-commit C
$ git revert --no-commit B
$ git commit -m "the commit message"

Solusi alternatif adalah dengan checkout konten dari commit A, dan komit pada kondisi ini:

$ git checkout -f A -- .
$ git commit -a

Maka Anda akan memiliki situasi berikut:

A <- B <- C <- D <- A '<- master <- HEAD

Komit A 'memiliki konten yang sama dengan komit A, tetapi komit berbeda (pesan komit, orang tua, tanggal komit).

The solusi dengan Jeff Ferland, dimodifikasi oleh Charles Bailey dibangun berdasarkan ide yang sama, tetapi menggunakan ulang git :

$ git reset --hard A
$ git reset --soft @{1}  # (or ORIG_HEAD), which is D
$ git commit
Jakub Narębski
sumber
40
Jika Anda menambahkan file dalam B, C atau D. git checkout -f A -- .Tidak akan menghapus ini, Anda harus melakukannya secara manual. Saya menerapkan strategi ini sekarang, terima kasih Jakub
oma
18
Solusi itu tidak setara. Yang pertama tidak menghapus file yang baru dibuat.
m33lky
10
@ Jerry: git checkout foomungkin berarti cabang checkout foo(beralih ke cabang) atau checkout file foo (dari indeks). --digunakan untuk disambiguasi, misalnya git checkout -- fooselalu tentang file.
Jakub Narębski
87
Selain jawaban yang bagus. git revert --no-commit D C B
Tulisan
9
@ welldan97: Terima kasih atas komentarnya. Saat menulis jawaban git revertini tidak menerima banyak komitmen; itu adalah tambahan yang cukup baru.
Jakub Narębski
248

Cara bersih yang menurut saya berguna

git revert --no-commit HEAD~3..

Perintah ini mengembalikan 3 commit terakhir dengan hanya satu commit.

Juga tidak menulis ulang sejarah.

menyelam dalam
sumber
16
Ini adalah jawaban yang sederhana dan terbaik
Julien Deniau
3
@ JohnLittle, ini akan melakukan perubahan. git commitdari sana benar-benar akan melakukan komit.
x1a4
14
Ini tidak akan berfungsi jika beberapa komit digabung komit.
MegaManX
5
Apa yang dilakukan dua titik pada akhirnya?
kapulaga
5
@ cardamom Menentukan rentang. HEAD~3..sama denganHEAD~3..HEAD
Toine H
238

Untuk melakukannya, Anda hanya perlu menggunakan perintah revert , menentukan rentang komit yang ingin Anda pulihkan .

Mempertimbangkan contoh Anda, Anda harus melakukan ini (dengan asumsi Anda menggunakan 'master' cabang):

git revert master~3..master

Ini akan membuat komit baru di lokal Anda dengan komit terbalik B, C dan D (artinya akan membatalkan perubahan yang dilakukan oleh komit ini):

A <- B <- C <- D <- BCD' <- HEAD
Pemenang
sumber
129
git revert --no-commit HEAD~2..adalah cara yang sedikit lebih idiomatis untuk melakukannya. Jika Anda berada di cabang master, tidak perlu menentukan master lagi. The --no-commitpilihan memungkinkan git mencoba untuk kembali semua komit sekaligus, bukan mengotori sejarah dengan beberapa revert commit ...pesan (dengan asumsi bahwa ini apa yang Anda inginkan).
kubi
6
@ Viktor Saya memperbaiki rentang komit Anda. Awal rentang adalah eksklusif, artinya tidak termasuk. Jadi, jika Anda ingin mengembalikan 3 komit terakhir, Anda harus memulai rentang dari induk dari komit ke-3, yaitu master~3.
2
@kubi apakah tidak ada cara untuk menyertakan SHA dalam pesan komit, menggunakan komit tunggal (metode Anda tetapi tanpa harus secara manual memasukkan komit yang dikembalikan)?
Chris S
@ Chris Pikiran pertama saya adalah untuk tidak menggunakan --no-commit(jadi Anda mendapatkan komit terpisah untuk setiap pengembalian), dan kemudian hancurkan semuanya bersama dalam rebase interaktif. Pesan komit gabungan akan berisi semua SHA, dan Anda dapat mengaturnya sesuka Anda menggunakan editor pesan komit Anda.
Radon Rosborough
71

Mirip dengan jawaban Jakub, ini memungkinkan Anda untuk dengan mudah memilih komit berturut-turut untuk dikembalikan.

# revert all commits from B to HEAD, inclusively
$ git revert --no-commit B..HEAD  
$ git commit -m 'message'
konyak
sumber
9
Solusi Anda bekerja dengan baik untuk saya, tetapi dengan sedikit modifikasi. Jika kita memiliki kasing Z -> A -> B -> C -> D -> KEPALA dan jika saya ingin kembali ke keadaan A, maka anehnya saya harus menjalankan git revert --no-commit Z .. KEPALA
Bogdan
3
Setuju dengan @Bogdan, rentang pengembaliannya seperti itu: SHA_TO_REVERT_TO..HEAD
Vadym Tyemirov
11
Kisarannya salah. Seharusnya B^..HEAD, kalau tidak B dikecualikan.
tessus
3
Setuju dengan @tessus, jadi hal yang benar untuk dilakukan adalah: git revert --no-commit B^..HEADataugit revert --no-commit A..HEAD
Yoho
64
git reset --hard a
git reset --mixed d
git commit

Itu akan bertindak sebagai pengembalian bagi mereka semua sekaligus. Berikan pesan komit yang baik.

Jeff Ferland
sumber
4
Jika dia ingin HEADterlihat seperti Amaka dia mungkin ingin indeks cocok jadi git reset --soft Dmungkin lebih tepat.
CB Bailey
2
--setel ulang perangkat lunak tidak memindahkan indeks, jadi ketika dia melakukan, itu akan terlihat seperti komit yang datang langsung dari bukan dari D. Itu akan membuat cabang terpecah. --mixed meninggalkan perubahan, tetapi memindahkan penunjuk indeks, jadi D akan menjadi komit induk.
Jeff Ferland
5
Ya, saya pikir git reset - tetaplah persis seperti yang saya miliki di atas. Itu keluar dalam versi 1.7.1, dirilis pada bulan April 2010, jadi jawabannya tidak ada pada saat itu.
Jeff Ferland
git checkout Akemudian di git commitatas tidak bekerja untuk saya, tetapi jawaban ini berhasil.
SimplGy
Mengapa git reset --mixed Ddiperlukan? Khususnya mengapa reset? Apakah karena, tanpa mengatur ulang ke D, itu, KEPALA akan menunjuk ke A, menyebabkan B, C, dan D menjadi "menggantung" dan mengumpulkan sampah - yang bukan yang ia inginkan? Tapi mengapa --mixed? Anda sudah menjawab " --softmengatur ulang tidak memindahkan indeks ..." Jadi dengan memindahkan indeks, ini berarti indeks akan berisi perubahan D, sedangkan Direktori Kerja akan berisi perubahan A - dengan cara ini a git statusatau git diff(yang membandingkan Indeks [D] ke Direktori Kerja [A]) akan menunjukkan substansi; pengguna yang pergi dari D kembali ke A?
Kacang Merah
39

Pertama-tama pastikan bahwa copy pekerjaan Anda tidak diubah. Kemudian:

git diff HEAD commit_sha_you_want_to_revert_to | git apply

dan kemudian lakukan saja. Jangan lupa untuk mendokumentasikan apa alasan untuk kembali.

mateusz.fiolka
sumber
1
Bekerja untukku! Punya masalah dengan perubahan fitur cabang yang dibuat dalam mengembangkan cabang (beberapa perbaikan bug), sehingga cabang fitur harus menimpa semua perubahan yang dibuat dalam pengembangan (termasuk menghapus beberapa file).
silentser
1
Tidak akan bekerja dengan file biner:error: cannot apply binary patch to 'some/image.png' without full index line error: some/image.png: patch does not apply
GabLeRoux
2
Ini adalah solusi yang jauh lebih fleksibel daripada jawaban yang diterima. Terima kasih!
Brian Kung
2
re: file biner menggunakan opsigit diff --binary HEAD commit_sha_you_want_to_revert_to | git apply
biner
2
Ini akan berfungsi bahkan jika Anda ingin mengembalikan serangkaian komit yang berisi komit gabungan. Saat menggunakan git revert A..ZAnda akan mendapatkanerror: commit X is a merge but no -m option was given.
Juliusz Gonera
35

Saya sangat frustrasi karena pertanyaan ini tidak bisa dijawab begitu saja. Setiap pertanyaan lain terkait dengan bagaimana memulihkan dengan benar dan menjaga sejarah. Pertanyaan ini mengatakan, "Saya ingin kepala cabang menunjuk ke A, yaitu saya ingin B, C, D, dan HEAD menghilang dan saya ingin kepala bersinonim dengan A."

git checkout <branch_name>
git reset --hard <commit Hash for A>
git push -f

Saya belajar banyak membaca posting Jakub, tetapi beberapa orang di perusahaan (dengan akses untuk mendorong ke cabang "pengujian" kami tanpa Pull-Request) mendorong seperti 5 komitmen buruk yang mencoba untuk memperbaiki dan memperbaiki dan memperbaiki kesalahan yang ia buat 5 komit yang lalu. Bukan hanya itu, tetapi satu atau dua Permintaan Tarik diterima, yang sekarang buruk. Jadi lupakan saja, saya menemukan good commit terakhir (abc1234) dan baru saja menjalankan skrip dasar:

git checkout testing
git reset --hard abc1234
git push -f

Saya mengatakan kepada 5 orang lain yang bekerja di repo ini bahwa mereka lebih baik membuat catatan tentang perubahan mereka selama beberapa jam terakhir dan Hapus / Re-Cabang dari pengujian terbaru. Akhir cerita.

Suamere
sumber
2
Saya belum menerbitkan komitmen, jadi ini adalah jawaban yang saya butuhkan. Terima kasih, @Suamere.
Tom Barron
Cara yang lebih baik untuk melakukan ini adalah git push --force-with-lease, yang akan menulis ulang sejarah hanya jika tidak ada orang lain yang berkomitmen untuk cabang setelah atau dalam kisaran komitmen untuk menguap. Jika orang lain telah menggunakan cabang, maka sejarahnya seharusnya tidak pernah ditulis ulang, dan komit harus secara sederhana dikembalikan.
frandroid
1
@frandroid "riwayat tidak boleh ditulis ulang", hanya kesepakatan sith dalam absolut. Pertanyaan utas ini, dan inti dari jawaban saya, adalah persis bahwa untuk skenario tertentu, semua sejarah harus dihapus.
Suamere
@Suamere Tentu, itu pertanyaannya. Tetapi ketika jawaban Anda menyebutkan tentang apa yang Anda harus katakan kepada orang lain, ada potensi masalah. Dari pengalaman pribadi, push -f dapat mengacaukan basis kode Anda jika orang lain telah berkomitmen setelah apa yang Anda coba hapus. --Paksa-dengan-sewa mencapai hasil yang sama, kecuali bahwa itu menghemat pantat Anda jika Anda akan mengacaukan repo Anda. Mengapa mengambil risiko? Jika --force-with-lease gagal, Anda dapat melihat komit mana yang menghalangi, menilai dengan benar, menyesuaikan, dan mencoba lagi.
frandroid
1
@Suamere Terima kasih! Saya setuju pertanyaan dengan jelas menyatakan ingin menulis ulang sejarah. Saya berada dalam situasi yang sama seperti Anda dan saya menebak OP di mana seseorang membuat lusinan jelek dan komit aneh dan reverts of reverts secara tidak sengaja (saat saya sedang berlibur) dan negara perlu dikembalikan. Dalam setiap kejadian bersama dengan peringatan sehat yang baik ini harus menjadi jawaban yang diterima.
Lee Richardson
9

Ini adalah perluasan dari salah satu solusi yang disediakan dalam jawaban Jakub

Saya dihadapkan dengan situasi di mana komit yang saya butuhkan untuk mundur agak rumit, dengan beberapa komit digabung komit, dan saya perlu menghindari penulisan ulang sejarah. Saya tidak dapat menggunakan serangkaiangit revert perintah karena saya akhirnya mengalami konflik antara perubahan pengembalian yang ditambahkan. Saya akhirnya menggunakan langkah-langkah berikut.

Pertama, periksa isi komit target sambil meninggalkan KEPALA di ujung cabang:

$ git checkout -f <target-commit> -- .

(The - memastikan <target-commit> ditafsirkan sebagai komit daripada file;. Yang merujuk ke direktori saat ini.)

Kemudian, tentukan file apa yang ditambahkan dalam komit yang sedang diputar kembali, dan dengan demikian perlu dihapus:

$ git diff --name-status --cached <target-commit>

File yang ditambahkan harus muncul dengan "A" di awal baris, dan seharusnya tidak ada perbedaan lainnya. Sekarang, jika ada file yang perlu dihapus, tahap file ini untuk dihapus:

$ git rm <filespec>[ <filespec> ...]

Akhirnya, lakukan pengembalian:

$ git commit -m 'revert to <target-commit>'

Jika diinginkan, pastikan kita kembali ke kondisi yang diinginkan:

$git diff <target-commit> <current-commit>

Seharusnya tidak ada perbedaan.

Warren Dew
sumber
Apakah Anda yakin dapat gitKEPALA hanya dengan ujung cabang?
Suamere
2
Ini adalah solusi yang jauh lebih baik bagi saya, karena di tambang saya memiliki komitmen gabungan.
sovemp
3

Cara mudah untuk mengembalikan sekelompok komit pada repositori bersama (yang digunakan orang dan Anda ingin melestarikan sejarah) adalah menggunakan git revertbersama dengan gitrev-list . Yang terakhir akan memberi Anda daftar komitmen, yang pertama akan melakukan pengembalian itu sendiri.

Ada dua cara untuk melakukan itu. Jika Anda ingin mengembalikan beberapa komit dalam penggunaan komit tunggal:

for i in `git rev-list <first-commit-sha>^..<last-commit-sha>`; do git revert -n $i; done

ini akan mengembalikan sekelompok komit yang Anda butuhkan, tetapi tinggalkan semua perubahan pada pohon kerja Anda, Anda harus mengkomit semuanya seperti biasa.

Pilihan lain adalah memiliki satu komit per perubahan yang dikembalikan:

for i in `git rev-list <first-commit-sha>^..<last-commit-sha>`; do git revert --no-edit -s $i; done

Misalnya, jika Anda memiliki pohon komit suka

 o---o---o---o---o---o--->    
fff eee ddd ccc bbb aaa

untuk mengembalikan perubahan dari eee ke bbb , jalankan

for i in `git rev-list eee^..bbb`; do git revert --no-edit -s $i; done
Ruslan Kabalin
sumber
baru saja menggunakan ini. Terima kasih!
Ran Biron
2

Tidak satu pun dari mereka yang bekerja untuk saya, jadi saya punya tiga komitmen untuk kembali (tiga komitmen terakhir), jadi saya melakukannya:

git revert HEAD
git revert HEAD~2
git revert HEAD~4
git rebase -i HEAD~3 # pick, squash, squash

Bekerja seperti pesona :)

Dorian
sumber
2
Ini adalah opsi yang layak hanya jika perubahan Anda belum didorong.
kboom
0

Menurut saya cara yang sangat mudah dan bersih adalah:

kembali ke A

git checkout -f A

arahkan kepala master ke kondisi saat ini

git symbolic-ref HEAD refs/heads/master

menyimpan

git commit
nulll
sumber
1
Bisakah Anda jelaskan alasan downvoting?
nulll
Ini berfungsi dengan baik. Apa gunanya memberi downvote untuk jawaban yang bermanfaat ini atau adakah yang bisa menjelaskan apa yang merupakan praktik terbaik?
Levent Divilioglu
Apakah itu sama dengan git checkout master; git reset --hard A? Atau jika tidak, bisakah Anda menjelaskan sedikit lebih banyak tentang apa ini?
MM
hanya berfungsi, tetapi HEAD simbolis-ref sepertinya bukan perintah "aman"
Sérgio
err Saya tidak ingin memperbaiki master, saya ingin memperbaiki cabang
Sérgio
-6

Jika Anda ingin sementara mengembalikan komit fitur, maka Anda dapat menggunakan serangkaian perintah berikut.

Inilah cara kerjanya

git log --pretty = oneline | grep 'feature_name' | cut -d '' -f1 | xargs -n1 git revert --no-edit

Ajit Singh
sumber