Làm thế nào tôi có thể chia ra một cam kết Git bị chôn vùi trong lịch sử?


292

Tôi lật lại lịch sử của mình và muốn thực hiện một số thay đổi cho nó. Vấn đề là, tôi có một cam kết với hai thay đổi không liên quan và cam kết này được bao quanh bởi một số thay đổi khác trong lịch sử địa phương (không bị đẩy) của tôi.

Tôi muốn tách ra cam kết này trước khi tôi đẩy nó ra, nhưng hầu hết các hướng dẫn tôi đang thấy phải thực hiện với việc chia nhỏ cam kết gần đây nhất của bạn hoặc các thay đổi cục bộ không được cam kết. Có khả thi để làm điều này với một cam kết bị chôn vùi trong lịch sử một chút, mà không phải "làm lại" các cam kết của tôi kể từ đó không?


Câu trả lời:


450

Có một hướng dẫn để phân chia các cam kết trong trang web rebase . Tóm tắt nhanh là:

  • Thực hiện một rebase tương tác bao gồm cả cam kết đích (ví dụ git rebase -i <commit-to-split>^ branch) và đánh dấu nó sẽ được chỉnh sửa.

  • Khi rebase đạt được cam kết đó, hãy sử dụng git reset HEAD^để thiết lập lại trước khi xác nhận, nhưng giữ nguyên cây công việc của bạn.

  • Thêm các thay đổi và cam kết chúng, thực hiện nhiều cam kết như mong muốn. add -pcó thể hữu ích để chỉ thêm một số thay đổi trong một tệp nhất định. Sử dụng commit -c ORIG_HEADnếu bạn muốn sử dụng lại thông điệp cam kết ban đầu cho một cam kết nhất định.

  • Nếu bạn muốn kiểm tra những gì bạn cam kết (ý tưởng tốt!) Sử dụng git stashđể ẩn đi phần bạn chưa cam kết (hoặc stash --keep-indextrước khi bạn cam kết), hãy kiểm tra, sau đó git stash poptrả phần còn lại cho cây công việc. Tiếp tục thực hiện các cam kết cho đến khi bạn nhận được tất cả các sửa đổi đã cam kết, tức là có một cây làm việc sạch sẽ.

  • Chạy git rebase --continueđể tiến hành áp dụng các xác nhận sau khi cam kết hiện đã chia.


17
... nhưng đừng làm điều đó nếu bạn đã đẩy lịch sử kể từ khi cam kết chia tách.
wilmustell

29
@wilmustell: Tôi đã bỏ qua cái nồi hơi "có khả năng nguy hiểm" thông thường của mình
Cascabel

2
và bạn đã thực hiện một đọc hoàn hảo. Tôi đã cố gắng tránh 'cái nồi hơi' khi tôi chỉ định nó chưa được chia sẻ lịch sử :) Về mọi mặt, tôi đã thành công với đề xuất của bạn. Đó là một nỗi đau lớn để làm công cụ này sau khi thực tế mặc dù. Tôi đã học được một bài học ở đây, và đó là để đảm bảo các cam kết được đưa vào chính xác để bắt đầu!
Ben

2
Bước đầu tiên có thể được nêu rõ hơn như git rebase -i <sha1_of_the_commit_to_split>^ branch. Và git guilà một công cụ tuyệt vời cho tác vụ chia tách, có thể được sử dụng để thêm các phần khác nhau của tệp vào các cam kết khác nhau.
Qiang Xu

3
@QiangXu: Đầu tiên là một gợi ý hợp lý. Thứ hai là chính xác lý do tại sao tôi đề xuất git add -p, có thể làm nhiều hơn git guicó thể trong bộ phận này (đáng chú ý là chỉnh sửa hunk, dàn dựng mọi thứ bắt đầu từ hunk hiện tại và tìm kiếm hunk bằng regex).
Cascabel

3

Đây là cách thực hiện với Magit .

Nói cam kết ed417ae là một trong những bạn muốn thay đổi; nó chứa hai thay đổi không liên quan và được chôn vùi dưới một hoặc nhiều cam kết. Nhấn llđể hiển thị nhật ký và điều hướng đến ed417ae:

nhật ký ban đầu

Sau đó nhấn rđể mở cửa sổ bật lên rebase

cửa sổ bật lên rebase

mđể sửa đổi các cam kết tại điểm.

Lưu ý cách @hiện tại trên cam kết mà bạn muốn tách - điều đó có nghĩa là bây giờ CHÍNH là tại cam kết đó:

sửa đổi một cam kết

