Buat komit saat ini satu-satunya (awal) komit dalam repositori Git?

665

Saat ini saya memiliki repositori Git lokal, yang saya dorong ke repositori Github.

Repositori lokal memiliki ~ 10 commit, dan repositori Github adalah duplikat tersinkronisasi dari ini.

Apa yang ingin saya lakukan adalah menghapus SEMUA riwayat versi dari repositori Git lokal, sehingga konten repositori saat ini muncul sebagai satu-satunya komit (dan karenanya file versi lama dalam repositori tidak disimpan).

Saya kemudian ingin mendorong perubahan ini ke Github.

Saya telah menyelidiki Git rebase, tetapi ini tampaknya lebih cocok untuk menghapus versi tertentu. Solusi potensial lain adalah dengan menghapus repo lokal, dan membuat yang baru - meskipun ini mungkin akan membuat banyak pekerjaan!

ETA: Ada direktori / file tertentu yang tidak dapat dilacak - jika mungkin saya ingin menjaga pelacakan dari file-file ini.

kaese
sumber
6
Lihat juga stackoverflow.com/questions/435646/… ("Bagaimana saya menggabungkan dua commit pertama dari repositori Git?")
Anonymoose

Jawaban:

982

Inilah pendekatan brute-force. Itu juga menghapus konfigurasi repositori.

Catatan : Ini TIDAK berfungsi jika repositori memiliki submodula! Jika Anda menggunakan submodul, Anda harus menggunakan mis. Rebase interaktif

Langkah 1: hapus semua riwayat ( Pastikan Anda memiliki cadangan, ini tidak dapat dikembalikan )

cat .git/config  # note <github-uri>
rm -rf .git

Langkah 2: merekonstruksi repo Git hanya dengan konten saat ini

git init
git add .
git commit -m "Initial commit"

Langkah 3: dorong ke GitHub.

git remote add origin <github-uri>
git push -u --force origin master
Fred Foo
sumber
3
Terima kasih larsmans - Saya telah memilih untuk menggunakan ini sebagai solusi saya. Meskipun menginisialisasi repo Git kehilangan catatan file yang tidak terlacak dalam repo lama, ini mungkin solusi yang lebih sederhana untuk masalah saya.
kaese
5
@kaese: Saya pikir Anda .gitignoreharus menangani itu, kan?
Fred Foo
48
Simpan .git / config Anda sebelumnya, dan kembalikan setelah itu.
lalebarde
@ lalebarde Jika Anda mengembalikan .git / config setelahnya, git commit -m "Initial commit"Anda mungkin dapat melewatkan git remote add ...bagian tersebut, dengan anggapan bahwa itu sudah ada dalam konfigurasi Anda, dan langsung beralih ke mendorong. Ini berhasil untuk saya.
Buttle Butkus
24
Hati-hati dengan ini jika Anda mencoba untuk menghapus data sensitif: keberadaan hanya satu komit di cabang master yang baru saja didorong adalah menyesatkan - sejarah akan tetap ada tetapi tidak akan dapat diakses dari cabang itu. Jika Anda memiliki tag, misalnya, yang menunjukkan komit yang lebih lama, komit ini akan dapat diakses. Faktanya, bagi siapa pun dengan sedikit git foo, saya yakin bahwa setelah dorongan git ini, mereka masih dapat memulihkan semua riwayat dari repositori GitHub - dan jika Anda memiliki cabang atau tag lain, maka mereka tidak bahkan butuh banyak git foo.
Robert Muil
621

Satu-satunya solusi yang bekerja untuk saya (dan membuat submodula berfungsi) adalah

git checkout --orphan newBranch
git add -A  # Add all files and commit them
git commit
git branch -D master  # Deletes the master branch
git branch -m master  # Rename the current branch to master
git push -f origin master  # Force push master branch to github
git gc --aggressive --prune=all     # remove the old files

Menghapus .git/selalu menyebabkan masalah besar ketika saya memiliki submodula. git rebase --rootEntah bagaimana menggunakan akan menyebabkan konflik bagi saya (dan butuh waktu lama sejak saya memiliki banyak sejarah).

Zeelot
sumber
55
ini seharusnya jawaban yang benar! tambahkan saja git push -f origin mastersebagai op terakhir dan matahari akan bersinar lagi pada repo baru Anda! :)
gru
2
Apakah ini tidak membuat komitmen lama tetap ada?
Brad
4
@JonePolvora git fetch; git reset --hard origin / master stackoverflow.com/questions/4785107/…
echo
5
setelah melakukan ini, akankah ruang bebas repo?
Inuart
8
Saya yakin Anda harus menambahkan saran @JasonGoemaat sebagai baris terakhir untuk jawaban Anda. Tanpa git gc --aggressive --prune allseluruh poin kehilangan sejarah akan terlewatkan.
Tuncay Göncüoğlu
93

