Làm thế nào để tiêm một cam kết giữa một số cam kết tùy ý trong quá khứ?


Câu trả lời:


177

Nó thậm chí còn dễ hơn câu trả lời của OP.

  1. git rebase -i <any earlier commit>. Điều này sẽ hiển thị một danh sách các cam kết trong trình soạn thảo văn bản được cấu hình của bạn.
  2. Tìm cam kết bạn muốn chèn sau (giả sử nó a1b2c3d). Trong trình chỉnh sửa của bạn, cho dòng đó, thay đổi pickthành edit.
  3. Bắt đầu rebase bằng cách đóng trình soạn thảo văn bản của bạn (lưu các thay đổi của bạn). Điều này để lại cho bạn một dấu nhắc lệnh với cam kết bạn đã chọn trước đó ( a1b2c3d) như thể nó vừa được cam kết .
  4. Thực hiện các thay đổi của bạn và git commit( KHÔNG sửa đổi, không giống như hầu hết editcác s). Điều này tạo ra một cam kết mới sau khi bạn chọn.
  5. git rebase --continue. Điều này phát lại các lần xác nhận liên tiếp, để lại cam kết mới của bạn được đặt đúng chỗ.

Coi chừng rằng điều này sẽ viết lại lịch sử, và phá vỡ bất cứ ai khác cố gắng kéo.


1
Điều này đã thêm vào cam kết mới sau lần cam kết, đó là sau lần cam kết mà tôi đã bắt đầu (cũng là lần cam kết cuối cùng), thay vì ngay sau lần cam kết mà tôi đã bắt đầu. Kết quả giống như khi tôi chỉ đơn giản thực hiện một cam kết mới ở cuối với những thay đổi tôi muốn chèn. Lịch sử của tôi đã trở thành A -- B -- C -- Dthay vì mong muốn A -- D -- B -- C.
XedinUn Unknown

2
@XedinUn Unknown: Sau đó, bạn đã không sử dụng Rebase đúng cách.
SLaks

3
Bây giờ Dcó thể là một cam kết bất cứ nơi nào. Giả sử chúng ta có A - B - Cvà chúng ta có một số cam kết Dthậm chí không có trong nhánh này. Chúng tôi biết SHA của nó tuy nhiên, chúng tôi có thể làm git rebase -i HEAD~3. Bây giờ giữa AB pickcác dòng, chúng tôi chèn một dòng mới pick cho biết pick SHA, đưa ra hàm băm mong muốn D. Nó không cần phải là hàm băm đầy đủ, chỉ cần rút gọn. git rebase -ichỉ chọn cherry bất cứ cam kết nào được liệt kê bởi pickcác dòng trong bộ đệm; họ không phải là người ban đầu mà nó liệt kê cho bạn.
Kaz

1
@Kaz Đây có vẻ là một câu trả lời hợp lệ khác.
BartoszKP

3
Thậm chí dễ dàng hơn, bạn có thể sử dụng breaktừ khóa trong trình chỉnh sửa trên dòng riêng của mình ở giữa hai lần xác nhận (hoặc trên dòng đầu tiên, để chèn một cam kết trước khi cam kết được chỉ định của bạn).
SimonT

30

Hóa ra khá đơn giản, câu trả lời được tìm thấy ở đây . Giả sử bạn đang ở trên một chi nhánh branch. Thực hiện các bước sau:

  • tạo một nhánh tạm thời từ cam kết sau khi bạn muốn chèn cam kết mới (trong trường hợp này là cam kết A):

    git checkout -b temp A
    
  • thực hiện các thay đổi và cam kết chúng, tạo ra một cam kết, hãy gọi nó là N:

    git commit -a -m "Message"
    

    (hoặc git addtheo sau git commit)

  • rebase các cam kết bạn muốn có sau lần xác nhận mới (trong trường hợp này là cam kết BC) vào cam kết mới:

    git rebase temp branch
    

(có thể bạn cần sử dụng -pđể duy trì sự hợp nhất, nếu có - nhờ vào một nhận xét không còn tồn tại của ciekawy )

  • xóa chi nhánh tạm thời:

    git branch -d temp
    

Sau này, lịch sử trông như sau:

A -- N -- B -- C

Tất nhiên có thể một số xung đột sẽ xuất hiện trong khi nổi loạn.