Chúng tôi muốn di chuyển CHÍNH đến cha mẹ, vì vậy hãy điều hướng đến cha mẹ (47e18b3) và nhấn x( magit-reset-quickly, bị ràng buộc onếu bạn đang sử dụng evil-magit) và nhập để nói "vâng, ý tôi là cam kết tại điểm". Nhật ký của bạn sẽ trông như sau:

đăng nhập sau khi đặt lại

Bây giờ, nhấn qđể đi đến trạng thái Magit thông thường, sau đó sử dụng ulệnh unstage thông thường để bỏ qua những gì không diễn ra trong lần xác nhận đầu tiên, cam kết cphần còn lại như bình thường, sau đó nói svà thực hiện cnhững gì diễn ra trong lần xác nhận thứ hai và khi hoàn thành: nhấn rđể mở cửa sổ bật lên rebase

cửa sổ bật lên rebase

và một cái khác rđể tiếp tục, và bạn đã hoàn tất! llbây giờ cho thấy:

tất cả đã hoàn thành


1

Để phân tách một cam kết <commit>và thêm cam kết mới trước cam kết này và lưu ngày tác giả của <commit>- các bước sau:

  1. Chỉnh sửa cam kết trước <commit>

    git rebase -i <commit>^^
    

    NB: có lẽ nó cũng sẽ cần thiết để chỉnh sửa <commit>.

  2. Cherry chọn <commit>vào chỉ số

    git cherry-pick -n <commit>
    
  3. Tương tác đặt lại các thay đổi không cần thiết từ chỉ mục và đặt lại cây làm việc

    git reset -p && git checkout-index -f -a
    

    Thay vào đó, chỉ cần bỏ các thay đổi không cần thiết tương tác: git stash push -p -m "tmp other changes"

  4. Thực hiện các thay đổi khác (nếu có) và tạo cam kết mới

    git commit -m "upd something" .
    

    Tùy chọn, lặp lại các mục 2-4 để thêm các cam kết trung gian.

  5. Tiếp tục nổi loạn

    git rebase --continue
    

0

Có phiên bản nhanh hơn nếu bạn chỉ muốn trích xuất nội dung từ chỉ một tệp. Nó nhanh hơn vì rebase tương tác không thực sự tương tác nữa (và tất nhiên thậm chí còn nhanh hơn nếu bạn muốn trích xuất từ ​​lần cam kết cuối cùng, sau đó không cần phải phản ứng lại)

  1. Sử dụng trình chỉnh sửa của bạn và xóa các dòng bạn muốn trích xuất từ the_file. Đóng lại the_file. Đó là phiên bản duy nhất bạn cần, tất cả phần còn lại chỉ là các lệnh git.
  2. Giai đoạn xóa trong chỉ mục:

    git  add  the_file
    
  3. Khôi phục các dòng bạn vừa xóa trở lại vào tập tin mà không ảnh hưởng đến chỉ mục !

    git show HEAD:./the_file > the_file
    
  4. "SHA1" là cam kết bạn muốn trích xuất các dòng từ:

    git commit -m 'fixup! SHA1' 
    
  5. Tạo cam kết thứ hai, hoàn toàn mới với nội dung cần trích xuất được khôi phục ở bước 3:

    git commit -m 'second and new commit' the_file 
    
  6. Không chỉnh sửa, không dừng / tiếp tục - chỉ chấp nhận mọi thứ:

    git rebase --autosquash -i SHA1~1
    

Tất nhiên thậm chí còn nhanh hơn khi cam kết trích xuất là cam kết cuối cùng:

4. git commit -C HEAD --amend
5. git commit -m 'second and new commit' thefile
6. no rebase, nothing

Nếu bạn sử dụng magitthì bước 4, 5 và 6 là một hành động duy nhất: Cam kết, Khắc phục tức thì


-2

Nếu bạn chưa đẩy, chỉ cần sử dụng git rebase. Thậm chí tốt hơn, sử dụng git rebase -iđể di chuyển cam kết xung quanh tương tác. Bạn có thể di chuyển các cam kết vi phạm ra phía trước, sau đó chia nó ra tùy thích và di chuyển các bản vá lại (nếu cần).


14
Không cần phải di chuyển nó đi đâu cả. Chia nó ở nơi nó được.
Cascabel

1
Thật không may, điều này không phù hợp với tôi vì một số lịch sử sau khi cam kết phụ thuộc vào nó, vì vậy tôi hơi bị hạn chế. Tuy nhiên, đây sẽ là lựa chọn đầu tiên của tôi.
Ben

@Ben: không sao cả - các cam kết sau đó sẽ không cần phải thay đổi (giả sử bạn giữ tất cả các thay đổi, thay vì vứt bỏ một số trong số chúng). Thêm thông tin tại đây - stackoverflow.com/questions/1440050/ Kẻ
Ether
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.