git merge: áp dụng các thay đổi cho mã đã chuyển sang một tệp khác


132

Tôi đang cố gắng một thao tác hợp nhất git ngay bây giờ. Một vấn đề mà tôi gặp phải là tôi đã thực hiện một số thay đổi đối với một số mã trong chi nhánh của mình, nhưng đồng nghiệp của tôi đã chuyển mã đó sang một tệp mới trong chi nhánh của anh ấy. Vì vậy, khi tôi làm git merge my_branch his_branch, git đã không nhận thấy rằng mã trong tệp mới giống với mã cũ và vì vậy không có thay đổi nào của tôi ở đó.

Cách dễ nhất để áp dụng lại các thay đổi của tôi cho mã trong các tệp mới. Tôi sẽ không có quá nhiều vấn đề trong việc tìm ra những cam kết nào cần được áp dụng lại (tôi chỉ có thể sử dụng git log --stat). Nhưng theo như tôi có thể nói, không có cách nào để git áp dụng lại các thay đổi vào các tệp mới. Điều dễ dàng nhất tôi đang thấy ngay bây giờ là tự mình áp dụng lại các thay đổi, đây không phải là một ý tưởng hay.

Tôi biết rằng git nhận ra các đốm màu, không phải các tệp, vì vậy chắc chắn phải có một cách để nói với nó, "áp dụng thay đổi mã chính xác này từ cam kết này, ngoại trừ không phải nó ở đâu mà bây giờ nó ở đâu trong tệp mới này".


2
Không hoàn toàn giống nhau, nhưng đây là một câu hỏi tương tự với các anwsers tốt có thể áp dụng: stackoverflow.com/questions/2701790/ chủ
Mariano Desanze 23/1/2015

4
Không có câu trả lời nào mô tả lý do tại sao git không thể thực hiện việc hợp nhất như thế này một cách tự động. Tôi nghĩ rằng nó được cho là đủ thông minh để phát hiện đổi tên và thực hiện hợp nhất tự động?
Giăng

Có lẽ điều này không đúng khi câu hỏi được đặt ra, nhưng các phiên bản hiện đại của git (tôi đang sử dụng 1.9.5) có thể hợp nhất các thay đổi trên các tệp được đổi tên và di chuyển. Thậm chí còn có một --rename-thresholdtùy chọn để điều chỉnh lượng tương tự được yêu cầu.
Todd Owen

1
@ToddOwen sẽ hoạt động nếu bạn đang thực hiện một cuộc tấn công vanilla từ một nhánh thượng nguồn, nhưng bạn vẫn sẽ gặp rắc rối nếu bạn chọn anh đào hoặc chuyển ngược lại một loạt các thay đổi có thể không bao gồm cam kết đổi tên các tệp .
GuyPaddock

Đây có phải là một vấn đề không nếu bạn thực hiện rebase trước?
hbogert

Câu trả lời:


132

Tôi đã có một vấn đề tương tự, và tôi đã giải quyết nó bằng cách khởi động lại công việc của mình để phù hợp với tổ chức tệp mục tiêu.

Giả sử bạn đã sửa đổi original.txttrên nhánh của mình ( localnhánh), nhưng trên nhánh chính, original.txtđã được sao chép sang một nhánh khác, nói copy.txt. Bản sao này đã được thực hiện trong một cam kết mà chúng tôi đặt tên là cam kết CP.

Bạn muốn áp dụng tất cả các thay đổi cục bộ, cam kết ABbên dưới, được thực hiện trên original.txttệp mới copy.txt.

 ---- X -----CP------ (master)
       \ 
        \--A---B--- (local)

Tạo một nhánh ném movetại điểm bắt đầu của những thay đổi của bạn với git branch move X. Có nghĩa là, đặt movechi nhánh tại cam kết X, một chi nhánh trước các cam kết bạn muốn hợp nhất; rất có thể, đây là cam kết mà bạn đã phân nhánh để thực hiện các thay đổi của mình. Như người dùng @digory doo đã viết dưới đây, bạn có thể làm git merge-base master localđể tìm X.

 ---- X (move)-----CP----- (master)
       \ 
        \--A---B--- (local)