Trong trường hợp chi nhánh của bạn không phải là cục bộ, điều này sẽ giới thiệu lịch sử viết lại, do đó có thể gây ra vấn đề nghiêm trọng.


2
Tôi không thể làm theo câu trả lời được chấp nhận bởi SLaks, nhưng điều này hiệu quả với tôi. Sau khi tôi có lịch sử cam kết tôi muốn, tôi phải git push --forcethay đổi repo từ xa.
escapecharacter

1
Khi sử dụng rebase bằng tùy chọn -Xtheirs, tự động giải quyết xung đột một cách chính xác git rebase temp branch -Xtheirs. Câu trả lời hữu ích cho việc tiêm vào một kịch bản!
David C

Đối với những người như tôi, tôi muốn thêm vào đó git rebase temp branch, nhưng trước đó git branch -d temp, tất cả những gì bạn phải làm là khắc phục và xử lý các xung đột và vấn đề git rebase --continue, tức là không cần phải cam kết bất cứ điều gì, v.v.
Pugsley

19

Giải pháp thậm chí dễ dàng hơn:

  1. Tạo cam kết mới của bạn ở cuối, D. Bây giờ bạn có:

    A -- B -- C -- D
    
  2. Sau đó chạy:

    $ git rebase -i hash-of-A
    
  3. Git sẽ mở trình soạn thảo của bạn và nó sẽ trông như thế này:

    pick 8668d21 B
    pick 650f1fc C
    pick 74096b9 D
    
  4. Chỉ cần di chuyển D lên đầu như thế này, sau đó lưu và thoát

    pick 74096b9 D
    pick 8668d21 B
    pick 650f1fc C
    
  5. Bây giờ bạn sẽ có:

    A -- D -- B -- C
    

6
Ý tưởng tuyệt vời, tuy nhiên có thể khó giới thiệu D trên C, khi bạn có ý định những thay đổi này là wrt. đến A.
BartoszKP

Tôi có một tình huống mà tôi có 3 cam kết rằng tôi muốn nổi loạn cùng nhau và một cam kết ở giữa không liên quan. Điều này là siêu tốt để có thể chỉ cần di chuyển cam kết đó sớm hơn hoặc muộn hơn dòng cam kết.
mở cửa

13

Giả sử rằng lịch sử cam kết là preA -- A -- B -- C, nếu bạn muốn chèn một cam kết giữa AB, các bước như sau:

  1. git rebase -i hash-of-preA

  2. Git sẽ mở trình soạn thảo của bạn. Nội dung có thể như thế này:

    pick 8668d21 A
    pick 650f1fc B
    pick 74096b9 C
    

    Thay đổi đầu tiên pickthành edit:

    edit 8668d21 A
    pick 650f1fc B
    pick 74096b9 C
    

    Lưu và thoát.

  3. Sửa đổi mã của bạn và sau đó git add . && git commit -m "I"

  4. git rebase --continue

Bây giờ lịch sử cam kết Git của bạn là preA -- A -- I -- B -- C


Nếu bạn gặp phải một cuộc xung đột, Git sẽ dừng lại ở cam kết này. Bạn có thể sử dụng git diffđể xác định vị trí đánh dấu xung đột và giải quyết chúng. Sau khi giải quyết tất cả các xung đột, bạn cần sử dụng git add <filename>để nói với Git rằng xung đột đã được giải quyết và sau đó chạy lại git rebase --continue.

Nếu bạn muốn hoàn tác rebase, hãy sử dụng git rebase --abort.


10

Đây là một chiến lược tránh thực hiện "chỉnh sửa hack" trong cuộc nổi loạn được thấy trong các câu trả lời khác mà tôi đã đọc.

Bằng cách sử dụng, git rebase -ibạn có được một danh sách các cam kết kể từ khi cam kết đó. Chỉ cần thêm một "break" ở đầu tập tin, điều này sẽ khiến rebase bị phá vỡ tại thời điểm đó.

break
pick <B's hash> <B's commit message>
pick <C's hash> <C's commit message>

Sau khi ra mắt, git rebasebây giờ sẽ dừng lại ở điểm "phá vỡ". Bây giờ bạn có thể chỉnh sửa các tập tin của bạn và tạo cam kết của bạn bình thường. Sau đó bạn có thể tiếp tục cuộc nổi loạn với git rebase --continue. Điều này có thể gây ra xung đột bạn sẽ phải khắc phục. Nếu bạn bị lạc, đừng quên bạn luôn có thể hủy bỏ bằng cách sử dụng git rebase --abort.

