Ubisam-MergeTool/doc/history/2026-09-16-8차.md
ksj09406 18f9ca3a9f 8차 E-1 기록: 다섯 길을 사람 순서 그대로 다시 끝까지
제품 코드 변경 없음. 병합 · pull · rebase · cherry-pick · revert 를 길마다 새 저장소에서
파일 비교 탭을 미리 열지 않고 MergeHub 만으로 끝까지 갔다.

- 다섯 길 모두 커밋(rebase 끝)까지 · 줄끝 · BOM 바뀐 파일 0 · 충돌 표식 0 · class 이름 검사 0
- 7차 A-1 · A-2 · B-1, 8차 A-1 · B-1 · B-2 다시 안 남 · F-1 병합 · pull 본문 26개
- 새로 걸린 것(9차): cherry-pick 마지막 저장 뒤 요약 "내 쪽 대비" 가 실제 기준(이력에서 고른 커밋)과 다름,
  한 번 다른 브랜치 커밋이 기준으로 잡힘(따로 재니 재현 안 됨)
- 8차 전부 끝

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 10:32:55 +09:00

39 KiB
Raw Blame History

8차 — 고른 파일에 고른 내용을 · 작업마다 화면이 사실대로 (2026-09-16 ~)

지시서: ../작업지시/8차.md. 7차 D-1 에서 새로 걸린 넷과 7차에서 남긴 것 · 웹 문서.

A-1. 고른 충돌 파일 대신 다른 파일이 보이고, 그 화면을 저장하면 고른 파일에 써진다 (2026-09-16, 완료)

고치기 전에 다시 났다 (새로 만든 저장소, MergeHub 로 병합)

파일 비교 탭을 한 번도 열지 않은 채 Git 탭 충돌 목록에서 Common/Util/Unit47.cs 를 고르고 [3분할로 열기].

확인 결과
파일 비교 탭 화면 (merge base) Common/Util/Unit15.cs — 고른 파일이 아니라 첫 충돌 파일
[다음 충돌] → Take Theirs → [Save] 눌린다
git M Common/Util/Unit47.cs · UU Common/Util/Unit15.cs
스테이징된 Unit47.cs 내용 123줄 · public sealed class Unit15 — Unit15 의 내용(고른 줄 · 머지 주석까지)이 Unit47 에 써졌다
로그 충돌 해결(직접 편집, UTF-8 (BOM) · CRLF): Common/Util/Unit47.cs
저장 뒤 화면 30초 동안 Unit15 · "남은 충돌 1개" 그대로

7차 D-1 에서 병합 · pull 둘 다 이렇게 났다(그때는 파일 비교 탭을 먼저 열어 두고 네 길을 확인했다).

원인

파일 비교 화면은 탭을 처음 열 때 만들어진다. 그때 두 가지가 겹친다.

  1. 목록 세우기(RebuildListAfterRefreshAsync → LoadCommitAsync)가 첫 파일(Unit15)을 읽는다.
  2. 넘겨받은 요청(DeliverPendingOpenConflict → OpenConflictAsync)이 고른 파일(Unit47)을 고른다.

LoadFileAsync 는 읽기가 끝난 뒤 늦게 끝난 결과를 IsGitMode 로만 버렸다. 읽기 시작한 경로가 지금 고른 파일과 같은지는 보지 않았다 (같은 자리 주석이 "경로는 새 파일인데 내용은 옛 파일" 을 이미 겪었다고 적고 있었다 - 데모 전환에서). 그래서 Unit15 의 내용이 화면을 덮었고, [Save] 는 화면 내용을 지금 고른 파일(Unit47)에 썼다.

고친 것

파일 무엇
Services/MergeSavePlan.cs IsStaleLoad(읽은 경로 · 읽은 커밋, 지금 경로 · 지금 커밋) — 읽는 사이에 파일 · 커밋이 바뀌었으면 버린다. ShownFileMismatch(화면에 실린 파일, 저장할 파일) — 다르거나 실린 것이 없으면 까닭을 돌려준다
ViewModels/HomeViewModel.cs _shownFile — 화면에 실제로 실린 파일. LoadFileAsync 는 행을 만들기 전과 화면에 옮기기 직전 두 곳에서 IsStaleLoad 로 버리고, 실을 때 _shownFile 을 적는다. 버린 뒤 화면이 지금 고른 파일과 다르면 ReloadWhenShownIsStale 로 한 번 더 읽는다(되풀이 방지 플래그). ClearPanes 는 _shownFile 을 지운다
〃 쓰기 세 길(SaveAndCommitAsync · SaveResolvedAsync · DecideDeletionAsync)이 아무것도 쓰기 전에 ShownFileMismatch 로 막고 알린다
하네스 MergeSaveTest 검사 15건 — 같은 파일 · 다른 파일 · 다른 커밋 · 선택 없음 · 경로 구분자 · 대소문자 · 해시 대소문자, 저장 막기(같음 · 다름 · 두 이름이 까닭에 들어감 · 화면이 빔 · 저장할 파일이 빔)

읽기 쪽만 고치고 끝내지 않았다. 되돌릴 수 없는 쓰기 앞에 문을 하나 더 뒀다.

고치는 중에 한 번 더 걸렸다. 버리기만 했더니 [3분할로 열기] 로 처음 열 때 화면이 빈 채로 남았다(단추도 없음). 버린 뒤 다시 읽는 ReloadWhenShownIsStale 을 넣어 고쳤다.

확인한 것 (길마다 저장소를 새로 만들어, 파일 비교 탭을 미리 열지 않고)

길 [3분할로 열기] 뒤 화면 Save 뒤 git 스테이징된 Unit47 안의 class
병합 (merge base) Common/Util/Unit47.cs · 남은 충돌 1개 · REMOTE feature/theirs M Unit47.cs · UU Unit15.cs Unit47
pull 같음 같음 Unit47
rebase 같음 · REMOTE 다시 올리는 내 커밋 1839bd3 같음 Unit47
cherry-pick 같음 · REMOTE 가져오는 커밋 f707175 같음 Unit47

