Có 2 bước để đạt được điều này:
- Tạo một cam kết trống mới
- Viết lại lịch sử để bắt đầu từ cam kết trống này
Chúng tôi sẽ đặt cam kết trống mới trên một nhánh tạm thời newroot
để thuận tiện.
1. Tạo một cam kết trống mới
Có một số cách bạn có thể làm điều này.
Chỉ sử dụng hệ thống ống nước
Cách tiếp cận sạch nhất là sử dụng hệ thống ống nước của Git để chỉ cần tạo một cam kết trực tiếp, giúp tránh chạm vào bản sao làm việc hoặc chỉ mục hoặc chi nhánh nào được kiểm tra, v.v.
Tạo một đối tượng cây cho một thư mục trống:
tree=`git hash-object -wt tree --stdin < /dev/null`
Bao bọc một cam kết xung quanh nó:
commit=`git commit-tree -m 'root commit' $tree`
Tạo một tham chiếu đến nó:
git branch newroot $commit
Tất nhiên bạn có thể sắp xếp lại toàn bộ quy trình thành một lớp lót nếu bạn biết rõ vỏ của mình.
Không có hệ thống ống nước
Với các lệnh sứ thông thường, bạn không thể tạo một cam kết trống mà không kiểm tra newroot
chi nhánh và cập nhật chỉ mục và bản sao làm việc nhiều lần, không có lý do chính đáng. Nhưng một số có thể thấy điều này dễ hiểu hơn:
git checkout --orphan newroot
git rm -rf .
git clean -fd
git commit --allow-empty -m 'root commit'
Lưu ý rằng trên các phiên bản Git rất cũ không có công --orphan
tắc checkout
, bạn phải thay thế dòng đầu tiên bằng dòng này:
git symbolic-ref HEAD refs/heads/newroot
2. Viết lại lịch sử để bắt đầu từ cam kết trống này
Bạn có hai lựa chọn ở đây: đánh lại hoặc viết lại lịch sử sạch.
Nổi loạn
git rebase --onto newroot --root master
Điều này có đức tính đơn giản. Tuy nhiên, nó cũng sẽ cập nhật tên và ngày của người giao dịch trên mỗi lần xác nhận cuối cùng trên chi nhánh.
Ngoài ra, với một số lịch sử trường hợp cạnh, nó thậm chí có thể thất bại do xung đột hợp nhất - mặc dù thực tế là bạn đang phản đối một cam kết không chứa gì.
Lịch sử viết lại
Cách tiếp cận sạch hơn là viết lại chi nhánh. Không giống như git rebase
, bạn sẽ cần tìm kiếm cam kết chi nhánh của bạn bắt đầu từ:
git replace <currentroot> --graft newroot
git filter-branch master
Việc viết lại xảy ra trong bước thứ hai, rõ ràng; đó là bước đầu tiên cần giải thích. Có gì git replace
không là nó nói với Git rằng bất cứ khi nào nó thấy một tham chiếu đến một đối tượng mà bạn muốn thay thế, thay vì Git nên xem xét việc thay đổi của đối tượng đó.
Với công --graft
tắc, bạn đang nói với nó một cái gì đó hơi khác so với bình thường. Bạn đang nói chưa có đối tượng thay thế, nhưng bạn muốn thay thế <currentroot>
đối tượng cam kết bằng một bản sao chính xác của chính nó ngoại trừ (các) cam kết cha mẹ thay thế phải là một đối tượng mà bạn đã liệt kê (tức là newroot
cam kết ). Sau đó git replace
tiếp tục và tạo cam kết này cho bạn, và sau đó tuyên bố cam kết đó là thay thế cho cam kết ban đầu của bạn.
Bây giờ nếu bạn làm một git log
, bạn sẽ thấy mọi thứ đã trông như bạn muốn: chi nhánh bắt đầu từ đó newroot
.
Tuy nhiên, lưu ý rằng git replace
không thực sự sửa đổi lịch sử - nó cũng không lan truyền ra khỏi kho lưu trữ của bạn. Nó chỉ thêm một chuyển hướng cục bộ vào kho lưu trữ của bạn từ đối tượng này sang đối tượng khác. Điều này có nghĩa là không ai khác thấy tác dụng của sự thay thế này - chỉ có bạn.
Đó là lý do tại sao filter-branch
bước này là cần thiết. Với git replace
bạn tạo một bản sao chính xác với các xác nhận gốc được điều chỉnh cho cam kết gốc; git filter-branch
sau đó lặp lại quá trình này cho tất cả các cam kết sau đây. Đó là nơi lịch sử thực sự được viết lại để bạn có thể chia sẻ nó.