Trên nhánh này, ban hành lệnh đổi tên sau:

git mv original.txt copy.txt

Điều này đổi tên tập tin. Lưu ý rằng copy.txtchưa tồn tại trong cây của bạn tại thời điểm này.
Cam kết thay đổi của bạn (chúng tôi đặt tên cho cam kết này MV).

        /--MV (move)
       /
 ---- X -----CP----- (master)
       \ 
        \--A---B--- (local)

Bây giờ bạn có thể khởi động lại công việc của bạn trên đầu trang move:

git rebase move local

Điều này sẽ hoạt động mà không có vấn đề, và những thay đổi của bạn được áp dụng cho copy.txtchi nhánh địa phương của bạn.

        /--MV (move)---A'---B'--- (local)
       /
 ---- X -----CP----- (master)

Bây giờ, bạn không nhất thiết muốn hoặc cần phải có cam kết MVtrong lịch sử của chi nhánh chính của mình, bởi vì hoạt động di chuyển có thể dẫn đến xung đột với hoạt động sao chép tại cam kết CPtrong chi nhánh chính.

Bạn chỉ phải khởi động lại công việc của mình một lần nữa, loại bỏ thao tác di chuyển, như sau:

git rebase move local --onto CP

... nơi CPcam kết copy.txtđược giới thiệu ở chi nhánh khác. Điều này phản hồi tất cả các thay đổi copy.txttrên đầu trang của CPcam kết. Bây giờ, localchi nhánh của bạn chính xác như thể bạn luôn sửa đổi copy.txtvà không original.txt, và bạn có thể tiếp tục hợp nhất với những người khác.

                /--A''---B''-- (local)
               /
 -----X-------CP----- (master)

Điều quan trọng là những thay đổi được áp dụng trên CPhoặc nếu không copy.txtsẽ không tồn tại và những thay đổi sẽ được áp dụng trở lại original.txt.

Hy vọng điều này là rõ ràng. Câu trả lời này đến muộn, nhưng điều này có thể hữu ích cho người khác.


2
Đó là rất nhiều công việc, nhưng tôi nghĩ rằng nó nên hoạt động trên nguyên tắc. Tuy nhiên, tôi nghĩ rằng bạn sẽ có may mắn hơn với việc hợp nhất hơn là rebase.
asmeker

7
Giải pháp này chỉ liên quan đến các lệnh git cơ bản, không giống như chỉnh sửa các bản vá hoặc sử dụng patch(liên quan đến tiềm năng rất nhiều công việc), và đó là lý do tại sao tôi nghĩ rằng nó có thể thú vị để hiển thị nó. Ngoài ra, lưu ý rằng tôi mất nhiều thời gian hơn để viết câu trả lời hơn là thực sự áp dụng các thay đổi, trong trường hợp của tôi. Ở bước nào bạn sẽ giới thiệu và sử dụng hợp nhất? và tại sao? Sự khác biệt duy nhất tôi thấy là bằng cách nổi loạn, tôi thực hiện một cam kết tạm thời mà sau đó bị loại bỏ (cam kết MV), điều này là không thể chỉ với việc hợp nhất.
coredump

1
Với rebase, bạn có cơ hội cao hơn để xử lý các xung đột hợp nhất, bởi vì bạn xử lý từng cam kết, trong khi với việc hợp nhất bạn xử lý mọi thứ cùng một lúc, có nghĩa là một số thay đổi có thể xảy ra với rebase sẽ không tồn tại với hợp nhất. Những gì tôi sẽ làm là hợp nhất, sau đó di chuyển thủ công tệp đã hợp nhất. Có lẽ tôi đã hiểu nhầm ý tưởng của câu trả lời của bạn.
asmeker

