충돌 해결하기
충돌은 Git이 같은 위치에 두 가지 변경 사항을 발견했고 어느 것을 선택해야 할지 판단을 거부한다는 의미입니다. 해결은 의도에 대한 결정이며, 안전한 기본값은 양쪽의 의도를 모두 유지하는 것입니다. 한쪽을 삭제하여 해결하면 마커는 사라지지만 문제는 더 악화됩니다. 빌드는 여전히 통과하지만 동료의 변경 사항은 사라집니다.
1. 현재 작업 중인 작업 파악하기 - ours와 theirs는 이에 따라 달라집니다
git status # "rebase in progress" / "You have unmerged paths" / "cherry-pick in progress"
| 작업 | --ours / HEAD 측 | --theirs 측 |
|---|---|---|
merge | 현재 브랜치 | 병합되는 브랜치 |
rebase | 리베이스 대상 브랜치(업스트림) | 재생 중인 자신의 커밋 |
cherry-pick, revert, stash pop | 현재 브랜치 | 적용되는 커밋 |
리베이스 행이 함정입니다. git rebase main 중에 "ours"는 main이고 "theirs"는 사용자 자신의 작업입니다. 이는 병합과 반대입니다. 리베이스 중 git checkout --theirs file은 자신의 변경 사항을 유지하고 업스트림의 변경 사항을 폐기합니다. 이 플래그를 사용하기 전에 위 표를 다시 읽으십시오.
2. 마커뿐만 아니라 전체 충돌 보기
git diff --name-only --diff-filter=U # 병합되지 않은 파일
git status # 유형: both modified / deleted by us / both added
git config merge.conflictStyle zdiff3 # 공통 조상도 표시 (git 2.35+; 이전 버전에서는 diff3 사용)
git checkout --conflict=zdiff3 -- <file> # 기본 조상이 표시된 하나의 파일 다시 렌더링 (이미 파일에 적용된 편집은 폐기)
기본 조상이 표시되면 마커에는 세 부분이 있습니다. 내 쪽, 원본, 상대방 쪽. 대부분의 명백한 충돌은 각 측이 무엇에서 변경되었는지를 볼 수 있을 때 명확해집니다.
각 측이 변경된 이유를 이해하려면:
git log --merge -p -- <file> # 파일을 변경한 양쪽의 커밋 (병합 또는 리베이스 진행 중)
git show REBASE_HEAD # 리베이스 중: 재생 중인 커밋
git show :1:<file> :2:<file> :3:<file> # base, "ours", "theirs" 전체 파일
커밋 메시지를 읽으십시오. 측별로 한 문장으로 설명할 수 없는 해결은 추측입니다.
3. 결합하여 해결하기
- 양쪽이 서로 다른 이유로 같은 라인을 편집한 경우 - 두 작업을 모두 수행하는 버전을 작성하십시오. 예를 들어, 한쪽은
tax_rate매개변수를 추가하고 다른 쪽은discount매개변수를 추가한 경우: 해결된 함수는 둘 다 받아서 둘 다 적용합니다. - 한쪽이 이동하거나 이름을 바꾸고 다른 쪽이 편집한 경우 - 새 위치에 편집 내용을 적용하십시오. 이전 파일과 새 파일을 모두 유지하지 마십시오.
- 한쪽이 삭제하고 다른 쪽이 수정한 경우 - 이는 기계적인 결정이 아니라 실제 결정입니다. 삭제된 이유를 찾으십시오. 수정 사항이 여전히 중요하다면 이를 이월해야 하고, 그렇지 않으면 삭제가 승리합니다. 불분명하면 사용자에게 물어보십시오.
- 생성된 파일 및 잠금 파일(
package-lock.json,poetry.lock,go.sum, 스냅샷, 컴파일된 출력) - 수동 병합하지 마십시오. 한쪽을 선택한 다음 도구를 사용하여 다시 생성하십시오(npm install,poetry lock,go mod tidy, 스냅샷 명령). - 바이너리 파일 - 병합이 불가능합니다. 의도적으로 한쪽을 선택하고(위 표를 염두에 두고) 그렇게 말하십시오.
텍스트는 깔끔하게 병합될 수 있지만 여전히 잘못될 수 있습니다. 한쪽이 함수 이름을 바꾸고 다른 쪽이 이전 이름에 대한 호출을 추가한 경우, Git은 충돌을 감지하지 못합니다. 이것이 4단계에 빌드가 포함된 이유입니다.
일괄 플래그는 결정을 숨깁니다. git checkout --ours ., -X ours / -X theirs(충돌하는 각 청크를 한쪽으로 해결) 및 -s ours(다른 브랜치를 완전히 폐기)는 각각 살펴보지 않고 작업을 버립니다. 사용자가 정확히 그렇게 요청했고 무엇이 삭제되는지 알고 있는 경우에만 사용하십시오.
4. 확인한 다음 계속 진행하기
git diff --check # 남은 충돌 마커 및 공백 오류 확인
grep -rnE '^(<<<<<<<|=======|>>>>>>>)' . # 안전장치, git diff-check가 확인하지 않는 파일에서
# 가장 빠른 관련 테스트 빌드 및 실행
git add <해결된-파일들>
일치하는 명령으로 계속 진행하십시오. 편집기가 열릴 수 있는 곳에 GIT_EDITOR=true를 추가하십시오. 편집기는 비대화형 셸에서 중단되기 때문입니다:
git merge --continue # 또는: git commit --no-edit
GIT_EDITOR=true git rebase --continue
GIT_EDITOR=true git cherry-pick --continue
리베이스는 커밋을 하나씩 재생하므로 다음 커밋에서 다시 중지될 수 있습니다. 루프를 반복하십시오. 충돌이 나타나는 가장 이른 커밋에서 해결하십시오. 이후 커밋은 동일한 충돌이 재생되는 것일 수 있습니다.
해결 후 테스트가 실패하면 실패는 해결에 대한 정보입니다. 실패를 무시하고 넘어가지 말고, 테스트가 보호하는 동작이 어느 쪽인지 알 때까지 테스트를 편집하여 "수정"하지 마십시오.
5. 중단하고 물어봐야 할 때
다음과 같은 경우 추측하지 말고 중단하십시오:
- 양쪽이 비즈니스 로직을 호환되지 않는 방식으로 변경한 경우(서로 다른 두 가지 세금 규칙, 서로 다른 두 가지 재시도 정책);
- 각 측이 변경한 이유를 한 문장으로 말할 수 없는 경우;
- 해결로 인해 테스트가 실패하고 원인이 명확하지 않은 경우.
두 의도를 진술하고, 무엇을 선택할지와 그 이유를 말한 다음 사용자가 결정하도록 하십시오. --continue 이전의 모든 것은 되돌릴 수 있습니다: git merge --abort, git rebase --abort, git cherry-pick --abort는 트리를 시작 상태로 되돌립니다.
반복되는 충돌
동일한 충돌이 반복되는 긴 리베이스 또는 반복적으로 병합되고 포기된 브랜치의 경우:
git config rerere.enabled true # 각 해결을 기록하고 다음 번에 재생합니다
git rerere diff # 기록된 내용 표시
git rerere forget-path <file> # 잘못된 기록된 해결을 폐기합니다
rerere는 묻지 않고 해결을 재적용하므로 잘못된 해결이 전파됩니다. rerere가 해결한 충돌 후에도 여전히 결과를 확인하십시오.
브랜치가 오래되었고 많이 뒤쳐진 경우, 트렁크를 한 번 병합하면 일반적으로 한 번의 충돌 라운드로 끝나고; 리베이스는 커밋별로 충돌을 재생합니다. 그 선택은 사용자의 몫입니다.
안전장치
- 충돌을 없애기 위해 한쪽을 통째로 선택하여 해결하지 마십시오. 다른 쪽은 누군가의 작업입니다.
- 지루하다는 이유로 사용자가 완료하도록 요청한 리베이스를 중단하지 마십시오. 잘못된 경우에만 중단하고 그렇게 말하십시오.
- 요청하지 않으면 해결 후 푸시하지 마십시오. 리베이스는 강제 푸시가 필요합니다.
git-safe-history-rewrite를 따르십시오. - 각 충돌 파일을 보고하십시오: 양쪽이 무엇을 했는지, 무엇을 유지했는지, 확인할 수 없는 모든 것.