Bagaimana cara memeras semua git menjadi satu?

480

Bagaimana Anda menekan seluruh repositori ke komit pertama?

Saya dapat rebase ke komit pertama, tetapi itu akan meninggalkan saya dengan 2 komit. Apakah ada cara untuk merujuk komit sebelum yang pertama?

Verhogen
sumber
8
"komit sebelum yang pertama"?
innaM
31
@innaM - Komit primordial yang menyebabkan git . (Berharap humor melewati cukup baik melalui jalinan).
ripper234
11
Bagi mereka yang datang belakangan untuk pertanyaan ini, pastikan untuk menggunakan jawaban yang lebih modern .
Droogan
1
Terkait, tetapi bukan duplikat ( --rootsebenarnya bukan solusi terbaik untuk menekan semua commit jika ada banyak dari mereka untuk squash): Gabungkan dua commit pertama dari repositori Git? .
2
Imo: ini yang terbaik dari @MrTux: stackoverflow.com/questions/30236694/… .
J0hnG4lt

Jawaban:

129

Mungkin cara termudah adalah dengan hanya membuat repositori baru dengan status copy pekerjaan saat ini. Jika Anda ingin menyimpan semua pesan komit yang dapat Anda lakukan pertama-tama git log > original.logdan kemudian mengeditnya untuk pesan komit awal Anda di repositori baru:

rm -rf .git
git init
git add .
git commit

atau

git log > original.log
# edit original.log as desired
rm -rf .git
git init
git add .
git commit -F original.log
Pat Notz
sumber
62
tetapi Anda kehilangan cabang dengan metode ini
Olivier Refalo
150
Git telah berkembang sejak jawaban ini diberikan. Tidak, ada cara sederhana dan lebih baik: git rebase -i --root. Lihat: stackoverflow.com/a/9254257/109618
David J.
5
Ini mungkin bekerja untuk beberapa kasus, tetapi pada dasarnya bukan jawaban untuk pertanyaan itu. Dengan resep ini, Anda kehilangan semua konfigurasi dan semua cabang lainnya juga.
iwein
3
Ini adalah solusi mengerikan yang tidak perlu merusak. Tolong jangan gunakan itu.
Daniel Kamil Kozar
5
juga akan merusak submodula. -1
Krum
694

Pada git 1.6.2 , Anda bisa menggunakan git rebase --root -i.

Untuk setiap komit kecuali yang pertama, ubah pickke squash.

Jordan Lewis
sumber
49
Silakan tambahkan contoh perintah yang lengkap dan berfungsi yang menjawab pertanyaan awal.
Jake
29
Saya berharap saya membaca ini sebelum membuang seluruh repositori saya seperti jawaban yang diterima mengatakan: /
Mike Chamberlain
38
Jawaban ini boleh saja , tetapi jika Anda secara interaktif memundurkan lebih dari, katakanlah, 20 komit, rebase interaktif mungkin akan terlalu lambat dan sulit digunakan. Anda mungkin akan mengalami kesulitan mencoba untuk menghancurkan ratusan atau ribuan komitmen. Saya akan pergi dengan reset lunak atau campuran ke komit root, kemudian berkomitmen kembali, dalam hal ini.
20
@Pred Jangan gunakan squashuntuk semua komit. Yang pertama perlu pick.
Geert
14
Jika Anda memiliki banyak komitmen, sulit untuk mengubah 'pilih' menjadi 'squash' secara manual. Gunakan :% s / pick / squash / g di baris perintah VIM untuk melakukan ini lebih cepat.
eilas
314

Memperbarui

Saya sudah membuat alias git squash-all.
Contoh penggunaan : git squash-all "a brand new start".

[alias]
  squash-all = "!f(){ git reset $(git commit-tree HEAD^{tree} -m \"${1:-A new start}\");};f"

Peringatan : ingatlah untuk memberikan komentar, jika tidak, pesan komit default "Awal baru" akan digunakan.

Atau Anda dapat membuat alias dengan perintah berikut:

git config --global alias.squash-all '!f(){ git reset $(git commit-tree HEAD^{tree} -m "${1:-A new start}");};f'

Satu Liner

git reset $(git commit-tree HEAD^{tree} -m "A new start")

Catatan : " A new start" di sini hanyalah contoh, jangan ragu untuk menggunakan bahasa Anda sendiri.

TL; DR

Tidak perlu squash, gunakan git commit-treeuntuk membuat komitmen anak yatim dan pergi bersamanya.

