git merge : 다른 파일로 이동 한 코드에 변경 사항을 적용


132

나는 지금 꽤 강력한 git merge 기동을 시도하고 있습니다. 내가 겪고있는 한 가지 문제는 지점의 일부 코드를 약간 변경했지만 동료가 해당 코드를 지점의 새 파일로 옮겼다는 것입니다. 그래서 내가 할 때 git merge my_branch his_branchgit은 새 파일의 코드가 이전 코드와 동일하다는 것을 알지 못했기 때문에 변경 사항이 없습니다.

변경 사항을 새 파일의 코드에 다시 적용하는 가장 쉬운 방법은 무엇입니까? 어떤 커밋을 다시 적용 해야하는지 찾는 데 너무 많은 문제가 없습니다 (그냥 사용할 수 있습니다 git log --stat). 그러나 내가 알 수있는 한, git이 변경 사항을 새 파일에 다시 적용 할 수있는 방법은 없습니다. 지금보고있는 가장 쉬운 방법은 변경 내용을 수동으로 다시 적용하는 것인데, 이는 좋은 생각처럼 보이지 않습니다.

나는 git이 파일이 아닌 blob을 인식한다는 것을 알고 있으므로 "이 커밋 에서이 정확한 코드 변경 사항을 적용하십시오.


2
여기 정확히 같은, 그러나 적용 할 수있는 좋은의 anwsers와 비슷한 질문입니다 stackoverflow.com/questions/2701790/...
마리아노 Desanze

4
git이 이와 같은 병합을 자동으로 수행 할 수 없는지 에 대한 대답 은 없습니다. 이름 바꾸기를 감지하고 적절한 병합을 자동으로 수행하기에 충분히 똑똑해야한다고 생각 했습니까?
John

아마도 이것은 질문을 받았을 때 사실이 아니지만 현대 버전의 git (1.9.5를 사용하고 있음)은 이름이 바뀌고 이동 된 파일의 변경 사항을 병합 할 수 있습니다. --rename-threshold필요한 유사성의 정도를 조정할 수 있는 옵션 도 있습니다 .
Todd Owen

1
@ToddOwen은 업스트림 브랜치에서 바닐라 리베이스를 수행하는 경우 작동하지만 파일의 이름을 바꾸는 커밋을 포함하지 않을 수있는 변경 사항을 체리 피킹하거나 백 포팅하는 경우 여전히 문제가 발생합니다. .
GuyPaddock

리베이스를 먼저 수행하면 이것이 전혀 문제가 되었습니까?
hbogert

답변:


132

비슷한 문제가 있었고 대상 파일 구성과 일치하도록 작업을 조정하여 문제를 해결했습니다.

수정 한 것을 말한다 original.txt지사 (온 local지점),하지만 마스터 분기에, original.txt또 다른 하나에 복사 된 말 copy.txt. 이 사본은 commit이라는 커밋에서 수행되었습니다 CP.

에 수행 된 모든 로컬 변경 사항, 커밋 AB아래 original.txt를 새 파일 에 적용하려고 합니다 copy.txt.

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

move을 사용하여 변경 시작 지점에 버리기 지점 을 만듭니다 git branch move X. 즉, 병합하려는 커밋 이전의 move분기를 commit에 두십시오 X. 아마도 이것은 변경 사항을 구현하기 위해 분기 한 커밋입니다. @digory doo 사용자 가 아래에 쓴 것처럼 git merge-base master local을 찾을 수 X있습니다.

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

이 분기에서 다음 이름 변경 명령을 실행하십시오.

git mv original.txt copy.txt

파일 이름이 바뀝니다. 참고 copy.txt아직이 시점에서 트리에 존재하지 않았다.
변경 사항을 커밋하십시오 (이 커밋 이름을 지정합니다 MV).

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

이제 다음 작업을 기반으로 작업을 리베이스 할 수 있습니다 move.

git rebase move local

문제없이 작동하며 변경 사항이 copy.txt로컬 지점에 적용됩니다 .

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

MV이동 조작 CP으로 인해 메인 브랜치 에서 커밋 할 때 복사 조작과 충돌이 발생할 수 있으므로 이제는 메인 브랜치 히스토리에서 커밋을 원하지 않거나 가질 필요가 없습니다 .

다음과 같이 이동 작업을 삭제하고 작업을 다시베이스로 변경하면됩니다.

git rebase move local --onto CP

... 다른 지점에서 도입 된 CP커밋은 어디에 있습니까 copy.txt? 이것은 커밋 copy.txt상단의 모든 변경 사항을 리베이스합니다 CP. 이제 local브랜치는 항상 수정 copy.txt하고 아닌 것처럼 정확하게 original.txt유지되며 다른 사람과 계속 병합 할 수 있습니다.

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

변경 사항이 적용 CP되거나 copy.txt존재하지 않고 변경 사항이 다시 적용되는 것이 중요합니다 original.txt.

이것이 분명하기를 바랍니다. 이 답변은 늦었지만 다른 사람에게는 유용 할 수 있습니다.


2
그것은 많은 작업이지만 원칙적으로 작동해야한다고 생각합니다. 그래도 rebase보다 merge로 더 나은 행운을 얻을 것이라고 생각합니다.
asmeurer 2019