이어서 병합을 사람 순서 그대로 끝까지(파일 비교 탭을 미리 열지 않고) 했다.

확인 결과
파일 단위 Accept Theirs 15개 24 → 9, 1.0초
3분할로 푼 파일 NewPanel · Unit47 · 한글설명 · LineEnding · Unit0(6) · 띠 둘 · View250 · Unit1(6) — 저장할 때마다 다음 파일이 저절로 열림
마지막 Save 뒤 12b0402 · Weld/Unit1.cs · 병합 전 내 쪽 · 충돌 정리 끝 · 병합 커밋을 할 수 있습니다 · 내 쪽 대비 받은 변경 3줄 (7차 A-1 그대로)
병합 커밋 3ba6314 부모 둘 · Merge branch 'feature/theirs' into feature/mine · 워킹트리 Clean
줄끝 · BOM · 인코딩이 첫 부모와 다른 파일 0 / 33 · 충돌 표식 0줄
class 이름이 파일 이름과 다른 Unit 파일 0

하네스 18벌 906건 통과(MergeSaveTest 140). 빌드 경고 0 · 오류 0. 설정 파일은 바이트 그대로 되돌림.

남긴 것

  • 자동 조작 쪽: Save 직후 찍은 화면이 Git 탭이라 파일 비교 탭 제목이 빈 것으로 읽혔다. 화면은 제대로였다(스크린샷).
  • 7차 D-1 때 넣은 우회(파일 비교 탭을 먼저 한 번 열기)는 스위치(D7_NO_PREOPEN)로 껐다 켤 수 있게 두었다. 8차 E-1 은 끈 채로 한다.

B-1. rebase · cherry-pick 에서 마지막 저장 뒤 "남은 차이" 가 뜬다 (2026-09-16, 완료)

고치기 전에 다시 났다 (길마다 저장소를 새로 만들어)

마지막 한 파일(Weld/Unit1.cs)만 남기고 23개를 파일 단위로 풀고, 남은 하나를 네 칸에서 Take Theirs 6번 → [Save].

길 파일 비교 탭 충돌 탭
rebase bbdc66e · Weld/Unit1.cs · 남은 차이 6줄 충돌 정리 끝 - [계속] 으로 rebase 를 이어 갑니다
cherry-pick 30c2133 · Weld/Unit1.cs · 남은 차이 6줄 충돌 정리 끝 - [커밋] 으로 cherry-pick 을 끝냅니다
병합 (회귀) c3691b2 · Weld/Unit1.cs · 병합 전 내 쪽 · 충돌 정리 끝 · 병합 커밋을 할 수 있습니다 · 내 쪽 대비 받은 변경 6줄 같은 말

두 화면이 다른 말을 한다. 방금 받아 놓은 6줄이 "아직 할 일" 로 읽힌다 — 7차 A-1 과 같은 증상인데 병합에서만 고쳐져 있었다.

원인

7차 A-1 에서 만든 MergeSavePlan.ResolvedMergeSummary · MergeBaseHeader 가 병합 전용이었다. 화면도 _workspace.IsMergeInProgress(MERGE_HEAD 기준)로 물었다. rebase 는 MERGE_HEAD 가 없고 cherry-pick 은 CHERRY_PICK_HEAD 라 둘 다 "병합 중이 아님" 이 되어 보통 요약("남은 차이 N줄")으로 떨어졌다. 7차 C-2 에서 만든 작업 상태(GitOperationState · OperationLabels)를 쓰지 않고 있었다.

고친 것

파일 무엇
Services/OperationLabels.cs ResolvedSummary(작업, 남은 충돌, 차이 줄) — 끝말은 진행 줄과 같은 FinishHint(병합 커밋 · [계속] · [커밋])를 쓴다. 차이 줄은 작업마다 무엇인지 적는다(받은 변경 · 올린 변경 · 가져온 변경 · 되돌린 변경). BaseHeader(작업, 짧은해시, 경로) — 병합 "병합 전 내 쪽", rebase "올릴 자리", cherry-pick "가져오기 전 내 쪽", revert "되돌리기 전 내 쪽"
Services/MergeSavePlan.cs 병합 전용이던 ResolvedMergeSummary · MergeBaseHeader 를 걷어냈다(글은 위로 옮김)
ViewModels/HomeViewModel.cs 요약 · BASE 제목이 IsMergeInProgress 대신 IsOperationInProgress · OperationState.Kind 를 본다
하네스 MergeSaveTest 병합 글이 예전과 같은지 6건 + 작업별 요약 · 제목 10건

확인한 것 (같은 순서로 다시)

길 마지막 Save 뒤 파일 비교 탭 충돌 탭
rebase 3e51519 · Weld/Unit1.cs · 올릴 자리 · 충돌 정리 끝 · [계속] 으로 rebase 를 이어 갑니다 · 올릴 자리 대비 올린 변경 6줄 같은 말
cherry-pick 충돌 정리 끝 · [커밋] 으로 cherry-pick 을 끝냅니다 같은 말
병합 (회귀) d091ca2 · Weld/Unit1.cs · 병합 전 내 쪽 · 충돌 정리 끝 · 병합 커밋을 할 수 있습니다 · 내 쪽 대비 받은 변경 6줄 같은 말

cherry-pick 의 BASE 제목에 꼬리가 없는 것은 맞다 — 그때 BASE 는 HEAD 가 아니라 이력에서 고른 가져올 커밋이라 "가져오기 전 내 쪽" 이라고 하면 틀린 말이 된다(제목은 14a9476 · Weld/Unit1.cs, HEAD 는 45bdbf5).

하네스 18벌 914건 통과(MergeSaveTest 148). 빌드 경고 0 · 오류 0. 설정 파일은 바이트 그대로 되돌림.

B-2. 바뀌는 것 없는 cherry-pick 에서 [커밋] 이 켜지고 누르면 실패한다 (2026-09-16, 완료)

고치기 전에 다시 났다 (새로 만든 저장소, 이력에서 HEAD 커밋을 골라 [Cherry-pick])

