Giới thiệu: Bạn có 5 giải pháp khả dụng
Các poster gốc nêu rõ:
Tôi đã vô tình cam kết một tệp không mong muốn ... vào kho lưu trữ của tôi một số lần xác nhận trước đây ... Tôi muốn xóa hoàn toàn tệp khỏi lịch sử kho lưu trữ.
Có thể viết lại lịch sử thay đổi filename.orig
không bao giờ được thêm vào kho lưu trữ ở nơi đầu tiên không?
Có nhiều cách khác nhau để xóa hoàn toàn lịch sử của tệp khỏi git:
- Sửa đổi cam kết.
- Đặt lại cứng (có thể cộng với một rebase).
- Rebase không tương tác.
- Cuộc nổi loạn tương tác.
- Lọc nhánh.
Trong trường hợp của poster gốc, việc sửa đổi cam kết không thực sự là một lựa chọn, vì anh ta đã thực hiện một số cam kết bổ sung sau đó, nhưng để hoàn thiện, tôi cũng sẽ giải thích cách thực hiện, cho bất kỳ ai khác muốn để sửa đổi cam kết trước đó của họ.
Lưu ý rằng tất cả các giải pháp này liên quan đến việc thay đổi / viết lại lịch sử / cam kết theo cách này theo cách khác, vì vậy bất kỳ ai có bản sao cũ của các cam kết sẽ phải làm thêm để đồng bộ lại lịch sử của họ với lịch sử mới.
Giải pháp 1: Cam kết sửa đổi
Nếu bạn vô tình thực hiện thay đổi (chẳng hạn như thêm tệp) trong lần xác nhận trước đó và bạn không muốn lịch sử của thay đổi đó tồn tại nữa, thì bạn có thể chỉ cần sửa đổi cam kết trước đó để xóa tệp khỏi nó:
git rm <file>
git commit --amend --no-edit
Giải pháp 2: Thiết lập lại cứng (Có thể cộng với Rebase)
Giống như giải pháp số 1, nếu bạn chỉ muốn thoát khỏi cam kết trước đó, thì bạn cũng có tùy chọn đơn giản là thực hiện thiết lập lại cứng cho cha mẹ của nó:
git reset --hard HEAD^
Lệnh đó sẽ thiết lập lại chi nhánh của bạn thành cam kết cha mẹ thứ 1 trước đó .
Tuy nhiên , nếu, giống như người đăng ban đầu, bạn đã thực hiện một số cam kết sau khi cam kết bạn muốn hoàn tác thay đổi, bạn vẫn có thể sử dụng các thiết lập lại cứng để sửa đổi nó, nhưng làm như vậy cũng liên quan đến việc sử dụng rebase. Dưới đây là các bước mà bạn có thể sử dụng để sửa đổi một cam kết trở lại trong lịch sử:
# Create a new branch at the commit you want to amend
git checkout -b temp <commit>
# Amend the commit
git rm <file>
git commit --amend --no-edit
# Rebase your previous branch onto this new commit, starting from the old-commit
git rebase --preserve-merges --onto temp <old-commit> master
# Verify your changes
git diff master@{1}
Giải pháp 3: Rebase không tương tác
Điều này sẽ hoạt động nếu bạn chỉ muốn xóa hoàn toàn một cam kết khỏi lịch sử:
# Create a new branch at the parent-commit of the commit that you want to remove
git branch temp <parent-commit>
# Rebase onto the parent-commit, starting from the commit-to-remove
git rebase --preserve-merges --onto temp <commit-to-remove> master
# Or use `-p` insteda of the longer `--preserve-merges`
git rebase -p --onto temp <commit-to-remove> master
# Verify your changes
git diff master@{1}
Giải pháp 4: Khởi động lại tương tác
Giải pháp này sẽ cho phép bạn thực hiện những điều tương tự như giải pháp # 2 và # 3, tức là sửa đổi hoặc xóa các cam kết trở lại trong lịch sử so với cam kết trước đó của bạn, do đó, giải pháp bạn chọn sử dụng là tùy thuộc vào bạn. Các cuộc nổi loạn tương tác không phù hợp để đánh bại hàng trăm cam kết, vì lý do hiệu suất, vì vậy tôi sẽ sử dụng các cuộc nổi loạn không tương tác hoặc giải pháp nhánh lọc (xem bên dưới) trong các tình huống đó.
Để bắt đầu rebase tương tác, sử dụng như sau:
git rebase --interactive <commit-to-amend-or-remove>~
# Or `-i` instead of the longer `--interactive`
git rebase -i <commit-to-amend-or-remove>~
Điều này sẽ khiến git tua lại lịch sử cam kết trở lại cha mẹ của cam kết mà bạn muốn sửa đổi hoặc xóa. Sau đó, nó sẽ hiển thị cho bạn một danh sách các cam kết tua lại theo thứ tự ngược trong bất kỳ git biên tập nào được đặt để sử dụng (đây là Vim theo mặc định):
pick 00ddaac Add symlinks for executables
pick 03fa071 Set `push.default` to `simple`
pick 7668f34 Modify Bash config to use Homebrew recommended PATH
pick 475593a Add global .gitignore file for OS X
pick 1b7f496 Add alias for Dr Java to Bash config (OS X)
Cam kết mà bạn muốn sửa đổi hoặc xóa sẽ nằm ở đầu danh sách này. Để xóa nó, chỉ cần xóa dòng của nó trong danh sách. Nếu không, thay thế "chọn" bằng "chỉnh sửa" trên dòng thứ 1 , như vậy:
edit 00ddaac Add symlinks for executables
pick 03fa071 Set `push.default` to `simple`
Tiếp theo, nhập git rebase --continue
. Nếu bạn chọn xóa hoàn toàn cam kết, thì đó là tất cả những gì bạn cần làm (ngoài xác minh, xem bước cuối cùng cho giải pháp này). Mặt khác, nếu bạn muốn sửa đổi cam kết, thì git sẽ áp dụng lại cam kết và sau đó tạm dừng rebase.
Stopped at 00ddaacab0a85d9989217dd9fe9e1b317ed069ac... Add symlinks
You can amend the commit now, with
git commit --amend
Once you are satisfied with your changes, run
git rebase --continue
Tại thời điểm này, bạn có thể xóa tệp và sửa đổi cam kết, sau đó tiếp tục rebase:
git rm <file>
git commit --amend --no-edit
git rebase --continue
Đó là nó. Bước cuối cùng, cho dù bạn đã sửa đổi cam kết hoặc loại bỏ hoàn toàn cam kết, thì luôn luôn nên xác minh rằng không có thay đổi bất ngờ nào được thực hiện cho chi nhánh của bạn bằng cách phân biệt nó với trạng thái của nó trước khi rebase:
git diff master@{1}
Giải pháp 5: Lọc các nhánh
Cuối cùng, giải pháp này là tốt nhất nếu bạn muốn xóa sạch hoàn toàn mọi dấu vết về sự tồn tại của tệp khỏi lịch sử và không có giải pháp nào khác hoàn toàn phù hợp với nhiệm vụ.
git filter-branch --index-filter \
'git rm --cached --ignore-unmatch <file>'
Điều đó sẽ loại bỏ <file>
khỏi tất cả các cam kết, bắt đầu từ cam kết gốc. Nếu thay vào đó, bạn chỉ muốn viết lại phạm vi cam kết HEAD~5..HEAD
, thì bạn có thể chuyển nó dưới dạng một đối số bổ sung filter-branch
, như được chỉ ra trong
câu trả lời này :
git filter-branch --index-filter \
'git rm --cached --ignore-unmatch <file>' HEAD~5..HEAD
Một lần nữa, sau khi filter-branch
hoàn thành, thường là một ý tưởng tốt để xác minh rằng không có thay đổi bất ngờ nào khác bằng cách làm khác nhánh của bạn với trạng thái trước đó trước thao tác lọc:
git diff master@{1}
Bộ lọc thay thế chi nhánh: BFG Repo Cleaner
Tôi đã nghe nói rằng công cụ BFG Repo Cleaner chạy nhanh hơn git filter-branch
, vì vậy bạn cũng có thể muốn kiểm tra xem đó là một tùy chọn. Nó thậm chí còn được đề cập chính thức trong tài liệu nhánh lọc như một sự thay thế khả thi:
git-filter-Branch cho phép bạn tạo các bản ghi lại theo kịch bản shell phức tạp trong lịch sử Git của bạn, nhưng bạn có thể không cần sự linh hoạt này nếu bạn chỉ cần xóa dữ liệu không mong muốn như các tệp lớn hoặc mật khẩu. Đối với các hoạt động đó, bạn có thể muốn xem xét BFG Repo-Cleaner , một giải pháp thay thế dựa trên JVM cho nhánh bộ lọc git, thường nhanh hơn ít nhất 10-50 lần cho các trường hợp sử dụng đó và với các đặc điểm khá khác nhau:
Bất kỳ phiên bản cụ thể của một tập tin được làm sạch chính xác một lần . BFG, không giống như chi nhánh git-filter, không cho bạn cơ hội xử lý một tệp khác nhau dựa trên vị trí hoặc thời điểm cam kết trong lịch sử của bạn. Hạn chế này cho phép hiệu quả lợi ích cốt lõi của BFG, và rất phù hợp với nhiệm vụ của làm sạch dữ liệu xấu - bạn không quan tâm nơi các dữ liệu xấu là, bạn chỉ muốn nó biến mất .
Theo mặc định, BFG tận dụng tối đa các máy đa lõi, song song xóa sạch các cây tệp cam kết. Làm sạch git-filter-branch cam kết liên tục (tức là một cách đơn ren), mặc dù nó là
có thể viết các bộ lọc bao gồm parallellism riêng của họ, trong kịch bản thực hiện với nhau cam kết.
Các tùy chọn lệnh hạn chế hơn nhiều so với nhánh bộ lọc git và chỉ dành riêng cho các tác vụ xóa dữ liệu không mong muốn - ví dụ : --strip-blobs-bigger-than 1M
.
Tài nguyên bổ sung
- Pro Git § 6.4 Công cụ Git - Lịch sử viết lại .
- git-filter-Branch (1) Trang hướng dẫn .
- git-commit (1) Trang hướng dẫn .
- git-reset (1) Trang hướng dẫn .
- git-rebase (1) Trang hướng dẫn .
- BFG Repo Cleaner (xem thêm câu trả lời này từ chính người sáng tạo ).