Hapus file sensitif dan komitmennya dari riwayat Git

353

Saya ingin menempatkan proyek Git di GitHub tetapi berisi file-file tertentu dengan data sensitif (nama pengguna dan kata sandi, seperti /config/deploy.rb untuk capistrano).

Saya tahu saya bisa menambahkan nama file ini ke .gitignore , tetapi ini tidak akan menghapus riwayatnya di dalam Git.

Saya juga tidak ingin memulai lagi dengan menghapus direktori /.git.

Apakah ada cara untuk menghapus semua jejak file tertentu di riwayat Git Anda?

Stefan
sumber

Jawaban:

448

Untuk semua tujuan praktis, hal pertama yang harus Anda khawatirkan adalah MENGUBAH PASSWORDS ANDA! Tidak jelas dari pertanyaan Anda apakah repositori git Anda sepenuhnya lokal atau apakah Anda memiliki repositori jarak jauh di tempat lain; jika remote dan tidak diamankan dari orang lain, Anda memiliki masalah. Jika ada orang yang mengkloning repositori itu sebelum Anda memperbaikinya, mereka akan memiliki salinan kata sandi Anda di mesin lokal mereka, dan tidak mungkin Anda bisa memaksa mereka untuk memperbarui ke versi "tetap" Anda dengan itu hilang dari sejarah. Satu-satunya hal aman yang dapat Anda lakukan adalah mengubah kata sandi Anda menjadi sesuatu yang lain di mana pun Anda menggunakannya.


Dengan cara itu, inilah cara untuk memperbaikinya. GitHub menjawab pertanyaan itu sebagai FAQ :