때 결과
[Cherry-pick] 로그 cherry-pick 적용됨 - 확인한 뒤 충돌 탭의 [커밋] 으로 끝냅니다. · .git 에 CHERRY_PICK_HEAD
충돌 탭 머리 cherry-pick 진행 중 · 진행 줄 충돌 정리 끝 - [커밋] 으로 cherry-pick 을 끝냅니다 · [커밋] 켜짐
[커밋] 을 눌러 봄 로그 [cherry-pick 커밋] 실패: No changes; nothing to commit. · HEAD 그대로 · 알림은 읽히지 않음
그 뒤 진행 줄 · [커밋] 이 그대로라 같은 단추를 또 누르게 된다. [작업 취소] 로는 빠져나온다

원인

  • 끝내기 단추(CanFinishOperation)는 "작업이 진행 중 · 충돌 0" 만 봤다. 커밋할 것이 있는지는 보지 않았다.
  • 진행 줄(OperationLabels.ProgressText)도 충돌이 0 이면 무조건 "끝낼 수 있다" 고 말했다.
  • 시작 로그(BuildPatchResult)는 적용 결과가 Applied 면 늘 "확인한 뒤 [커밋] 으로 끝냅니다" 였다.
  • 실제로 막은 것은 libgit2 였고, 그 말(No changes; nothing to commit.)이 로그에 그대로 나왔다.

고친 것

파일 무엇
Services/GitService.cs HasStagedChanges() — 인덱스가 HEAD 와 다른가. cherry-pick 적용 로그가 이걸 보고 "cherry-pick 했지만 가져올 변경이 없습니다 - [작업 취소] 로 끝냅니다." 로 적는다(revert 는 "되돌릴"). CommitPendingOperation 은 커밋 전에 같은 것을 보고 사람 말로 막는다
Services/IGitService.cs 선언
Services/GitWorkspace.cs PendingHasChanges — 상태를 읽을 때 함께 읽어 둔다. 진행 줄이 이 값을 쓴다
Services/OperationLabels.cs ProgressText(…, hasChanges) — 충돌 0 인데 가져올 것이 없는 cherry-pick · revert 는 "가져올(되돌릴) 변경이 없습니다 - [작업 취소] 로 끝냅니다". 병합 · rebase 는 따지지 않는다(병합 커밋은 트리가 같아도 정상이다)
ViewModels/GitViewModel.cs CanFinishOperation — cherry-pick · revert 는 커밋할 것이 있어야 켜진다
하네스 MergeSaveTest 진행 줄 검사 7건(없음 · revert · 있음 · 충돌 남음 · 병합 · rebase · 기본값이 예전과 같은지)

병합 커밋에는 걸지 않았다. 처음에 CommitMerge 에도 같은 막음을 넣었다가 뺐다 — 병합은 가져올 변경이 없어도(양쪽이 같게 고친 경우 등) 커밋이 정상이다.

확인한 것 (저장소를 새로 만들어)

경우 결과
① HEAD 커밋을 다시 [Cherry-pick] 로그 cherry-pick 했지만 가져올 변경이 없습니다 - [작업 취소] 로 끝냅니다. · 진행 줄 가져올 변경이 없습니다 - [작업 취소] 로 끝냅니다 · [커밋] 꺼짐 · [작업 취소] 켜짐
① [작업 취소] CHERRY_PICK_HEAD 없어짐 · 머리 표시 · 진행 줄 사라짐
② 회귀 - 충돌 24건짜리 보통 cherry-pick 충돌 중 [커밋] 꺼짐 → 다 풀면 진행 줄 충돌 정리 끝 - [커밋] 으로 cherry-pick 을 끝냅니다 · [커밋] 켜짐 → 누르면 cherry-pick 커밋: e35f7b7 상대 쪽 작업(부모 하나 · 원래 메시지)

하네스 18벌 921건 통과(MergeSaveTest 155). 빌드 경고 0 · 오류 0. 설정 파일은 바이트 그대로 되돌림.

정하지 않은 것

  • git 의 --allow-empty 처럼 빈 cherry-pick 을 커밋으로 남기는 길은 지시서대로 두지 않았다. 사용자와 정한 뒤에 넣는다.

B-3. MERGED 칸 줄 번호가 결과 파일 번호가 아니다 (2026-09-16, 완료)

정한 것 (2026-09-16, 사용자) — 두 가지를 보이고 골랐다. ① 결과 파일 번호로(고름) ② 지금처럼 내 쪽 번호로 두고 그렇다고 적기. 결과 파일 번호는 상태바 · [이동] 칸 · 코드 검사의 "N행" 과 같은 기준이다.

고치기 전에 다시 났다 (새로 만든 저장소, 병합 중 Motion/Unit0.cs 를 네 칸으로)

충돌 6개를 Take Theirs 하면 고른 줄 · 머지 주석으로 결과가 늘어난다.

확인 값
상태바 총 3369줄
[이동] 칸에 3369 3369행으로 감
MERGED 칸 끝 번호 3363 — 여섯 줄이 모자란다
LOCAL (Mine) 칸 끝 번호 3361 (내 파일 그대로 · 맞다)

원인

MERGED 칸 번호는 MergeLine.LocalNumber 인데, 이 값은 정렬기가 짝지을 때 한 번 정해지고 그대로다. 머지 주석 줄은 새로 끼워 넣으면서 번호를 주지 않았고(Lines.Insert), 그 뒤의 줄들도 번호를 다시 매기지 않았다. 그래서 결과가 길어질수록 어긋난다.

고친 것

파일 무엇
Services/MergedNumbering.cs (새 파일) Assign(결과에 들어가는가[]) — 결과에 들어가는 줄만 1부터 센다. Count — 결과 줄 수
ViewModels/HomeViewModel.cs RenumberMerged(rows) — 줄 목록에 결과 번호를 다시 매긴다. 두 줄 만들기(두 칸 · 네 칸) 끝과 요약을 그릴 때마다(고르기 · 편집 · 되돌리기 · 주석 끼우기 뒤) 부른다
하네스 MergeSaveTest 번호 검사 7건(결과에 없는 줄 · 끝 번호 = 결과 줄 수 · 빈 목록 · null 등)