Ini adalah pendekatan favorit saya:

git branch new_branch_name $(echo "commit message" | git commit-tree HEAD^{tree})

Ini akan membuat cabang baru dengan satu komit yang menambahkan semuanya di HEAD. Itu tidak mengubah apa pun, jadi itu benar-benar aman.

dan_waterworth
sumber
3
Pendekatan terbaik! Bersihkan, dan lakukan pekerjaan. Selain itu, saya mengubah nama cabang dengan banyak perubahan dari "master" menjadi "local-work" dan "new_branch_name" menjadi "master". Dalam master, lakukan hal berikut: git -m local-changes git branch -m local-changes git checkout new_branch_name cabang git -m master <
Valtoni Boaventura
Ini terlihat sangat pendek dan ramping, satu-satunya hal yang saya tidak mengerti atau belum lihat adalah HEAD ^ {tree}, dapatkah seseorang menjelaskan? Selain itu saya membaca ini sebagai "membuat cabang baru dari komit yang diberikan, dibuat dengan membuat komit-objek baru dengan komit-pesan yang diberikan dari ___"
TomKeegasi
3
Tempat definitif untuk mencari jawaban atas pertanyaan tentang sintaks referensi git ada dalam git-rev-parsedokumen. Apa yang terjadi di sini adalah git-commit-treememerlukan referensi ke pohon (snapshot dari repo), tetapi HEADmerupakan revisi. Untuk menemukan pohon yang terkait dengan komit, kami menggunakan <rev>^{<type>}formulir.
dan_waterworth
Jawaban bagus. Bekerja dengan baik. Akhirnya katakangit push --force <remote> new_branch_name:<remote-branch>
Felipe Alvarez
31

Opsi lain, yang bisa berubah menjadi banyak pekerjaan jika Anda memiliki banyak komitmen, adalah rebase interaktif (dengan asumsi versi git Anda> = 1.7.12):git rebase --root -i

Ketika disajikan dengan daftar komitmen di editor Anda:

  • Ubah "pilih" menjadi "reword" untuk komit pertama
  • Ubah "pilih" menjadi "perbaikan" setiap komit lainnya

Simpan dan tutup. Git akan mulai rebasing.

Pada akhirnya Anda akan memiliki komit root baru yang merupakan kombinasi dari semua yang datang setelahnya.

Keuntungannya adalah Anda tidak perlu menghapus repositori dan jika Anda berpikir dua kali, Anda selalu memiliki cadangan.

Jika Anda benar-benar ingin menghapus riwayat Anda, setel ulang master ke komit ini dan hapus semua cabang lainnya.

Carl
sumber
Setelah rebase selesai, saya tidak bisa mendorong:error: failed to push some refs to
Begueradj
@ Begueradj jika Anda sudah mendorong cabang yang Anda rebined, maka Anda harus memaksakan dorongan git push --force-with-lease. force-with-leasing digunakan karena kurang merusak daripada - force.
Carl
19

Varian dari metode larsman yang diusulkan:

Simpan daftar untrackfiles Anda:

git ls-files --others --exclude-standard > /tmp/my_untracked_files

Simpan konfigurasi git Anda:

mv .git/config /tmp/

Kemudian lakukan langkah pertama larsman:

rm -rf .git
git init
git add .

Pulihkan konfigurasi Anda:

mv /tmp/config .git/

Hapus jejak file yang tidak terlacak:

cat /tmp/my_untracked_files | xargs -0 git rm --cached

Kemudian lakukan:

git commit -m "Initial commit"

Dan akhirnya dorong ke repositori Anda:

git push -u --force origin master
lalebarde
sumber
6

Di bawah ini adalah skrip yang diadaptasi dari jawaban @Zeelot. Seharusnya menghapus sejarah dari semua cabang, bukan hanya cabang master:

for BR in $(git branch); do   
  git checkout $BR
  git checkout --orphan ${BR}_temp
  git commit -m "Initial commit"
  git branch -D $BR
  git branch -m $BR
done;
git gc --aggressive --prune=all

Ini berfungsi untuk tujuan saya (saya tidak menggunakan submodula).