Menjelaskan

  1. buat komit tunggal via git commit-tree

    Apa yang git commit-tree HEAD^{tree} -m "A new start"dilakukan adalah:

    Membuat objek komit baru berdasarkan objek pohon yang disediakan dan memancarkan id objek komit baru di stdout. Pesan log dibaca dari input standar, kecuali opsi -m atau -F diberikan.

    Ekspresi HEAD^{tree}berarti objek pohon yang terkait HEAD, yaitu ujung cabang Anda saat ini. lihat Tree-Objects dan Commit-Objects .

  2. atur ulang cabang saat ini ke komit baru

    Kemudian git resetcukup reset cabang saat ini ke objek komit yang baru dibuat.

Dengan cara ini, tidak ada di ruang kerja yang disentuh, juga tidak perlu rebase / squash, yang membuatnya sangat cepat. Dan waktu yang dibutuhkan tidak relevan dengan ukuran repositori atau kedalaman riwayat.

Variasi: Repo Baru dari Template Proyek

Ini berguna untuk membuat "commit awal" dalam proyek baru menggunakan repositori lain sebagai templat / pola dasar / seed / kerangka. Sebagai contoh:

cd my-new-project
git init
git fetch --depth=1 -n https://github.com/toolbear/panda.git
git reset --hard $(git commit-tree FETCH_HEAD^{tree} -m "initial commit")

Ini menghindari menambahkan repo templat sebagai remote ( originatau sebaliknya) dan menciutkan riwayat repo templat ke komit awal Anda.

Ryenus
sumber
6
Sintaks revisi git (HEAD ^ {tree}) dijelaskan di sini jika ada orang yang bertanya-tanya: jk.gs/gitrevisions.html
Colin Bowern
1
Apakah ini mengatur ulang repositori lokal dan jarak jauh, atau hanya salah satunya?
aleclarson
4
@aleclarson, ini hanya mereset cabang saat ini di repositori lokal, gunakan git push -funtuk propagasi.
ryenus
2
Saya menemukan jawaban ini sambil mencari cara untuk memulai proyek baru dari repositori templat proyek yang tidak melibatkan git clone. Jika Anda menambahkan --harduntuk git resetdan beralih HEADdengan FETCH_HEADdi git commit-treeAnda dapat membuat awal komit setelah mengambil repo Template. Saya telah mengedit jawaban dengan bagian di bagian akhir yang menunjukkan hal ini.
toolbear
4
Anda bisa menyingkirkan "Peringatan" itu tetapi hanya menggunakan${1?Please enter a message}
Elliot Cameron
172

Jika semua yang ingin Anda lakukan adalah menekan semua komit Anda ke root commit, lalu sementara

git rebase --interactive --root

dapat bekerja, itu tidak praktis untuk sejumlah besar komit (misalnya, ratusan komit), karena operasi rebase mungkin akan berjalan sangat lambat untuk menghasilkan daftar komit editor rebase interaktif, serta menjalankan rebase itu sendiri.

Berikut adalah dua solusi yang lebih cepat dan lebih efisien ketika Anda menekan sejumlah besar komitmen:

Solusi alternatif # 1: cabang yatim

Anda cukup membuat cabang yatim baru di ujung (yaitu komit terbaru) dari cabang Anda saat ini. Cabang yatim ini membentuk komit root awal dari pohon histori komit yang sama sekali baru dan terpisah, yang secara efektif setara dengan menekan semua komit Anda:

git checkout --orphan new-master master
git commit -m "Enter commit message for your new initial commit"

# Overwrite the old master branch reference with the new one
git branch -M new-master master

Dokumentasi:

Solusi alternatif # 2: soft reset

Solusi efisien lainnya adalah dengan menggunakan campuran atau soft reset ke root commit <root>:

git branch beforeReset

git reset --soft <root>
git commit --amend

# Verify that the new amended root is no different
# from the previous branch state
git diff beforeReset

Dokumentasi:


sumber
22
Solusi alternatif # 1: cabang yatim - batu!
Thomas
8
Solusi alternatif # 1 FTW. Sekadar menambahkan, jika Anda ingin mendorong perubahan Anda ke remote, lakukan git push origin master --force.
Eddy Verbruggen
1
jangan lupagit push --force
NecipAllef
Jangan lakukan cabang yatim pada kode (yaitu jangan lakukan solusi alternatif # 1 di atas) jika Anda akan mendorong ke permintaan tarik Github yang terbuka !!! Github akan menutup PR Anda karena kepala saat ini bukan keturunan dari kepala disimpan .
Andrew Mackie
Solusi alternatif # 1 juga menghindari konflik gabungan yang dapat terjadi ketika squashing melakukan
FernAndr
52
echo "message" | git commit-tree HEAD^{tree}

Ini akan membuat komit yatim dengan pohon KEPALA, dan menampilkan nama itu (SHA-1) di stdout. Kemudian atur ulang cabang Anda di sana.

git reset SHA-1
kusma
sumber
27
git reset $(git commit-tree HEAD^{tree} -m "commit message")akan membuatnya lebih mudah.
ryenus
4
^ INI! - harus menjadi jawaban. Tidak sepenuhnya yakin apakah itu niat penulis, tetapi milik saya (diperlukan repo murni dengan komit tunggal, dan itu menyelesaikan pekerjaan).
chesterbr
@ryenus, solusi Anda melakukan apa yang saya cari. Jika Anda menambahkan komentar sebagai jawaban, saya akan menerimanya.
tldr
2
Alasan saya tidak menyarankan varian subshell sendiri, adalah bahwa itu tidak akan bekerja pada cmd.exe di Windows.
kusma
Pada prompt Windows, Anda mungkin harus mengutip parameter terakhir: echo "message" | git commit-tree "HEAD^{tree}"
Bernard
41

Inilah cara saya akhirnya melakukan ini, kalau-kalau itu bekerja untuk orang lain:

Ingatlah bahwa selalu ada risiko dalam melakukan hal-hal seperti ini, dan tidak pernah ada ide buruk untuk membuat cabang penyimpanan sebelum memulai.

Mulailah dengan login

git log --oneline

Gulir ke komit pertama, salin SHA

git reset --soft <#sha#>

Ganti <#sha#>dengan SHA yang disalin dari log

git status

Pastikan semuanya berwarna hijau, jika tidak jalankan git add -A

git commit --amend

Ubah semua perubahan saat ini menjadi komit pertama saat ini

Sekarang paksa dorong cabang ini dan itu akan menimpa apa yang ada.

Logan
sumber
1
Pilihan luar biasa! Sangat sederhana.
dua kali
1
Ini lebih dari fantastis! Terima kasih!
Matt Komarnicki
1
Perhatikan bahwa ini tampaknya meninggalkan sejarah. Anda yatim piatu, tapi masih ada di sana.
Brad
Jawaban yang sangat berguna ... tetapi Anda harus menyadari bahwa setelah perintah perubahan Anda akan menemukan diri Anda dalam editor vim dengan sintaks khusus. ESC, ENTER,: x adalah teman Anda.
Erich Kuester
Pilihan luar biasa!
danivicario
36

Saya membaca sesuatu tentang menggunakan cangkok tetapi tidak pernah banyak menyelidiki.

Bagaimanapun, Anda dapat menekan 2 komit terakhir secara manual dengan sesuatu seperti ini:

git reset HEAD~1
git add -A
git commit --amend
R. Martinho Fernandes
sumber
4
Ini sebenarnya jawaban yang saya cari, berharap itu yang diterima!
Jay
Ini adalah jawaban yang luar biasa
Tuan Yoda
36

Cara termudah adalah dengan menggunakan perintah 'plumbing' update-refuntuk menghapus cabang saat ini.

Anda tidak dapat menggunakan git branch -Dkarena memiliki katup pengaman untuk menghentikan Anda menghapus cabang saat ini.

Ini menempatkan Anda kembali ke keadaan 'komit awal' di mana Anda dapat mulai dengan komit awal yang baru.

git update-ref -d refs/heads/master
git commit -m "New initial commit"
CB Bailey
sumber
15

Dalam satu baris 6 kata

git checkout --orphan new_root_branch  &&  git commit
Kyb
sumber
@AlexanderMills, Anda harus membaca git help checkouttentang--orphan
kyb
dapatkah Anda menautkan ke dokumen untuk itu sehingga setiap orang yang membaca ini tidak harus mencarinya secara manual?
Alexander Mills
1
mudah. ini diagit help checkout --orphan
kyb
8

buat cadangan

git branch backup

reset ke komit yang ditentukan

git reset --soft <#root>

kemudian tambahkan semua file ke pementasan

git add .

komit tanpa memperbarui pesan

git commit --amend --no-edit

dorong cabang baru dengan komitmen terjepit untuk repo

git push -f
David Morton
sumber
Apakah ini akan menyimpan pesan komit sebelumnya?
not2qubit
1
@ not2qubit tidak ini tidak akan menyimpan pesan komit sebelumnya, alih-alih komit # 1, komit # 2, komit # 3, Anda akan mendapatkan semua perubahan komit yang dikemas ke dalam komit tunggal # 1. Komit # 1 akan menjadi <root>komit yang Anda atur kembali. git commit --amend --no-editakan melakukan semua perubahan pada komit saat ini yang <root>tanpa perlu mengedit pesan komit.
David Morton
5

Untuk squash menggunakan cangkok

Tambahkan file .git/info/grafts, letakkan di sana hash komit yang Anda inginkan menjadi root Anda

git log sekarang akan mulai dari komit itu

Untuk membuatnya 'nyata' berjalan git filter-branch

dimus
sumber
1

Jawaban ini meningkat pada pasangan di atas (silakan pilih mereka), dengan asumsi bahwa selain membuat satu komit (no-parent-no-history), Anda juga ingin mempertahankan semua data komit komit tersebut:

  • Penulis (nama dan email)
  • Tanggal yang diotorisasi
  • Commiter (nama dan email)
  • Tanggal yang ditentukan
  • Pesan log komit

Tentu saja komit-SHA dari komit baru / tunggal akan berubah, karena itu merepresentasikan sejarah baru (non-), menjadi komitmen tanpa orangtua / root.

Ini dapat dilakukan dengan membaca git logdan mengatur beberapa variabel untuk git commit-tree. Dengan asumsi bahwa Anda ingin membuat komit tunggal masterdi cabang baru one-commit, simpan data komit di atas:

git checkout -b one-commit master ## create new branch to reset
git reset --hard \
$(eval "$(git log master -n1 --format='\
COMMIT_MESSAGE="%B" \
GIT_AUTHOR_NAME="%an" \
GIT_AUTHOR_EMAIL="%ae" \
GIT_AUTHOR_DATE="%ad" \
GIT_COMMITTER_NAME="%cn" \
GIT_COMMITTER_EMAIL="%ce" \
GIT_COMMITTER_DATE="%cd"')" 'git commit-tree master^{tree} <<COMMITMESSAGE
$COMMIT_MESSAGE
COMMITMESSAGE
')
javabrett
sumber
1

Untuk melakukan ini, Anda dapat mereset repositori git lokal Anda ke tagar komit pertama, sehingga semua perubahan Anda setelah komit tersebut akan tidak bertahap, kemudian Anda bisa komit dengan opsi --amend.

git reset your-first-commit-hashtag
git add .
git commit --amend

Dan kemudian edit nam komit pertama jika diperlukan dan simpan file.

T.EL MOUSSAOUI
sumber
1

Bagi saya ini berfungsi seperti ini: Saya memiliki total 4 komitmen, dan menggunakan rebase interaktif:

git rebase -i HEAD~3

Komit pertama tetap dan saya mengambil 3 komit terbaru.

Jika Anda terjebak dalam editor yang muncul berikutnya, Anda melihat sesuatu seperti:

pick fda59df commit 1
pick x536897 commit 2
pick c01a668 commit 3

Anda harus mengambil komit pertama dan menekan orang lain ke dalamnya. Yang harus Anda miliki adalah:

pick fda59df commit 1
squash x536897 commit 2
squash c01a668 commit 3

Untuk itu gunakan tombol INSERT untuk mengubah mode 'masukkan' dan 'edit'.

Untuk menyimpan dan keluar dari penggunaan editor :wq. Jika kursor Anda berada di antara garis komit atau di tempat lain tekan ESC dan coba lagi.

Sebagai hasilnya, saya memiliki dua komit: yang pertama tetap dan yang kedua dengan pesan "Ini adalah kombinasi dari 3 komit.".

Periksa detailnya di sini: https://makandracards.com/makandra/527-squash-several-git-commits-into-a-single-commit

Vlad
sumber
0

Saya biasanya melakukannya seperti ini:

  • Pastikan semuanya berkomitmen, dan tuliskan id komit terbaru jika terjadi kesalahan, atau buat cabang terpisah sebagai cadangan

  • Jalankan git reset --soft `git rev-list --max-parents=0 --abbrev-commit HEAD`untuk mengatur ulang kepala Anda ke komit pertama, tetapi biarkan indeks Anda tidak berubah. Semua perubahan sejak komit pertama sekarang akan tampak siap untuk berkomitmen.

  • Jalankan git commit --amend -m "initial commit"untuk mengubah komit Anda ke komit pertama dan ubah pesan komit, atau jika Anda ingin menyimpan pesan komit yang ada, Anda dapat menjalankangit commit --amend --no-edit

  • Berlari git push -funtuk memaksa mendorong perubahan Anda

Kent Munthe Caspersen
sumber