결과 파일은 BuildLocalText 가 HasLocal 인 줄만 이어 붙인 것이라, 번호도 같은 기준으로 센다. LOCAL (Mine) 칸은 OriginalLocalNumber(내 파일 번호)를 그대로 쓴다 — 바꾸지 않았다.

확인한 것 (같은 순서로 다시)

확인 값
상태바 총 3369줄
MERGED 칸 끝 번호 3369 (3352 ~ 3369)
[이동] 칸에 3369 3369행
[Save] 한 뒤 워킹트리 파일 3369줄 — 화면 번호와 같다
LOCAL (Mine) 칸 3346 ~ 3361 그대로

하네스 18벌 928건 통과(MergeSaveTest 162). 빌드 경고 0 · 오류 0. 설정 파일은 바이트 그대로 되돌림.

C-1. revert 충돌을 화면으로 끝까지 (2026-09-16, 완료)

7차 C-2 에서 revert 는 rebase · cherry-pick 과 같은 길을 쓰게 했지만 화면으로 끝까지 해 본 적이 없었다.

시험 재료를 먼저 만들었다 (생성기 C1Repo, 같은 씨앗)

revert 가 충돌을 내려면 되돌릴 줄을 뒤이어 또 고친 커밋이 있어야 한다. 갈래를 하나 더했다.

  • feature/revert = 내 쪽 작업 위에 뒤이은 수정 하나. 같은 줄 충돌 15개 파일과 큰 파일 Motion/Unit0.cs 의 같은 자리를 또 고친다.
  • 기존 경우(병합 · pull · rebase · cherry-pick)는 그대로다 — feature/mine · feature/theirs 를 건드리지 않았다.
  • git 으로 미리 재 보니 그 위에서 내 쪽 작업 을 revert 하면 충돌 16개다.

확인한 것 (MergeHub 만으로)

때 결과
이력에서 내 쪽 작업 을 골라 [Revert] 알림 충돌 16건 - … 다 풀면 [커밋] 으로 revert 를 끝냅니다. · 로그 revert 시작: c579a81 내 쪽 작업 → revert 충돌 16건 - 풀고 나서 충돌 탭의 [커밋] 으로 끝냅니다.
충돌 중 머리 revert 진행 중 · [작업 취소] 켜짐 · 진행 줄 남은 충돌 16개 · 끝내기 단추 [커밋](꺼짐)
네 칸 (Motion/Unit0.cs) LOCAL feature/revert · REMOTE REMOTE · 되돌리는 커밋 c579a81 내 쪽 작업 · 머리 글 revert 충돌 · REMOTE = 되돌리는 커밋 c579a81 · "3-Way 안 씀" 없음
줄을 고르면 로그 101행 Theirs(커밋 c579a81) 반영 (6줄)
나머지 15개 파일 단위 Accept Theirs git 충돌 15 → 0
다 푼 뒤 진행 줄 충돌 정리 끝 - [커밋] 으로 revert 를 끝냅니다 · [커밋] 켜짐 · 커밋 메시지 칸 Revert "내 쪽 작업" + This reverts commit c579a81…
[커밋] 로그 revert 커밋: acefbcd Revert "내 쪽 작업" · 부모 하나 · 진행 표시 · [작업 취소] 사라짐 · 워킹트리 Clean
결과 Common/Util/Unit15.cs 가 바탕과 같아짐 · 되돌린 글("내가 고침") 0개 파일 · REVERT_HEAD 없음
바이트 바뀐 파일 36개 · 줄끝 · BOM · 인코딩이 부모와 다른 파일 0 · 충돌 표식 0줄 · class 이름이 다른 Unit 파일 0

알게 된 것 · 남긴 것

  • 충돌 난 16개 파일에서는 뒤이은 수정 의 글도 함께 사라진다. 그 줄들을 전부 Accept Theirs(되돌리는 쪽)로 골랐기 때문이다 — 고른 대로다. 뒤 커밋의 글을 지키려면 그 줄만 Take Mine 으로 고르면 된다.
  • 자동 조작 쪽: 첫 확인에서 파일 단위 Accept Theirs 가 15개 중 1개만 풀었다(목록 선택이 앱에 안 닿음 - 7차 C-2 와 같은 증상). 충돌이 0 이 될 때까지 되풀이하도록 스크립트를 고쳐 다시 했다. 제품 결함이 아니다.
  • 제품 코드는 한 줄도 바꾸지 않았다. 7차 C-2 에서 만든 길이 revert 에서도 그대로 동작했다.

C-2. cherry-pick 이 원래 작성자를 남기는지 눈으로 (2026-09-16, 완료)

7차 C-2 에서는 시험 저장소의 커밋 작성자가 모두 같아(시험) 코드로만 확인했다.

시험 재료 (생성기 C1Repo, 같은 씨앗)

상대 쪽 작업 커밋만 다른 사람이 쓴 것으로 뒀다 — 작성자 박지훈 <jihoon@localhost> · 커밋한 사람 시험 <test@localhost>. 저장소의 user.name 은 그대로 시험 이라, 앱이 커밋하면 커밋한 사람은 시험 이 된다.

확인한 것 (같은 저장소를 새로 만들어, MergeHub 와 git 을 따로)

무엇 작성자 커밋한 사람
가져올 커밋 (상대 쪽 작업) 박지훈 <jihoon@localhost> 시험
MergeHub cherry-pick → [커밋] 박지훈 <jihoon@localhost> 시험 <test@localhost>
git cherry-pick (사본에서 같은 순서) 박지훈 <jihoon@localhost> 시험 <test@localhost>
MergeHub revert → [커밋] 시험 <test@localhost> 시험 <test@localhost>
git revert (사본에서) 시험 <test@localhost> 시험 <test@localhost>

둘이 같다. cherry-pick 은 원래 작성자를 남기고 커밋한 사람만 지금 사용자로 바뀐다. revert 는 둘 다 지금 사용자다 — git 과 같은 규칙이다.