Catatan untuk pengguna Windows : gunakan tanda kutip ganda (") sebagai ganti tunggal dalam perintah ini

git filter-branch --index-filter \
'git update-index --remove PATH-TO-YOUR-FILE-WITH-SENSITIVE-DATA' <introduction-revision-sha1>..HEAD
git push --force --verbose --dry-run
git push --force

Pembaruan 2019:

Ini adalah kode saat ini dari FAQ:

  git filter-branch --force --index-filter \
  "git rm --cached --ignore-unmatch PATH-TO-YOUR-FILE-WITH-SENSITIVE-DATA" \
  --prune-empty --tag-name-filter cat -- --all
  git push --force --verbose --dry-run
  git push --force

Ingatlah bahwa setelah Anda mendorong kode ini ke repositori jarak jauh seperti GitHub dan yang lainnya telah mengkloning repositori jarak jauh itu, Anda sekarang berada dalam situasi di mana Anda menulis ulang sejarah. Ketika orang lain mencoba menarik perubahan terbaru Anda setelah ini, mereka akan mendapatkan pesan yang menunjukkan bahwa perubahan tidak dapat diterapkan karena itu bukan fast-forward.

Untuk memperbaikinya, mereka harus menghapus repositori yang sudah ada dan mengkloning kembali, atau mengikuti instruksi di bawah "MEMULIHKAN DARI REBASE UPSTREAM" di halaman manual git-rebase .

Kiat : Jalankangit rebase --interactive


Di masa depan, jika Anda secara tidak sengaja melakukan beberapa perubahan dengan informasi sensitif tetapi Anda perhatikan sebelum mendorong ke repositori jarak jauh, ada beberapa perbaikan yang lebih mudah. Jika komit terakhir adalah yang menambahkan informasi sensitif, Anda dapat menghapus informasi sensitif, kemudian jalankan:

git commit -a --amend

Itu akan mengubah komit sebelumnya dengan setiap perubahan baru yang Anda buat, termasuk seluruh penghapusan file yang dilakukan dengan a git rm. Jika perubahan lebih lanjut dalam sejarah tetapi masih tidak didorong ke repositori jarak jauh, Anda dapat melakukan rebase interaktif:

git rebase -i origin/master

Itu membuka editor dengan komit yang Anda buat sejak nenek moyang terakhir bersama dengan repositori jarak jauh. Ubah "pilih" menjadi "edit" pada baris apa pun yang mewakili komit dengan informasi sensitif, dan simpan dan keluar. Git akan berjalan melalui perubahan, dan meninggalkan Anda di tempat di mana Anda dapat:

$EDITOR file-to-fix
git commit -a --amend
git rebase --continue

Untuk setiap perubahan dengan informasi sensitif. Akhirnya, Anda akan kembali ke cabang Anda, dan Anda dapat dengan aman mendorong perubahan baru.

natacado
sumber
5
Sobat sempurna, itu jawaban yang bagus. Anda menghemat hari saya.
zzeroo
18
Hanya untuk menambahkan satu bit - pada Windows, Anda harus menggunakan tanda kutip ganda (") alih-alih tunggal.
ripper234
4
Ini berhasil. Saya tersesat dalam terjemahan. Saya menggunakan tautan alih-alih perintah di sini. Juga, perintah Windows pada akhirnya membutuhkan tanda kutip ganda seperti ripper234 menyebutkan, path lengkap seperti yang disarankan MigDus, dan tidak termasuk karakter "\" yang tautannya ditempelkan sebagai indikator pembungkus baris baru. Perintah terakhir terlihat seperti: git filter-branch --force --index-filter "git rm --cached --ignore-unmatch src [Project] [File]. [Ext]" --prune-empty --tag- name-filter cat - --all
Eric Swanson
3
Tampaknya ada beberapa perbedaan mendasar antara filter-branchkode Anda dan bahwa pada halaman github yang Anda tautkan. Misalnya baris ke-3 mereka --prune-empty --tag-name-filter cat -- --all. Apakah solusinya berubah atau saya kehilangan sesuatu?
geotheory
2
Solusi ini terlihat cukup bagus, tetapi jika saya telah memperkenalkan file untuk dihapus di awal komit <introduction-revision-sha1>..HEADtidak berfungsi. Ini hanya menghapus file dari komit kedua dan seterusnya. (Bagaimana cara saya memasukkan komit awal ke dalam rentang komit?) Cara simpanan ditunjukkan di sini: help.github.com/articles/…git filter-branch --force --index-filter \ 'git rm --cached --ignore-unmatch PATH-TO-YOUR-FILE-WITH-SENSITIVE-DATA' \ --prune-empty --tag-name-filter cat -- --all
white_gecko
91

Mengubah kata sandi Anda adalah ide yang bagus, tetapi untuk proses menghapus kata sandi dari riwayat repo Anda, saya merekomendasikan BFG Repo-Cleaner , sebuah alternatif yang lebih cepat dan lebih sederhana untuk git-filter-branchsecara eksplisit dirancang untuk menghapus data pribadi dari repositori Git.

Membuat private.txt file yang mencantumkan kata sandi, dll, yang ingin Anda hapus (satu entri per baris) dan kemudian jalankan perintah ini:

$ java -jar bfg.jar  --replace-text private.txt  my-repo.git

Semua file di bawah ukuran ambang (1MB secara default) dalam riwayat repo Anda akan dipindai, dan setiap string yang cocok (yang tidak ada dalam komit terbaru Anda ) akan diganti dengan string "*** DIHAPUS ***". Anda kemudian dapat menggunakan git gcuntuk membersihkan data yang mati:

$ git gc --prune=now --aggressive

BFG biasanya 10-50x lebih cepat daripada berjalan git-filter-branchdan opsinya disederhanakan dan disesuaikan dengan dua use case berikut:

  • Menghapus File Besar Gila
  • Menghapus Kata Sandi, Kredensial & Data pribadi lainnya

Pengungkapan penuh: Saya penulis Repo-Cleaner BFG.

Roberto Tyley
sumber
Ini adalah pilihan, tetapi itu bisa merusak aplikasi Anda ketika kata sandi digunakan, misalnya untuk mengatur koneksi database. Saya lebih suka jawaban yang saat ini diterima karena masih bisa menyimpan kata sandi dalam copy pekerjaan Anda dan mengabaikan file yang berisinya dengan .gitignore.
Henridv
6
Ini adalah kemenangan besar di sini. Setelah beberapa kali mencoba, saya dapat menggunakannya untuk menghapus komit yang berisi informasi sensitif dari repo pribadi dengan sangat teliti dan secara paksa memperbarui repo jarak jauh dengan riwayat yang direvisi. Satu catatan tambahan adalah Anda harus memastikan bahwa ujung repo (HEAD) Anda sendiri bersih tanpa data sensitif karena komit ini dianggap "dilindungi" dan tidak akan direvisi oleh alat ini. Jika tidak, cukup bersihkan / ganti secara manual dan git commit. Jika tidak, +1 untuk alat baru di kotak alat pengembang :)
Matt Borja
1
@Henridv Per komentar saya baru-baru ini, itu tidak boleh merusak aplikasi Anda seperti yang Anda perkirakan, dengan asumsi aplikasi Anda saat ini terletak di ujung atau kepala cabang Anda (yaitu komit terbaru). Alat ini akan secara eksplisit melaporkan komit terakhir Anda These are your protected commits, and so their contents will NOT be alteredsaat melintasi dan merevisi sisa riwayat komit Anda. Namun, jika Anda perlu mengembalikan, maka ya Anda harus melakukan pencarian ***REMOVED***di komit yang baru saja Anda putar.
Matt Borja
1
+1 untuk BFG (jika Anda memiliki Java yang diinstal atau tidak keberatan menginstalnya). Satu tangkapan adalah bahwa BFG menolak untuk menghapus file jika terkandung dalam HEAD. Jadi lebih baik terlebih dahulu melakukan komit di mana file yang diinginkan akan dihapus dan hanya kemudian menjalankan BFG. Setelah itu Anda dapat mengembalikan komit terakhir, sekarang tidak mengubah apa pun.
Fr0sT
1
Ini sebenarnya harus diterima sebagai jawaban yang benar. Lakukan apa yang tertulis di kotak!
gjoris
21

Jika Anda mendorong ke GitHub, memaksakan dorongan tidak cukup, hapus repositori atau hubungi dukungan

Bahkan jika Anda memaksa mendorong satu detik setelah itu, itu tidak cukup seperti yang dijelaskan di bawah ini.

Satu-satunya tindakan yang valid adalah:

  • Apakah yang dibocorkan adalah kredensial yang dapat diubah seperti kata sandi?

    • ya: segera ubah kata sandi Anda, dan pertimbangkan untuk menggunakan lebih banyak kunci OAuth dan API!
    • tidak ada (foto telanjang):

      • apakah Anda peduli jika semua masalah di repositori dihilangkan?

        • tidak: hapus repositori
        • Iya:

          • hubungi dukungan
          • jika kebocoran sangat penting bagi Anda, sampai Anda bersedia untuk mendapatkan beberapa downtime repositori untuk membuatnya lebih kecil kemungkinannya bocor, jadikan itu pribadi sementara Anda menunggu dukungan GitHub untuk membalas Anda

Memaksa satu detik kemudian tidak cukup karena:

Jika Anda menghapus repositori alih-alih hanya memaksakan dorongan, komit segera menghilang bahkan dari API dan memberikan 404, misalnya https://api.github.com/repos/cirosantilli/test-dangling-delete/commits/8c08448b5fbf0f891696819f3b2b2d653f7a24 ini karya bahkan jika Anda membuat ulang repositori lain dengan nama yang sama.

Untuk menguji ini, saya telah membuat repo: https://github.com/cirosantilli/test-dangling dan lakukan:

git init
git remote add origin [email protected]:cirosantilli/test-dangling.git

touch a
git add .
git commit -m 0
git push

touch b
git add .
git commit -m 1
git push

touch c
git rm b
git add .
git commit --amend --no-edit
git push -f

Lihat juga: Bagaimana cara menghapus komit menggantung dari GitHub?

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
sumber
20

Saya merekomendasikan naskah ini oleh David Underhill, bekerja seperti pesona bagi saya.

Ia menambahkan perintah-perintah ini sebagai tambahan cabang-filter natacado untuk membersihkan kekacauan yang ditinggalkannya:

rm -rf .git/refs/original/
git reflog expire --all
git gc --aggressive --prune

Skrip lengkap (semua kredit ke David Underhill)

#!/bin/bash
set -o errexit

# Author: David Underhill
# Script to permanently delete files/folders from your git repository.  To use 
# it, cd to your repository's root and then run the script with a list of paths
# you want to delete, e.g., git-delete-history path1 path2

if [ $# -eq 0 ]; then
    exit 0
fi

# make sure we're at the root of git repo
if [ ! -d .git ]; then
    echo "Error: must run this script from the root of a git repository"
    exit 1
fi

# remove all paths passed as arguments from the history of the repo
files=$@
git filter-branch --index-filter \
"git rm -rf --cached --ignore-unmatch $files" HEAD

# remove the temporary history git-filter-branch
# otherwise leaves behind for a long time
rm -rf .git/refs/original/ && \
git reflog expire --all && \
git gc --aggressive --prune

Dua perintah terakhir mungkin berfungsi lebih baik jika diubah menjadi berikut:

git reflog expire --expire=now --all && \
git gc --aggressive --prune=now
Jason Goemaat
sumber
1
Perhatikan bahwa penggunaan kedaluwarsa dan pemangkasan Anda salah, jika Anda tidak menentukan tanggal maka default untuk semua komit lebih dari 2 minggu untuk pemangkasan. Apa yang Anda inginkan adalah semua komit sehingga melakukan:git gc --aggressive --prune=now
Adam Parkin
@ Adam Parkin Saya akan meninggalkan kode dalam jawaban yang sama karena berasal dari script di situs David Underhill, Anda dapat berkomentar di sana dan jika dia mengubahnya saya akan mengubah jawaban ini karena saya benar-benar tidak tahu git bahwa baik. Perintah kedaluwarsa sebelum prune tidak memengaruhi apakah itu?
Jason Goemaat
1
@MarkusUnterwaditzer: Yang itu tidak akan berfungsi karena komitmen yang ditekan.
Max Beikirch
Mungkin Anda harus meletakkan semua perintah dalam jawaban Anda; itu akan jauh lebih konsisten dan tidak akan memerlukan mental menggabungkan posting terpisah :)
Andrew Mao
9

Agar jelas: Jawaban yang diterima benar. Coba dulu. Namun, itu mungkin tidak perlu rumit untuk beberapa kasus penggunaan, terutama jika Anda menemukan kesalahan yang menjengkelkan seperti 'fatal: revisi buruk --prune-kosong', atau benar-benar tidak peduli tentang sejarah repo Anda.

Alternatifnya adalah:

  1. cd ke cabang dasar proyek
  2. Hapus kode / file sensitif
  3. rm -rf .git / # Hapus semua info git dari kode Anda
  4. Pergi ke github dan hapus repositori Anda
  5. Ikuti panduan ini untuk mendorong kode Anda ke repositori baru seperti biasanya - https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/

Ini tentu saja akan menghapus semua cabang histori komit, dan masalah dari repo github Anda, dan repo git lokal Anda. Jika ini tidak dapat diterima, Anda harus menggunakan pendekatan alternatif.

Sebut ini opsi nuklir.

kehilangan filsuf
sumber
9

Kamu bisa menggunakan git forget-blob .

Penggunaannya cukup sederhana git forget-blob file-to-forget . Anda bisa mendapatkan info lebih lanjut di sini

https://ownyourbits.com/2017/01/18/completely-remove-a-file-from-a-git-repository-with-git-forget-blob/

Ini akan hilang dari semua komit dalam riwayat Anda, reflog, tag, dan sebagainya

Saya mengalami masalah yang sama setiap sekarang dan kemudian, dan setiap kali saya harus kembali ke posting ini dan yang lainnya, itu sebabnya saya mengotomatiskan prosesnya.

Kredit untuk kontributor dari Stack Overflow yang memungkinkan saya untuk menyatukan ini

nachoparker
sumber
8

Ini solusi saya di windows

git filter-branch --tree-filter "KEPALA -f 'diarsipkan / nama file'" KEPALA

git push --force

pastikan jalurnya benar jika tidak maka tidak akan berhasil

Saya harap ini membantu

vertigo71
sumber
8

Gunakan cabang-filter :

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

git push origin *branch_name* -f
Shiv Krishna Jaiswal
sumber
3

Saya harus melakukan ini beberapa kali to-date. Perhatikan bahwa ini hanya berfungsi pada 1 file pada satu waktu.

  1. Dapatkan daftar semua komit yang mengubah file. Yang di bawah akan melakukan komit pertama:

    git log --pretty=oneline --branches -- pathToFile

  2. Untuk menghapus file dari histori gunakan komit pertama sha1 dan path ke file dari perintah sebelumnya, dan isi mereka ke dalam perintah ini:

    git filter-branch --index-filter 'git rm --cached --ignore-unmatch <path-to-file>' -- <sha1-where-the-file-was-first-added>..

b01
sumber
3

Jadi, ini terlihat seperti ini:

git rm --cached /config/deploy.rb
echo /config/deploy.rb >> .gitignore

Hapus cache untuk file yang dilacak dari git dan tambahkan file itu ke .gitignoredaftar

przbadu
sumber
2

Dalam proyek android saya, saya memiliki admob_keys.xml sebagai file xml yang terpisah di folder app / src / main / res / values ​​/ . Untuk menghapus file sensitif ini saya menggunakan skrip di bawah ini dan bekerja dengan sempurna.

git filter-branch --force --index-filter \
'git rm --cached --ignore-unmatch  app/src/main/res/values/admob_keys.xml' \
--prune-empty --tag-name-filter cat -- --all
Ercan
sumber