Shafique Jamal
sumber
4
Saya pikir Anda lupa untuk memaksa push master untuk menyelesaikan prosedur.
not2qubit
2
Saya harus melakukan sedikit modifikasi. git branchakan menyertakan tanda bintang di sebelah cabang yang Anda periksa, yang kemudian akan di-globbed, menyebabkannya untuk menyelesaikan semua file atau folder seolah-olah itu juga nama-nama cabang. Sebagai gantinya, saya menggunakan git branch --format="%(refname:lstrip=2)"yang memberi saya hanya nama cabang.
Ben Richards
@ not2qubit: Terima kasih untuk ini. Apa yang akan menjadi perintah yang tepat? git push --force origin master, atau git push --force-with-lease? Rupanya yang terakhir lebih aman (lihat stackoverflow.com/questions/5509543/… )
Shafique Jamal
@BenRichards. Menarik. Saya akan mencoba ini lagi di beberapa titik dengan folder yang cocok dengan nama cabang untuk mengujinya, lalu perbarui jawabannya. Terima kasih.
Shafique Jamal
4

git filter-branch adalah alat operasi utama.

git filter-branch --parent-filter true -- @^!

--parent-filtermembuat orang tua memakai stdin dan harus mencetak orang tua yang ditulis ulang di stdout; unix trueberhasil keluar dan tidak mencetak apa pun, jadi: tidak ada orang tua. @^!adalah singkatan Git untuk "kepala melakukan tetapi tidak ada orang tuanya". Kemudian hapus semua referensi lainnya dan dorong pada waktu luang.

jthill
sumber
3

Hapus saja repo Github dan buat yang baru. Sejauh ini pendekatan tercepat, termudah dan teraman. Lagi pula, apa yang harus Anda dapatkan dengan menjalankan semua perintah itu dalam solusi yang diterima ketika yang Anda inginkan adalah cabang utama dengan satu komit?

AndroidDev
sumber
1
Salah satu poin utama adalah untuk dapat melihat dari mana ia bercabang.
not2qubit
Saya hanya melakukan ini dan tidak masalah
thanos.a
2

Metode di bawah ini benar-benar dapat direproduksi, jadi tidak perlu menjalankan klon lagi jika kedua belah pihak konsisten, cukup jalankan skrip di sisi lain juga.

git log -n1 --format=%H >.git/info/grafts
git filter-branch -f
rm .git/info/grafts

Jika Anda ingin membersihkannya, coba skrip ini:

http://sam.nipl.net/b/git-gc-all-ferocious

Saya menulis sebuah skrip yang "membunuh sejarah" untuk setiap cabang di repositori:

http://sam.nipl.net/b/git-kill-history

lihat juga: http://sam.nipl.net/b/confirm

Sam Watkins
sumber
1
Terima kasih untuk ini. Hanya FYI: skrip Anda untuk membunuh sejarah untuk setiap cabang dapat menggunakan beberapa pembaruan - ini memberikan kesalahan berikut: git-hash: not founddanSupport for <GIT_DIR>/info/grafts is deprecated
Shafique Jamal
1
@ShafiqueJamal, terima kasih, skrip kecil "git-hash" adalah git log HEAD~${1:-0} -n1 --format=%H, di sini, sam.aiki.info/b/git-hash Akan lebih baik untuk meletakkan semuanya dalam satu skrip untuk konsumsi publik. Jika saya pernah menggunakannya lagi, saya mungkin mencari cara untuk melakukannya dengan fitur baru yang menggantikan "cangkokan".
Sam Watkins
2

Apa yang ingin saya lakukan adalah menghapus SEMUA riwayat versi dari repositori Git lokal, sehingga konten repositori saat ini muncul sebagai satu-satunya komit (dan karenanya file versi lama dalam repositori tidak disimpan).

Jawaban yang lebih konseptual:

git secara otomatis mengumpulkan sampah commit lama jika tidak ada tag / cabang / referensi menunjuk mereka. Jadi, Anda hanya perlu menghapus semua tag / cabang dan membuat komit yatim baru, yang terkait dengan cabang apa pun - dengan konvensi Anda akan membiarkan cabang mastermenunjuk komit itu.

Komit lama yang tidak terjangkau tidak akan pernah lagi dilihat oleh siapa pun kecuali mereka menggali dengan perintah git tingkat rendah. Jika itu cukup bagi Anda, saya hanya akan berhenti di situ dan membiarkan GC otomatis melakukan tugasnya kapan pun ia mau. Jika Anda ingin segera menyingkirkannya, Anda dapat menggunakan git gc(mungkin dengan --aggressive --prune=all). Untuk repositori git jarak jauh, tidak ada cara bagi Anda untuk memaksakan itu, kecuali jika Anda memiliki akses shell ke sistem file mereka.