4
Tôi đã thêm một số cây ASCII để làm rõ cách tiếp cận. Liên kết hợp nhất với rebase trong trường hợp đó: những gì tôi muốn làm là thực hiện tất cả các thay đổi trên 'original.txt' trong nhánh của tôi và áp dụng chúng cho 'copy.txt' trong nhánh chính, vì một số lý do, 'nguyên bản. txt 'đã được sao chép (và không được di chuyển) vào' copy.txt 'tại một số điểm. Sau bản sao đó, 'original.txt' cũng có thể đã phát triển trên nhánh chính. Nếu tôi sáp nhập trực tiếp, các thay đổi cục bộ của tôi trên tệp gốc.txt sẽ được áp dụng cho tệp gốc.txt đã sửa đổi trong nhánh chính, điều này sẽ khó hợp nhất. Trân trọng.
coredump

1
Dù bằng cách nào, tôi tin rằng giải pháp này sẽ hoạt động (mặc dù tôi rất may không có tình huống để thử ngay bây giờ), vì vậy bây giờ tôi sẽ đánh dấu nó là câu trả lời.
asmeker

31

Bạn luôn có thể sử dụng git diff(hoặc git format-patch) để tạo bản vá, sau đó tự chỉnh sửa tên tệp trong bản vá và áp dụng với git apply(hoặcgit am ).

Nói tóm lại, cách duy nhất nó sẽ hoạt động tự động là nếu phát hiện đổi tên của git có thể nhận ra rằng các tệp cũ và mới là giống nhau - có vẻ như chúng không thực sự trong trường hợp của bạn, chỉ là một đoạn của chúng. Đúng là git sử dụng các đốm màu, không phải các tệp, nhưng một blob chỉ là nội dung của toàn bộ tệp, không có tên tệp và siêu dữ liệu được đính kèm. Vì vậy, nếu bạn có một đoạn mã được di chuyển giữa hai tệp, chúng không thực sự giống nhau - các nội dung còn lại của blob là khác nhau, chỉ là đoạn chung.


1
Vâng, đó là câu trả lời tốt nhất cho đến nay. Tôi không thể tìm ra cách để thực hiện git format-patchcông việc cho một cam kết. Nếu tôi làm git format-patch SHA1, nó sẽ tạo ra một loạt các tệp vá cho toàn bộ lịch sử. Nhưng tôi đoán git show SHA1 > diff.patchsẽ làm việc tốt như vậy.
asmeker

1
@asmeker: sử dụng -1tùy chọn. Chế độ hoạt động bình thường cho bản vá định dạng là một phạm vi sửa đổi, như origin/master..mastervậy, vì vậy bạn có thể dễ dàng chuẩn bị một loạt bản vá.
Cascabel

1
Trên thực tế, một lưu ý khác. git applygit amquá cầu kỳ, bởi vì họ muốn các số dòng giống nhau. Nhưng tôi hiện đang thành công với patchlệnh UNIX .
asmeker

23

Dưới đây là một giải pháp hợp nhất khi gặp xung đột hợp nhất với đổi tên và chỉnh sửa và giải quyết nó bằng mergetool nhận ra 3 tệp nguồn hợp nhất chính xác.

  • Sau khi hợp nhất không thành công vì 'tệp bị xóa' mà bạn nhận ra đã được đổi tên và chỉnh sửa:

    1. Bạn hủy bỏ hợp nhất.
    2. Cam kết đổi tên các tập tin trên chi nhánh của bạn.
    3. Và hợp nhất một lần nữa.

Đi qua:

Tạo một file.txt:

$ git init
Initialized empty Git repository in /tmp/git-rename-and-modify-test/.git/

$ echo "A file." > file.txt
$ git add file.txt
$ git commit -am "file.txt added."
[master (root-commit) 401b10d] file.txt added.
 1 file changed, 1 insertion(+)
 create mode 100644 file.txt