7
이 솔루션에는 패치 편집이나 사용과는 달리 (기본적으로 많은 명령이 필요함) 기본 git 명령 만 포함 patch되므로이를 표시하는 것이 흥미로울 수 있다고 생각했습니다. 또한 필자의 경우 실제로 변경 사항을 적용하는 것보다 답변을 작성하는 데 더 많은 시간이 걸렸습니다. 병합을 사용하여 어느 단계에서 다시 명령 하시겠습니까? 그리고 왜? 내가 아는 유일한 차이점은 rebasing을 통해 나중에 커밋 (commit MV) 되는 임시 커밋을 만들고 병합으로는 가능하지 않다는 것입니다.
coredump

1
rebase를 사용하면 각 커밋을 처리하기 때문에 병합 충돌을 처리 할 가능성이 높아지는 반면, 병합을 사용하면 모든 것을 한 번에 처리 할 수 ​​있습니다. 즉, rebase에서 발생할 수있는 일부 변경 사항은 병합에 존재하지 않습니다. 내가하는 일은 병합 한 다음 병합 된 파일을 수동으로 이동하는 것입니다. 그래도 나는 당신의 대답에 대한 아이디어를 오해했을 것입니다.
asmeurer

4
접근 방식을 명확히하기 위해 ASCII 트리를 추가했습니다. 이 경우 Regaring merge vs. rebase : 내가하고 싶은 일은 내 브랜치의 'original.txt'에서 모든 변경 사항을 가져 와서 마스터 브랜치의 'copy.txt'에 적용하는 것입니다. txt '가 어느 시점에서'copy.txt '로 복사 (이동되지 않음)되었습니다. 그 후 'original.txt'도 마스터 브랜치에서 진화했을 수 있습니다. 직접 병합하면 original.txt의 로컬 변경 사항이 마스터 분기의 수정 된 original.txt에 적용되므로 병합하기가 어렵습니다. 문안 인사.
coredump

1
어느 쪽이든, 나는이 솔루션이 효과가 있다고 믿습니다 (고맙게도 지금 시도 할 상황이 없지만). 지금은 대답으로 표시 할 것입니다.
asmeurer

31

항상 git diff(또는 git format-patch)를 사용하여 패치를 생성 한 다음 패치에서 파일 이름을 수동으로 편집하고 git apply(또는 git am)로 적용 할 수 있습니다 .

이것의 부족으로, 자동으로 작동하는 유일한 방법은 git의 이름 변경 감지가 이전 파일과 새 파일이 같은 것을 알아낼 수 있는지입니다. git은 파일이 아닌 blob을 사용하지만 blob은 파일 이름과 메타 데이터가 첨부되지 않은 전체 파일의 내용 일뿐입니다. 따라서 두 파일 사이에서 코드 덩어리가 이동하면 실제로 동일한 얼룩이 아닙니다. 나머지 얼룩의 내용은 다릅니다.


1
글쎄, 지금까지 가장 좋은 대답입니다. git format-patch커밋 작업을 수행 하는 방법을 알 수 없었습니다 . 내가 git format-patch SHA1하면 전체 기록에 대한 전체 패치 파일을 생성합니다. 그러나 나는 git show SHA1 > diff.patch잘 작동 할 것이라고 생각 합니다.
asmeurer

1
@asmeurer : -1옵션을 사용하십시오 . format-patch의 일반 작동 모드는과 같은 개정 범위 origin/master..master이므로 패치 시리즈를 쉽게 준비 할 수 있습니다.
Cascabel

1
사실, 또 다른 메모입니다. git apply그리고 git am그들은 같은 줄 번호를 원하기 때문에, 너무 까다 롭고 있습니다. 그러나 나는 현재 UNIX patch명령 으로 성공하고 있습니다.
asmeurer

24

다음은 이름 바꾸기 및 편집시 병합 충돌이 발생하고 올바른 3 개의 병합 소스 파일을 인식하는 mergetool로이를 해결 하는 병합 솔루션입니다.

  • '삭제 된 파일'로 인해 병합이 실패하면 이름이 바뀌고 편집 된 것입니다.

    1. 병합을 중단합니다.
    2. 지점에서 이름이 바뀐 파일을 커밋합니다.
    3. 그리고 다시 합치십시오.

연습 :

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

나중에 편집 할 지점을 만듭니다.

$ git branch branch-with-edits
Branch branch-with-edits set up to track local branch 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

지점으로 스왑하고 편집하십시오.

$ 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(+)

마스터 병합을 시도하십시오 :

$ 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.

충돌을 해결하기 어렵고 파일 이름이 바뀌 었습니다. 중단하고 이름 바꾸기를 모방합니다.

$ 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%)

다시 병합하십시오 :

$ 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.

큰! 병합하면 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

흥미 롭군 다음에이 문제가 발생하면이 작업을 시도해야합니다.
asmeurer

1
이 솔루션에서 중단 된 병합의 첫 번째 단계는 이름을 바꾸고 원격으로 편집 한 파일을 찾는 데 유용합니다. 당신이 그들을 미리 알고 있다면. 이 단계를 건너 뛸 수 있으며 기본적으로 해결책은 파일을 로컬로 수동으로 이름을 바꾼 다음 평소와 같이 충돌을 병합하고 해결하는 것입니다.

1
감사합니다. 내 상황은 내가 이동이었다 B.txt -> C.txtA.txt -> B.txt자동 올바르게 병합 충돌을 일치하지 않을 수 자식 MV, 그리고 자식과 (구 사이에 점점 병합 충돌했습니다 B.txt새로운 B.txt). 이 방법을 사용하면 이제 병합 충돌이 올바른 파일 사이에있게됩니다.
cib

이것은 전체 파일을 이동할 때만 작동하지만 일반적으로 git은 자동으로 해당 상황을 감지해야합니다. 까다로운 상황은 파일의 일부만 이동할 때입니다.
Robin Green
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.