변경 사항 다루기

충돌 해결

머지, 리베이스, 체리픽, 리버트가 멈추면 배너가 무엇이 멈췄고 무엇 사이에서 멈췄는지 알려 줘요. 그냥 "충돌"이 아니라 "feature/x를 main에 머지하는 중"처럼요.

충돌 해결기

왜 충돌하는지

헤더의 왜 충돌하는지는 브랜치가 갈라진 뒤 이 파일을 건드린 커밋을 쪽별로 나열해요. git log --merge가 하는 일인데, git이 아주 오래전부터 제공해 왔지만 아무도 발견하지 못하는 기능이에요.

충돌한 파일을 건드린 양쪽의 커밋들

마커는 무엇이 부딪히는지 말해 줘요. 이것은 누가 왜 바꿨는지를 말해 주는데, 해결 방향을 실제로 결정하는 건 대개 그쪽이에요. 거기에 아무것도 없다면 양쪽 모두 이 정확한 경로에 대한 변경을 커밋하지 않았다는 뜻이고, 충돌은 이름 변경이나 이동에서 온 거예요.

세 개의 패널

패널 무엇인지
왼쪽 Ours — 내가 있던 쪽. 해당 커밋이 함께 표시돼요
오른쪽 Theirs — 들어오는 쪽. 해당 커밋이 함께 표시돼요
가운데 결과물 — 편집할 수 있고, 줄 번호가 있고, 실제로 스테이징되는 내용이에요

세 패널 모두 크기를 조절할 수 있고, 결과물 패널의 헤더에는 보기 토글이 두 개 있어요.

토글 하는 일
줄 바꿈 A와 B 패널 안에서 긴 줄을 가로 스크롤 대신 줄 바꿈해요. 결과물 패널은 한 줄이 한 행을 유지해요 — 옆의 마커가 그걸 전제로 하거든요 — 그래서 항상 스크롤돼요
연동 A, B, 결과물을 세로와 가로로 함께 스크롤해요. 서로 줄 수가 다르기 때문에 세로 위치는 비율로 맞춰요

줄 바꿈은 꺼진 채로, 연동은 켜진 채로 시작하고, 둘 다 상태를 기억해요.

돌아다니기

파일을 열면 파일 맨 위가 아니라 첫 번째 충돌에 내려앉아요. 결과물 헤더의 ⌃ / ⌄ 화살표 — 또는 Alt+↑ / Alt+↓ — 로 나머지를 하나씩 넘어가고, 그때마다 세 패널이 모두 그 위치로 스크롤돼요.

고르기

줄 단위, 청크 단위, 또는 한쪽 전체를 한 번에 고를 수 있고, 답이 "둘 다 남기기"일 때는 한 청크에서 양쪽을 모두 가져올 수도 있어요. 충돌 하나하나를 따라가는 내비게이터가 남은 것을 훑어 주니, 실수로 마커를 남겨 둘 일이 없어요.

AI 도움

AI를 켜 두면 AI로 해결이 결과물 패널에 머지 결과를 제안해요. 스스로 무언가를 적용하는 일은 결코 없어요. 여러분이 읽고, 고치고, 스테이징하는 거예요. AI 기능을 참고하세요.

Xcode 프로젝트 파일

project.pbxproj는 iOS 저장소의 어떤 파일보다도 자주 충돌합니다. 그리고 그 이유가 누군가의 의견 차이인 경우는 거의 없습니다. 24자리 16진수 id를 키로 하는 하나의 평평한 객체 사전이라, 파일 하나를 추가하면 네 개의 항목이 쓰입니다 — PBXBuildFile, PBXFileReference, 소속 그룹의 children 한 줄, 타깃 빌드 단계의 한 줄. 두 사람이 각각 파일 하나씩 추가하면 같은 몇 줄에 떨어지는 여덟 개의 항목이 쓰입니다. git은 충돌로 보지만, 해결할 것은 없습니다.

충돌한 파일이 project.pbxproj이면, 해결기는 세 버전을 텍스트가 아니라 프로젝트로 읽고 구조로 병합할 것을 제안합니다. id로 객체를 짝짓고, 양쪽의 모든 추가를 가져오고, children과 files 배열을 합치고, 진짜로 갈라진 곳에서 멈춥니다. 창 위의 띠가 각 쪽이 무엇을 추가했는지, 그리고 — 있다면 — 무엇이 당신에게 남았는지 알려줍니다.

AI 제안과 마찬가지로 출력 창에 놓일 뿐 아무것도 스테이지하지 않습니다. 저장하기 전에 직접 읽습니다.

Xcode 프로젝트 파일에서 충돌 창 위에 나타나는 구조 병합 띠

하지 않는 일