이력 목록에도 그 커밋이 상대 쪽 작업 dddd515 · 박지훈 으로 보여, 고를 때 누가 쓴 것인지 화면에서 읽힌다.

제품 코드는 바꾸지 않았다.

남긴 것

  • 자동 조작 쪽: git 으로 revert 를 견줄 때 MergeHub 가 이미 revert 한 뒤의 저장소를 복제해 git 이 되돌릴 것이 없었다. 저장소를 새로 만들어 git 쪽만 다시 쟀다(위 표의 값).

C-3. 창 크기 조절에서 안 본 것 (2026-09-16, 완료 · 플랫폼)

7차 E-4 에서 "스냅 · 첫 Win+↓ · 덮는 창 가운데 · 1px 테두리" 네 가지를 남겨 뒀다. 실제 픽셀로 재고, 정할 것은 사용자와 정했다.

정한 것 (2026-09-16, 사용자)

  • 첫 Win+↓ 는 두 번 누르기 복원과 같은 크기 · 자리로.
  • 최대화일 때 덮는 창(이름 · 확인 · 잠금 확인)도 창 가운데로.
  • 보통 크기 창의 1px 테두리 선은 그대로 둔다(Windows 가 그리는 선 - 창 경계가 보여 끌기 쉽다).

고치기 전에 쟀다 (2560×1600@150% · 1920×1080@100% 두 모니터)

확인 값
첫 Win+↓ (기억 없음) 2400×1350px @(266,266) — 1600×900 DIP(XAML 기본). 아래로 16px 화면 밖, 작업 표시줄도 가림
이름 창 (최대화 창 2560×1600) 가운데에서 세로 36px 위 — 작업 표시줄 높이(72px)의 절반
이름 창 (보통 크기 창) 어긋남 0 (이때는 맞았다)
Win+← 주 모니터(150%) 1920×1528px = 1280×1018.7 DIP — 화면 반(1280px)이 아니라 75%. 최소 폭 1280 DIP 때문
Win+← 옆 모니터(100%) 1280×1032px — 반(960px)보다 큼. 같은 까닭
Win+↑ (왼쪽 스냅 상태에서) 1280×720px — Windows 11 의 네 분할 자리인데 최소 크기라 그만큼 커진다

고친 것 (플랫폼)

파일 무엇
Shell/ShellWindow.xaml.cs 최대화로 들어갈 때 기억 여부를 잡아 둔다(normalBeforeMaximize) — 복원할 때 보면 늦다. 크기 알림이 상태 알림보다 먼저 와서 방금 내려온 기본 크기가 "사용자가 고른 보통 크기" 로 기억돼 버린다. 기억이 없던 복원은 RestoreTarget(work, useRemembered: false) 로 작업 영역 80% · 가운데. 기억이 있어도 화면 밖으로 걸치면 KeepInside 로 끌어들인다
Shell/OwnerCentering.cs (새 파일) 덮는 창을 본 창의 실제 사각형 가운데에 놓는다. WPF 의 CenterOwner 는 최대화 창의 "복원 자리" 를 기준 삼아 어긋난다. 맞출 창이 없으면(켤 때 이름 창이 셸보다 먼저 뜬다) 모니터 전체 가운데 — 셸의 최대화가 작업 표시줄까지 덮으므로 그 가운데와 같다
Settings/UserNameDialog.xaml.cs · Messaging/MessageDialog.xaml.cs · Locking/LockConfirmDialog.xaml.cs 다 그려진 뒤(ContentRendered) 위 도우미를 부른다. Loaded 때는 높이가 아직 없어(SizeToContent) 그냥 돌아갔다

확인한 것 (실제 픽셀 · 배포본으로 다시)

확인 고치기 전 고친 뒤
첫 Win+↓ (기억 없음) 2400×1350 @(266,266) · 화면 밖 16px 2048×1222 @(256,153) = 작업 영역 80% · 가운데 · 화면 밖 0
두 번 누르기 복원 작업 영역 80% 같음 (둘이 같아졌다)
이름 창 · 최대화 창 세로 36px 위 어긋남 가로 0 · 세로 0
이름 창 · 보통 크기 창 0 0 (그대로)
보통 크기로 [종료] → 다시 켜기 - 기억 {256,153,2048,1222,최대화 false} 대로 뜬다
화면 이동 DIP 크기 지킴 같음(1280×1019 DIP)

PlacementTest 37건 통과. 플랫폼 빌드 경고 0 · 오류 0. 배포는 사용자 확인 뒤 publish-platform.ps1 로 했고, MergeHub 를 다시 빌드해 배포본으로 같은 값을 다시 쟀다(앱 폴더 Core = 배포본 확인).

남긴 것 (고치지 않음)

  • 스냅은 화면 반쪽이 될 수 없다. 최소 크기(1280×720 DIP)가 반쪽보다 크기 때문이다 — 주 모니터에서 75%, 옆 모니터에서 67%. 최소 크기를 줄이지 않는 한 그대로다. Windows 11 의 네 분할도 같은 까닭으로 1280×720 이 된다.
  • 왼쪽 스냅 상태에서 Win+→ 는 스냅을 풀지 않고 그대로 있었다(잰 두 번 중 한 번은 복원됐다). Windows 쪽 동작이라 두고 본다.
  • 1px 테두리 선은 사용자 결정대로 그대로 둔다.
  • 화면 잠금은 재지 않았다. 잠그면 사람이 직접 풀어야 해서 확인이 멈춘다. 잠금 확인 창은 이름 창과 같은 길(OwnerCentering)을 쓰므로 함께 맞는다.

D-1. 웹 문서를 7차 · 8차에 맞게 (2026-09-16, 완료)

doc/web/index.html 이 6차에서 멈춰 있었다. 7차 · 8차에서 바뀐 화면과 말을 맞추고, "알아 둘 한계" 를 지금 기준으로 다시 썼다.

고친 것