Tạo một nhánh nơi bạn sẽ chỉnh sửa sau:

$ git branch branch-with-edits
Branch branch-with-edits set up to track local branch master.

Tạo đổi tên và chỉnh sửa trên master:

$ git mv file.txt renamed-and-edited.txt
$ echo "edits on master" >> renamed-and-edited.txt 
$ git commit -am "file.txt + edits -> renamed-and-edited.txt."
[master def790f] file.txt + edits -> renamed-and-edited.txt.
 2 files changed, 2 insertions(+), 1 deletion(-)
 delete mode 100644 file.txt
 create mode 100644 renamed-and-edited.txt

Trao đổi với chi nhánh và chỉnh sửa ở đó:

$ git checkout branch-with-edits 
Switched to branch 'branch-with-edits'
Your branch is behind 'master' by 1 commit, and can be fast-forwarded.
  (use "git pull" to update your local branch)
$ 
$ echo "edits on branch" >> file.txt 
$ git commit -am "file.txt edited on branch."
[branch-with-edits 2c4760e] file.txt edited on branch.
 1 file changed, 1 insertion(+)

Cố gắng hợp nhất chủ:

$ git merge master
CONFLICT (modify/delete): file.txt deleted in master and modified in HEAD. Version HEAD of file.txt left in tree.
Automatic merge failed; fix conflicts and then commit the result.

Lưu ý rằng xung đột khó giải quyết - và các tệp đó đã được đổi tên. Hủy bỏ, bắt chước đổi tên:

$ git merge --abort
$ git mv file.txt renamed-and-edited.txt
$ git commit -am "Preparing for merge; Human noticed renames files were edited."
[branch-with-edits ca506da] Preparing for merge; Human noticed renames files were edited.
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename file.txt => renamed-and-edited.txt (100%)

Hãy thử hợp nhất lại:

$ git merge master
Auto-merging renamed-and-edited.txt
CONFLICT (add/add): Merge conflict in renamed-and-edited.txt
Recorded preimage for 'renamed-and-edited.txt'
Automatic merge failed; fix conflicts and then commit the result.

Tuyệt quá! Hợp nhất kết quả trong một cuộc xung đột 'bình thường' có thể được giải quyết bằng mergetool:

$ git mergetool
Merging:
renamed-and-edited.txt

Normal merge conflict for 'renamed-and-edited.txt':
  {local}: created file
  {remote}: created file
$ git commit 
Recorded resolution for 'renamed-and-edited.txt'.
[branch-with-edits 2264483] Merge branch 'master' into branch-with-edits

Hấp dẫn. Tôi sẽ phải thử điều này vào lần tới khi tôi gặp vấn đề này.
asmeker

1
Trong giải pháp đó, đối với tôi, bước đầu tiên, việc hợp nhất bị hủy bỏ, chỉ hữu ích để tìm ra tập tin nào được đổi tên và chỉnh sửa từ xa. Nếu bạn biết họ trước. Bạn có thể bỏ qua bước đó và về cơ bản, giải pháp chỉ đơn giản là đổi tên các tệp theo cách thủ công cục bộ, sau đó hợp nhất và giải quyết các xung đột như bình thường.

1
Cảm ơn bạn. Tình huống của tôi là tôi đã di chuyển B.txt -> C.txtA.txt -> B.txtvới git mv, và git không thể tự động khớp chính xác các xung đột hợp nhất (đang nhận được xung đột hợp nhất giữa cũ B.txtvà mới B.txt). Bằng cách sử dụng phương pháp này, các xung đột hợp nhất hiện nằm giữa các tệp chính xác.
cib

Điều này chỉ hoạt động khi toàn bộ tệp được di chuyển, nhưng git thường sẽ tự động phát hiện tình huống đó. Tình huống khó khăn là khi chỉ một phần của tập tin được di chuyển.
Robin Green
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.