Chiến lược này có thể được khái quát hóa để chèn một cam kết ở bất cứ đâu, chỉ cần đặt "ngắt" tại vị trí bạn muốn chèn một cam kết.

Sau khi viết lại lịch sử, đừng quên git push -f. Các cảnh báo thông thường về người khác lấy chi nhánh của bạn được áp dụng.


Xin lỗi, nhưng tôi gặp khó khăn trong việc hiểu làm thế nào là "tránh rebase" này. Bạn đang chạy rebaseở đây. Không có nhiều khác biệt cho dù bạn sẽ tạo ra cam kết trong quá trình rebase hay trước đó.
BartoszKP

Ái chà, ý tôi là tránh "hack hack" trong cuộc nổi loạn, tôi đoán là tôi đã nói sai điều đó.
axerologementy

Đúng. Câu trả lời của tôi cũng không sử dụng tính năng "chỉnh sửa" của rebase. Tuy nhiên, đây vẫn là một cách tiếp cận hợp lệ khác - cảm ơn! :-)
BartoszKP

6

Nhiều câu trả lời tốt ở đây rồi. Tôi chỉ muốn thêm một giải pháp "không rebase", trong 4 bước đơn giản.


Tóm lược

git checkout A
git commit -am "Message for commit D"
git cherry-pick A..C
git branch -f master HEAD

Giải trình

(Lưu ý: một ưu điểm của giải pháp này là bạn không chạm vào chi nhánh của mình cho đến bước cuối cùng, khi bạn chắc chắn 100% là bạn ổn với kết quả cuối cùng, do đó bạn có bước "xác nhận trước" rất tiện dụng cho phép thử nghiệm AB .)


Trạng thái ban đầu (tôi đã giả sử mastercho tên chi nhánh của bạn)

A -- B -- C <<< master <<< HEAD

1) Bắt đầu bằng cách chỉ vào đúng vị trí

git checkout A

     B -- C <<< master
    /
   A  <<< detached HEAD

(Tùy chọn ở đây, thay vì tách ra CHÍNH, chúng ta có thể tạo một nhánh tạm thời git checkout -b temp A, mà chúng ta sẽ cần phải xóa ở cuối quá trình. Cả hai biến thể đều hoạt động như bạn muốn vì mọi thứ khác vẫn giữ nguyên)


2) Tạo cam kết D mới được chèn

# at this point, make the changes you wanted to insert between A and B, then

git commit -am "Message for commit D"

     B -- C <<< master
    /
   A -- D <<< detached HEAD (or <<< temp <<< HEAD)

3) Sau đó mang theo bản sao của các cam kết bị thiếu B và C cuối cùng (sẽ là cùng một dòng nếu có nhiều cam kết hơn)

git cherry-pick A..C

# (if any, resolve any potential conflicts between D and these last commits)

     B -- C <<< master
    /
   A -- D -- B' -- C' <<< detached HEAD (or <<< temp <<< HEAD)

(thoải mái kiểm tra AB ở đây nếu cần)

Bây giờ là thời điểm để kiểm tra mã của bạn, kiểm tra bất cứ điều gì cần kiểm tra và bạn cũng có thể tìm / so sánh / kiểm tra những gì bạn cónhững gì bạn sẽ nhận được sau các hoạt động.


4) Tùy thuộc vào các thử nghiệm của bạn giữa CC', nó ổn hoặc là KO.

(EITHER) 4-OK) Cuối cùng, di chuyển ref củamaster

git branch -f master HEAD

     B -- C <<< (B and C are candidates for garbage collection)
    /
   A -- D -- B' -- C' <<< master

(HOẶC) 4-KO) Chỉ cần giữ masternguyên

Nếu bạn đã tạo một nhánh tạm thời, chỉ cần xóa nó với git branch -d <name>, nhưng nếu bạn đã đi theo tuyến đường Tách rời, không có hành động nào cần thiết vào thời điểm này, các cam kết mới sẽ đủ điều kiện để thu gom rác ngay sau khi bạn gắn lại HEADvớigit checkout master

Trong cả hai trường hợp (OK hoặc KO), tại thời điểm này, chỉ cần kiểm tra masterlại để gắn lại HEAD.

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.