어디 무엇
개요(숫자) 검사 하네스 22벌 · 1,073건(948건에서), 로컬 커밋 94(75에서), "7차 끝 · 8차 진행 · 네 길 줄끝 바뀐 파일 0". 출처를 작업지시/8차.md 로
매뉴얼 · 켜기 이름을 저장하기 전에는 그 창이 닫히지도 움직이지도 않는다(7차 E-3). 창 크기 문단 추가 — 끌어 조절(최소 1280 × 720) · 두 번 누르기 · Win+↑/↓ · 최대화는 작업 표시줄까지 덮음 · 크기 · 모니터 기억 · [화면 이동] 이 크기를 지킴(7차 E-4 · 8차 C-3)
매뉴얼 · 폴더 비교 끌어다 놓기 첫 칸 → 둘째 칸 차례, 두 칸이 차면 바로 비교(7차 E-2)
매뉴얼 · 네 칸 네 칸이 뜨는 경우를 작업별로 — rebase 는 LOCAL 이 올릴 자리 · REMOTE 가 다시 올리는 내 커밋, cherry-pick 은 가져오는 커밋, revert 는 되돌리는 커밋. 3-Way 콤보 자리의 머리 글. MERGED 번호는 결과 파일 번호(8차 B-3)와 줄 고른 로그 문구(7차 A-2)
매뉴얼 · 병합 순서 마지막 [Save] 뒤 화면이 작업에 맞게 말한다(7차 A-1 · 8차 B-1). pull · rebase · cherry-pick · revert 도 같은 길 문단 — 다른 것은 끝내는 단추뿐. 가져올 변경이 없는 cherry-pick 안내(8차 B-2)와 이력 [모든 브랜치](7차 C-2)
매뉴얼 · Git 탭 충돌 탭 끝내기 단추가 작업에 따라 병합 커밋 · 계속 · 커밋. 이력 탭에 [모든 브랜치]
매뉴얼 · 알아 둘 한계 7차 · 8차에서 사라진 네 줄(주석 자동 풀기가 Merge 에만 · rebase · cherry-pick 미확인 · "3-Way 안 씀" 로그 · 마지막 저장 뒤 "남은 차이")을 빼고, 지금 남은 것으로 — 파일 단위 Accept 는 고른 쪽 줄끝을 그대로 가져온다 · 빠른 비교 · 코드 검사 · 스냅이 반쪽이 안 됨(8차 C-3) · 로그인 · 권한
남은 일 "7차 재료" 표를 7차에서 고친 것 · 8차에서 고친 것 두 표로. "6차에서 남긴 것" 을 8차에 남은 것(E-1 다섯 길 재확인 · 빈 커밋을 두지 않기로 한 결정)으로
지금까지 만든 것 09-15(7차) · 09-16(8차) 두 줄 추가, 기획 3단계 칸을 6차 → 7차 → 8차로
발표 자료 17장의 "7차: …" 줄을 "7차 · 8차: 다른 충돌 길 · 셸 창 크기 조절 — 끝" 으로
머리 문서 기준 날짜 2026-09-14 → 2026-09-16

확인한 것

  • 화면 문구와 대조: "해결로 저장" · "3-Way 안 씀" · "948건" 같은 옛말이 문서에 하나도 없다(grep).
  • 태그 짝 세기: section 26/26 · div 188/188 · table 26/26 · ul 35/35 · ol 11/11 · li 193/193 · tr 212/212 · td 520/520.
  • 발행: 같은 주소의 아티팩트를 버전 7 로 갱신했다(읽어서 옛 내용을 확인한 뒤 올림). 저장소의 doc/web/index.html 과 같은 파일이다.

제품 코드는 바꾸지 않았다.

F-1. 병합 커밋 메시지 본문에 충돌 해결 내역을 자동으로 적기 (2026-09-17, 추가 요청, 완료)

사용자: "파일단위 머지할때 이력은 그럼 어디남아?" → 제안 중 병합 커밋 본문에 해결 내역을 자동으로 적기로 정함. 함께 "스타일 맞추는건 안할거야" — 비교할 때만 C# 스타일을 맞추고 저장은 원래대로 하는 선택지는 만들지 않는다.

고치기 전

충돌을 어떻게 풀었는지가 오래 남는 곳이 없었다.

어디 남는 것 한계
소스 네 칸에서 줄을 가져오면 // [이름] 날짜 충돌 탭 파일 단위 Accept 는 고른 쪽 파일을 바이트째 써서 주석이 없다
git 병합 커밋 부모 둘 · 제목 · 작성자 본문이 비어 있었다 — 7차 D-1(fce391f "본문 없음") · 8차 A-1(3ba6314) 에서 잰 대로
Git 탭 로그 충돌 해결(Theirs): 경로 화면에만 있고 끄면 사라진다(GitWorkspace.AppendLog 는 글 속성에만 쌓음)
플랫폼 로그 D:\UbisamConfig\Log 버튼 클릭: Accept Theirs 파일 이름도 사람도 없다

고친 것