둘 다 건드린 설정을 절대 추측하지 않습니다. 당신이 MARKETING_VERSION을 1.1로, 상대가 2.0으로 두었다면 그것은 판단입니다. 뒤에서 해결되는 대신 설정 이름과 당신의 값, 상대의 값으로 띠에 나옵니다. 매듭짓지 못한 객체는 당신의 버전을 그대로 유지하므로, 절반만 적용된 병합이 디스크에 닿는 일은 없습니다.

세 버전 중 하나라도 파싱되지 않으면 파일 전체를 거부합니다. Xcode가 열 수 없는 project.pbxproj는 수동 병합보다 비싸므로, 확실히 읽을 수 없는 것은 평범한 텍스트 충돌로 남고, 그렇게 말합니다.

서로 다른 객체에 같은 id가 발급된 경우는 감지하지 못합니다. Xcode가 id를 무작위로 고르기 때문에 드물지만, 일어나면 어느 쪽을 택하든 누군가의 파일을 조용히 잃게 되므로 병합 대신 보고합니다.

merge=union은 답이 아닙니다

이에 대해 도는 처방은 .gitattributes의 *.pbxproj merge=union입니다. 쓰지 마세요. union은 변경이 독립적인 추가뿐일 때만 동작하고, 두 사람이 같은 빌드 설정을 편집하는 순간 두 줄을 모두 내보내 Xcode가 열기를 거부하는 파일을 만듭니다 — 하필 당신이 diff를 가장 꼼꼼히 읽지 않을 그 순간에요. 구조 병합은 그 고장 없이 같은 편의를 줍니다.

잠금 파일

Podfile.lock, Package.resolved, yarn.lock과 그 사촌들은 누군가의 리졸버가 이미 푼 의존성 그래프를 기록합니다. 한 해답의 절반을 다른 해답의 절반에 꿰매 붙이면 아무도 풀지 않은 그래프가 됩니다. 설치가 안 될 수도 있고, 된다 해도 어느 브랜치도 테스트하지 않은 것이 설치됩니다.

그래서 충돌한 파일이 잠금 파일이면, 띠가 그 파일을 관장하는 도구의 이름을 말하고, 우리 것 유지와 상대 것 유지를 그 자리에서 제공하며, 그다음 다시 생성하는 명령을 알려줍니다. 여기서 한쪽을 고르는 것은 타협이 아닙니다 — 그게 방법의 전부이고, 옳게 만들어 주는 것은 재생성입니다.

충돌 창 위의 잠금 파일 띠

세 창은 그대로 남아 있습니다. 알아보는 체크섬, 예상했던 버전처럼 무엇이 바뀌었는지 읽고 싶을 때가 가끔은 있으니까요. 이것이 말리려는 것은 손으로 고치는 쪽입니다.

애초에 피하기

충돌 레이더는 어떤 브랜치가 충돌할지 머지하기 전에 알려 줘요.

git이 기억하게 하기 (rerere)

오래 살아 있는 브랜치를 리베이스하면 같은 충돌을 매번 다시 만나요. rerere — reuse recorded resolution — 가 git의 답이에요. 충돌을 어떻게 해결했는지 외워 두었다가 똑같은 충돌이 다음에 나타나면 그 답을 그대로 재생해 줘요.

설정 → 일반 → 충돌 해결 내용 기억하기. 전역 git 설정에 rerere.enabled를 기록하므로 명령줄도 똑같이 동작해요.

git이 대신 답을 낸 경우, 해결기는 "충돌 마커 없음"이라는 빈 화면을 보여 주는 대신 그 사실을 말해 주고 이 해결 내용 잊기를 제안해요. 이건 기억을 지우고 동시에 충돌을 되돌려 놓아서, 다르게 해결할 수 있게 해 줘요.

알아 둘 만한 것이 두 가지 있어요.

  • 재생된 해결 내용은 스테이징되지 않아요. 재생된 해결 내용을 자동으로 스테이징을 켜지 않는 한요. 그건 꺼 두세요. 멈춤의 요점은 외워 둔 답이 이번 머지에는 틀릴 수도 있다는 것이고, 보지 않고 스테이징하는 것이 그 답이 커밋까지 도달하는 경로거든요.

    그래서 재생된 파일은 충돌한 파일 목록에 그대로 남아 있어요. git이 내용은 썼지만 인덱스는 여전히 그것을 병합되지 않은 상태로 들고 있고, 스테이징만이 그걸 마무리해요. 해결기의 있는 그대로 스테이징이나 목록의 모두 해결됨으로 표시가 그것을 옮겨 주는 동작이에요.

  • rerere가 모든 충돌을 이해하지는 못해요. add/add와 delete/modify 충돌에는 프리이미지가 없어서 언제나 날것으로 다시 돌아와요. 설정에 표시되는 개수가 실제로 갖고 있는 개수이고, 전부 잊기가 그걸 비워요.

함께 보기: 충돌 레이더 · 머지와 리베이스

GitHub에서 이 번역 개선하기