AnoE
sumber
Tambahan yang bagus, jika dilihat dalam konteks jawaban @Zeelot.
Mogens TrasherDK
Yup, Zeelot's memiliki perintah yang pada dasarnya melakukan ini (hanya berbeda, dengan memulai sepenuhnya, yang mungkin baik-baik saja untuk OP). @MogensTrasherDK
AnoE
0

Ini dia:

#!/bin/bash
#
# By Zibri (2019)
#
# Usage: gitclean username password giturl
#
gitclean () 
{ 
    odir=$PWD;
    if [ "$#" -ne 3 ]; then
        echo "Usage: gitclean username password giturl";
        return 1;
    fi;
    temp=$(mktemp -d 2>/dev/null /dev/shm/git.XXX || mktemp -d 2>/dev/null /tmp/git.XXX);
    cd "$temp";
    url=$(echo "$3" |sed -e "s/[^/]*\/\/\([^@]*@\)\?\.*/\1/");
    git clone "https://$1:$2@$url" && { 
        cd *;
        for BR in "$(git branch|tr " " "\n"|grep -v '*')";
        do
            echo working on branch $BR;
            git checkout $BR;
            git checkout --orphan $(basename "$temp"|tr -d .);
            git add -A;
            git commit -m "Initial Commit" && { 
                git branch -D $BR;
                git branch -m $BR;
                git push -f origin $BR;
                git gc --aggressive --prune=all
            };
        done
    };
    cd $odir;
    rm -rf "$temp"
}

Juga dihosting di sini: https://gist.github.com/Zibri/76614988478a076bbe105545a16ee743

Zibri
sumber
Gah! Jangan buat saya memberikan kata sandi saya yang tidak dilindungi dan tidak terlindungi di baris perintah! Juga, output dari cabang git biasanya kurang cocok untuk skrip. Anda mungkin ingin melihat alat-alat pipa.
D. Ben Knoble
-1

Saya memecahkan masalah yang sama dengan hanya menghapus .gitfolder dari proyek saya dan mengintegrasikan kembali dengan kontrol versi melalui IntelliJ. Catatan: .gitFolder ini disembunyikan. Anda dapat melihatnya di terminal dengan ls -a, dan kemudian menghapusnya menggunakan rm -rf .git.

JB Lovell
sumber
Itulah yang dia lakukan pada langkah 1: rm -rf .git?
malam
-1

Untuk itu gunakan perintah Dangkal Klon git klon --depth 1 URL - Ini hanya akan mengkloning KEPALA repositori

kkarki
sumber
-2

Untuk menghapus komit terakhir dari git, Anda cukup menjalankannya

git reset --hard HEAD^ 

Jika Anda menghapus beberapa komit dari atas, Anda dapat menjalankan

git reset --hard HEAD~2 

untuk menghapus dua komitmen terakhir. Anda dapat menambah jumlahnya untuk menghapus lebih banyak komit.

Info lebih lanjut di sini.

Git tutoturial di sini menyediakan bantuan tentang cara membersihkan repositori:

Anda ingin menghapus file dari histori dan menambahkannya ke .gitignore untuk memastikan itu tidak dilakukan kembali secara tidak sengaja. Sebagai contoh kita, kita akan menghapus Rakefile dari repositori permata GitHub.

git clone https://github.com/defunkt/github-gem.git

cd github-gem

git filter-branch --force --index-filter \
  'git rm --cached --ignore-unmatch Rakefile' \
  --prune-empty --tag-name-filter cat -- --all

Sekarang kita telah menghapus file dari histori, mari kita pastikan bahwa kita tidak sengaja melakukannya lagi.

echo "Rakefile" >> .gitignore

git add .gitignore

git commit -m "Add Rakefile to .gitignore"

Jika Anda senang dengan keadaan repositori, Anda perlu mendorong-paksa perubahan untuk menimpa repositori jarak jauh.

git push origin master --force
Kiriloff
sumber
6
Menghapus file atau melakukan dari repositori sama sekali tidak ada hubungannya dengan pertanyaan (yang meminta untuk menghapus histori, hal yang sama sekali berbeda). OP ingin riwayat bersih tetapi ingin mempertahankan kondisi repositori saat ini.
Victor Schröder
ini tidak menghasilkan hasil yang ditanyakan dalam pertanyaan. Anda membuang semua perubahan setelah komit yang Anda simpan terakhir dan kehilangan semua perubahan sejak saat itu, tetapi pertanyaannya meminta untuk menyimpan file saat ini dan menghapus riwayat.
Tuncay Göncüoğlu