파일 무엇
Services/MergeResolutionLog.cs (새 파일) MergeResolutionKind(줄 단위 · Accept Mine · Theirs · Both · 파일 남기기 · 지우기 · 머지 주석 자동). 기록 파일 읽기 · 쓰기(Parse · Record — 첫 줄 MERGE_HEAD, "종류 탭 경로", 같은 파일은 마지막 것). 본문 만들기(Body — "충돌 해결 N개 · 이름", 갈래마다 파일 한 줄씩, 한 갈래 30개 넘으면 "… 외 N개"). 메시지에 붙이기(WithBody — 옛 블록은 갈아 끼워 두 번 안 들어가고, 사람이 쓴 본문은 지키고 뒤에 붙인다)
Services/GitService.cs RecordResolution — 병합 중(MERGE_HEAD)일 때만 .git/MERGEHUB_RESOLUTIONS 에 적는다. 못 적어도 푸는 일은 막지 않는다. GetResolutionBody · ResolverName. 머지 주석 자동 풀기가 적는다. CommitMerge 가 본문을 붙인다 — 사람이 메시지를 고쳤어도 블록은 붙는다(유일한 기록). ForgetStaleMergeConflicts 가 기록 파일도 같은 규칙으로 치운다(병합 커밋 · 작업 취소 · 새로고침)
Services/IGitService.cs 선언
Services/GitWorkspace.cs 상태를 읽을 때 이름을 넘기고(Git.ResolverName) 본문을 MergeResolutionBody 로 들고 있는다
ViewModels/GitViewModel.cs 충돌 탭 Accept Mine · Theirs · Both 가 적는다. 병합 메시지를 채울 때 본문을 붙이고, 본문이 바뀌면(충돌을 하나 풀 때마다) 메시지 칸을 새로 채운다 — 사람이 칸을 고쳤으면 두고, 커밋 때 붙는다
ViewModels/HomeViewModel.cs [Save] · Save & Commit 은 "줄 단위", 띠 [파일 남기기] · [파일 지우기] 가 적는다
하네스 MergeSaveTest 21건 — 기록(첫 줄 · 다른 병합 버림 · 같은 파일 마지막 것 · 역슬래시 · 빈 글), 본문(첫 줄 · 갈래 이름 · 파일마다 한 줄 · 순서 · 없음 · 사람 모름 · 30개 넘음), 메시지(제목 · 빈 줄 · 본문 · 두 번 안 붙음 · 갈아 끼움 · 사람 본문 지킴 · 걷어냄 · CRLF · 제목 없음)

cherry-pick · revert · rebase 는 원래 커밋 메시지를 쓰므로 이번에는 적지 않는다(MERGE_HEAD 가 없어 RecordResolution 이 건너뛴다).

확인한 것 (길마다 저장소를 새로 만들어, MergeHub 만으로)

① 병합을 사람 순서 그대로 끝까지 (파일 단위 15 · 네 칸 7 · 띠 2 · 머지 주석 자동 2)

  • [병합 커밋] 전 Git 탭 메시지 칸에 본문이 미리 보였다.
  • 커밋 d023a8c 본문:
Merge branch 'feature/theirs' into feature/mine

충돌 해결 26개 · 김승준
줄 단위 (파일 비교에서 골라 Save) 7
  Ui/Views/NewPanel.cs … Weld/Unit1.cs
Accept Theirs (상대 쪽 파일 그대로) 15
  Common/Util/Unit15.cs … Weld/Unit17.cs
파일 남기기 1
  Recipe/ToBeDeletedByMe.cs
파일 지우기 1
  Recipe/ToBeDeleted.cs
머지 주석만 달라 자동으로 풂 2
  Ui/Dialogs/Unit46.cs
  Ui/Views/Unit45.cs
  • 7 + 15 + 1 + 1 + 2 = 26 = 처음 충돌 수. 커밋 뒤 MERGEHUB_RESOLUTIONS · MERGEHUB_CONFLICTS 없음. 줄끝 · BOM 바뀐 파일 0/33 · 충돌 표식 0 · class 이름 검사 0 · 워킹트리 Clean (회귀 없음).

② 병합 → Accept Mine 두 개 → [작업 취소]

때 결과
Accept Mine 뒤 기록 파일 첫 줄 MERGE_HEAD, NoteAuto 2 · Mine Io/Unit12.cs · Mine Io/Unit20.cs
메시지 칸 "충돌 해결 4개 · 김승준 / Accept Mine (내 쪽 파일 그대로) 2 / 머지 주석만 달라 자동으로 풂 2"
[작업 취소] 뒤 git 충돌 0 · 기록 파일 없음

③ pull → 전부 파일 단위 Accept Theirs → [병합 커밋]

  • 본문 "Merge remote-tracking branch 'origin/feature/mine' into feature/mine" 아래 충돌 해결 26개 — Accept Theirs 24(지운 파일 둘도 파일 단위로 골랐으므로 여기에) · 머지 주석 자동 2. 기록 파일 없음 · Clean.

하네스 22벌 1,094건 통과(MergeSaveTest 183). 빌드 경고 0 · 오류 0. 설정 파일은 바이트 그대로 되돌림.

알아 둘 것

  • 병합 커밋 작성자는 지금처럼 git user.name 이고, 본문의 이름은 MergeHub 의 작업자 이름(설정 [머지 주석] 작성자 → 켤 때 이름 → git user.name 순)이다. 둘이 다르면 둘 다 남는다.
  • 메시지 칸에서 본문을 지워도 커밋할 때 다시 붙는다 — 파일 단위 Accept 의 유일한 기록이라 뺄 수 없게 했다.
  • MergeHub 밖(터미널)에서 푼 충돌은 적히지 않는다. 본문 건수가 처음 충돌 수보다 적으면 그만큼은 밖에서 푼 것이다.

E-1. 다섯 길을 사람 순서 그대로 다시 끝까지 (2026-09-17, 완료)

지시서대로 고치지 않고 적기만 했다. 제품 코드는 한 줄도 안 바꿨다.

무엇으로 했나

  • 길마다 시험 저장소를 C1Repo 로 새로 만들었다. 병합 · pull · rebase · cherry-pick 은 충돌 26개(머지 주석만 다른 2개 포함), revert 는 feature/revert 위에서 16개.
  • 파일 비교 탭을 미리 열지 않았다 — 충돌 탭의 [3분할로 열기] 로 처음 연다(8차 A-1 을 그대로 밟는 순서). 켤 때 이름 창은 [저장] 으로 닫고, 접힌 원격 영역은 [펴기] 로.
    • 처음 돌린 병합 스크립트에 "파일 목록 세기" 단계가 남아 있어 병합만 탭을 먼저 한 번 열었다. 그 단계를 스위치로 끄고 병합을 다시 했다(아래 표 ① 은 다시 한 것).
  • 푸는 순서는 네 길 같게: 같은 줄 충돌 15개 파일 단위 Accept Theirs → NewPanel Accept Both [아니오] → [3분할로 열기] 뒤 저절로 열리는 파일마다 Theirs · Mine 번갈아 [Save] · 띠 [파일 지우기] · [파일 남기기]. revert 는 큰 파일 하나를 네 칸으로, 나머지 15개를 파일 단위로.
  • 확인 값: 커밋 blob 바이트로 BOM · CRLF · LF · UTF-8 을 첫 부모와 견줌, 충돌 표식, UnitN.cs 의 class 이름. 설정 CSharpBraceStyle 은 길마다 백업 → 끔 → 되돌림(다섯 번 모두 해시 같음).

다섯 길의 결과

확인 ① 병합 ② pull ③ rebase ④ cherry-pick ⑤ revert
시작 알림 충돌 24건 (머지 주석만 다른 2건은 자동으로 풀림) 같음 같음 + [계속] 안내 같음 + [커밋] 안내 충돌 16건 + [커밋] 안내
머리 · 진행 줄 병합 진행 중 · 2/26 같음 rebase 1/1 · 남은 충돌 24개 cherry-pick 진행 중 · 24개 revert 진행 중 · 16개
[3분할로 열기] 로 처음 연 파일 NewPanel (고른 파일) 같음 같음 같음 Motion/Unit0.cs (고른 파일)
네 칸 REMOTE feature/theirs 같음 다시 올리는 내 커밋 가져오는 커밋 · 작성자 박지훈 되돌리는 커밋
줄 고른 로그 18줄 · "3-Way 안 씀" 0 같음 18줄 · 0 18줄 · 0 6줄 · 0
마지막 Save 뒤 파일 비교 탭 충돌 정리 끝 · 병합 커밋을 할 수 있습니다 · … · 병합 전 내 쪽 같음 충돌 정리 끝 · [계속] 으로 rebase 를 이어 갑니다 · … · 올릴 자리 충돌 정리 끝 · [커밋] 으로 cherry-pick 을 끝냅니다 (N1) (마지막 파일은 파일 단위)
충돌 남은 채 Save & Commit 막힘 · git 그대로 같음 꺼짐 꺼짐 -
끝낸 커밋 3725e9c 부모 둘 69214e6 부모 둘 · Merge remote-tracking branch … f2b3b2a rebase 완료 1/1 96cfbc4 부모 하나 · 원래 메시지 650bcd1 Revert "내 쪽 작업"
커밋 본문(8차 F-1) 충돌 해결 26개 · 김승준 (줄 단위 7 · Theirs 15 · 남기기 1 · 지우기 1 · 주석 자동 2) 같음 26개 해당 없음 해당 없음 해당 없음(원래 revert 본문)
줄끝 · BOM · 인코딩 바뀐 파일 0 / 33 0 / 33 0 / 31 0 / 33 0 / 36
충돌 표식 · class 이름 다른 Unit 0 · 0 0 · 0 0 · 0 0 · 0 0 · 0
끝난 뒤 Clean · .git 표시 없음 · 기록 파일 없음 같음 Clean · rebase-merge 없음 Clean · CHERRY_PICK_HEAD 없음 Clean
빈 cherry-pick (HEAD 를 다시) - - - [커밋] 꺼짐 · "가져올 변경이 없습니다 - [작업 취소] 로 끝냅니다" · [작업 취소] 로 나옴 -

판정 기준

기준 결과
다섯 길 모두 MergeHub 만으로 끝까지 됐다
줄끝 · BOM 바뀐 파일 0 · 충돌 표식 0 · class 이름 다른 Unit 파일 0 다섯 길 모두 0
7차 A-1 · A-2 · B-1 다시 안 남 안 남 — 마지막 Save 뒤 요약이 충돌 탭과 같은 말, "3-Way 안 씀" 로그 0, 커밋 뒤 MERGEHUB_CONFLICTS 없음
8차 A-1 다시 안 남 안 남 — 파일 비교 탭을 [3분할로 열기] 로 처음 열어도 고른 파일이 열리고, class 이름 검사 0
8차 B-1 · B-2 다시 안 남 안 남 — rebase "올릴 자리", 빈 cherry-pick [커밋] 꺼짐
8차 F-1 병합 · pull 본문 26개 = 처음 충돌 수, 커밋 뒤 기록 파일 없음

새로 걸린 것 (고치지 않음 → 9차)

# 무엇 근거
N1 cherry-pick 마지막 Save 뒤 두 칸의 BASE 는 HEAD(내 쪽)가 아니라 이력에서 고른 커밋인데, 요약은 "내 쪽 대비 가져온 변경 N줄" 이라고 한다(8차 B-1 에서 넣은 글). 줄 수가 무엇과 견준 것인지 틀리게 읽힌다 따로 재 봄: Selected Commit 0c9bd22 "상대 쪽 작업"(가져온 커밋) · HEAD 85e6806. 8차 B-1 기록의 "cherry-pick 제목에 꼬리가 없는 것은 맞다" 는 제목만 본 판단이었다
N1-덧 E-1 cherry-pick 에서 한 번은 제목이 f310f49 · Weld/Unit1.cs 로, HEAD(b7ac29b)도 가져온 커밋(68fe17f)도 아니었다. 줄 수(3줄)로 보면 [모든 브랜치] 목록의 다른 브랜치 커밋(뒤이은 수정)으로 보인다 같은 순서로 따로 다시 재니 가져온 커밋으로 잡혀 다시 나지 않았다. 새로고침 뒤 이력 선택이 어느 커밋으로 돌아가는지 9차에서 원인부터 본다

시험 도구 쪽 (제품 결함 아님)

  • 병합 스크립트의 "파일 목록 세기" 가 파일 비교 탭을 먼저 여는 바람에 조건이 어긋났다 — 스위치(D7_NO_PREOPEN)로 끄고 다시 했다.
  • 결과 확인 스크립트가 부모 하나인 커밋(rebase · cherry-pick · revert)에서 merge-base 오류를 냈지만 줄끝 판정은 첫 부모와 제대로 했다.
  • Recipe/ToBeDeletedByMe.cs 를 남기면 git 상태가 R(이름 바뀜)로 읽힌다 — git 의 이름 추적이고 커밋 결과는 맞다(7차 D-1 과 같음).

코드는 바꾸지 않았으므로 검사는 그대로(하네스 22벌 1,094건) · 빌드 경고 0 · 오류 0.