Ubisam-MergeTool/doc/history/2026-09-14-7차.md
ksj09406 bb6bd655a5 7차 B-1: 병합이 끝나면 .git/MERGEHUB_CONFLICTS 를 치운다
병합 때 적는 처음 충돌 목록이 병합 커밋 · 작업 취소 · 터미널에서 끝낸 병합 뒤에도 남았다.
MERGE_HEAD 가 없거나 다른 병합의 것이면 병합 커밋 · 작업 취소 직후와 상태를 다시 읽을 때마다 지운다.
병합 중에는 그대로라 "푼 것 M개 (M/전체)" 는 이어진다. 세 경우 화면에서 확인, MergeSaveTest 88건 통과.

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

227 lines
16 KiB
Markdown

# 7차 — 병합 화면이 사실대로 말하게 · 병합 말고 다른 충돌 길 (2026-09-14 ~)
지시서: [../작업지시/7차.md](../작업지시/7차.md). 6차 D-1 에서 새로 걸린 세 가지와 6차에서 뺀 pull · rebase · cherry-pick 충돌.
## A-1. 마지막 충돌을 저장하면 "남은 차이" 가 뜬다 (2026-09-14, 완료)
### 고치기 전에 다시 났다
새로 만든 저장소를 git 으로 병합하고 `Weld/Unit1.cs` 하나만 남긴 채 나머지 25개를 상대 쪽으로 풀었다(준비만 git).
MergeHub 파일 비교 탭에서 그 파일의 충돌 6개를 Take Theirs → [Save].
| 때 | 칸 제목 | 요약 | 충돌 탭 |
|---|---|---|---|
| 충돌 파일 열림 | `(merge base) Weld/Unit1.cs` · 네 칸 | 남은 충돌 6개 | - |
| **마지막 Save 뒤** (git 충돌 0, 병합 중) | **`90d2a8c · Weld/Unit1.cs`** · 두 칸 | **남은 차이 6줄** | 충돌 정리 끝 - 병합 커밋을 할 수 있습니다 |
두 화면이 다른 말을 한다. 파일 비교 탭만 보면 덜 푼 것으로 읽힌다.
(처음 두 번은 재현 준비가 틀렸다 - 충돌 탭에서 22개를 골라 한 번에 Accept 하려 했는데 자동 조작의 목록 찾기가 엉뚱한 목록을 잡아 한 개만 풀렸고,
그다음은 준비 스크립트가 모든 충돌을 풀어 버렸다. 세 번째에 저장소를 새로 만들어 git 으로 준비했다.)
### 원인
- [Save] 뒤 `ReloadAfterResolveAsync` → `LoadCommitAsync` 가 충돌이 0 이면 보통 화면(HEAD ↔ 워킹트리)으로 연다. 방금 저장한 파일이 목록에 있어 그대로 골라진다.
- 보통 화면의 요약은 `남은 차이 N줄`, BASE 제목은 `짧은해시 · 경로` 뿐이라 **병합 중이라는 사정도, 그 차이가 방금 받은 변경이라는 것도** 말하지 않았다.
### 고친 것
| 파일 | 무엇 |
|---|---|
| `Services/MergeSavePlan.cs` | `ResolvedMergeSummary(병합 중, 남은 충돌, 차이 줄)` — 병합 중 · 충돌 0 이면 "충돌 정리 끝 · 병합 커밋을 할 수 있습니다", 차이가 있으면 "· 내 쪽 대비 받은 변경 N줄". 아니면 null. `MergeBaseHeader` — `짧은해시 · 경로 · 병합 전 내 쪽` |
| `ViewModels/HomeViewModel.cs` | `UpdateSummaries`: Git 모드 두 칸에서 위 요약을 쓴다. `LoadFileAsync`: 병합 중이고 BASE 가 HEAD 면 제목에 "병합 전 내 쪽" |
| 하네스 `MergeSaveTest` | 요약 · 제목 검사 7건(병합 아님 · 충돌 남음 → 보통 요약, 차이 0 · 6 · 음수, "남은" 이 안 들어감) |
화면 틀(두 칸)과 파일 목록은 바꾸지 않았다. 병합 커밋 뒤에는 병합 중이 아니므로 지금처럼 보통 화면이다(6차 B-8).
### 확인한 것 (같은 준비로 다시)
| 때 | 칸 제목 | 요약 · 상태바 |
|---|---|---|
| 충돌 파일 열림 | `(merge base) Weld/Unit1.cs` | 남은 충돌 6개 |
| **마지막 Save 뒤** (git 충돌 0) | **`9ab2256 · Weld/Unit1.cs · 병합 전 내 쪽`** | **충돌 정리 끝 · 병합 커밋을 할 수 있습니다 · 내 쪽 대비 받은 변경 6줄** |
| 충돌 탭 | - | 충돌 정리 끝 - 병합 커밋을 할 수 있습니다 (말이 같다) |
| [병합 커밋] 뒤 (`cea61d2`, 부모 둘) | `cea61d2 · Weld/Unit1.cs` | 차이 없음 · 병합 진행 중 표시 없음 |
MergeSaveTest 70건 통과. 빌드 경고 0 · 오류 0. 설정 파일은 바이트 그대로 되돌림.
## A-2. 3분할에서 줄을 골라도 로그는 "3-Way 안 씀" 이라고 한다 (2026-09-14, 완료)
### 고치기 전에 다시 났다
새로 만든 저장소를 git 으로 병합(충돌 26)하고 MergeHub 로 열었다. 파일 비교 탭이 첫 충돌 파일 `Common/Util/Unit15.cs` 를 네 칸으로 연다.
| 확인 | 결과 |
|---|---|
| 칸 | 네 칸 · REMOTE 제목 `REMOTE · feature/theirs` |
| 머리의 3-Way 콤보 | **`(3-Way 안 씀)`** — 네 칸이 떠 있는데 콤보는 안 쓴다고 말한다 |
| [다음 충돌] → Take Theirs | 로그 **`10행 (3-Way 안 씀) 반영`** |
| Undo → Take Mine | 로그 **없음** — 무엇을 골랐는지 남지 않는다 |
### 원인
- `ApplyRemote` 가 로그에 3-Way 콤보 값(`RemoteBranch`)을 썼다. 충돌 파일은 콤보를 안 건드리고 네 칸을 띄우므로(6차 A-3) 그 값이 `(3-Way 안 씀)` 이다.
- `TakeLocalLine` 은 줄이 이미 내 것과 같으면 표시만 남기고 로그 없이 돌아갔다. 충돌에서 Take Mine 은 대개 이 경우다.
- 콤보는 병합 충돌에서 가리킬 것이 없는데도 그대로 보였다.
### 고친 것
| 파일 | 무엇 |
|---|---|
| `Services/MergeNames.cs` | `SideLabel` · `TakeTheirsLog` · `TakeMineLog`. 병합의 두 쪽이면 `Theirs(이름)` · `Mine(이름)`, 이미 내 것이면 "으로 정함". 콤보로 고른 브랜치 3-Way 는 지금 문구 그대로("7행 develop 반영", "5행 내 원본으로 되돌림") |
| `ViewModels/HomeViewModel.cs` | 충돌 파일을 열 때 상대 이름(`GetMergeTheirsName`)을, git mergetool 세 벌은 파일 이름을 들고 있는다. Take Theirs · Take Mine 로그가 위 함수를 쓴다. Take Mine 이 내용을 안 바꿔도 병합의 두 쪽이면 로그를 남긴다. `IsConflictThreeWay` · `ShowBranchCombo` · `ConflictSideText` |
| `Views/HomeView.xaml` | 충돌 네 칸이면 3-Way 콤보를 감추고 그 자리에 **"병합 충돌 · REMOTE = 상대 이름"** |
| 하네스 `MergeSaveTest` | 로그 검사 12건(충돌 Theirs · 삭제 · 이름 모름, 브랜치 3-Way 그대로, Mine 정함 · 되돌림 · 삭제, mergetool 파일 이름, 공백 이름) |
### 확인한 것 (같은 저장소)
| 확인 | 결과 |
|---|---|
| 충돌 네 칸 머리 | **`병합 충돌 · REMOTE = feature/theirs`**, 3-Way 콤보 안 보임. 화면 어디에도 "3-Way 안 씀" 없음 |
| Take Theirs | 로그 **`10행 Theirs(feature/theirs) 반영`** |
| Undo → Take Mine | 로그 **`10행 Mine(feature/mine) 으로 정함`** |
| 한쪽이 지운 충돌(`Recipe/ToBeDeleted.cs`, 두 칸) | 콤보가 다시 보임 — 네 칸이 아니므로 지금 그대로 |
| git | 충돌 26 그대로(저장 안 함) |
MergeSaveTest 82건 통과. 빌드 경고 0 · 오류 0. 설정 파일은 바이트 그대로 되돌림.
콤보로 브랜치를 고르는 3-Way 는 병합이 아닐 때만 쓰는 길이라 화면으로는 따로 안 봤다 — 문구가 그대로인 것은 하네스로 확인했다.
## B-1. 병합 커밋 뒤에도 `.git/MERGEHUB_CONFLICTS` 가 남는다 (2026-09-14, 완료)
### 고치기 전에 다시 났다 (새로 만든 저장소, MergeHub 로 병합)
| 때 | MERGE_HEAD | MERGEHUB_CONFLICTS |
|---|---|---|
| 병합 전 | 없음 | 없음 |
| MergeHub [Merge] 뒤 | 있음 | 있음 |
| [작업 취소] 뒤 | 없음 | **남음** |
| 다시 병합 → 터미널에서 풀고 커밋 → 앱 [새로고침] | 없음 | **남음** |
| (6차 D-1) MergeHub [병합 커밋] 뒤 | 없음 | **남음** |
### 원인
`GitService.RememberMergeConflicts` 가 병합 때 적기만 하고, 병합 커밋 · 작업 취소 · 상태 다시 읽기 어디에도 치우는 코드가 없었다.
첫 줄의 MERGE_HEAD 와 대조하므로(`GetMergeConflictTotal`) 다음 병합의 수가 틀리지는 않았다 - 남의 `.git` 에 우리 파일이 남는 것만 문제였다.
### 고친 것
| 파일 | 무엇 |
|---|---|
| `Services/MergeConflictRecord.cs` (새 파일) | `IsStale(MERGE_HEAD, 첫 줄)` — 병합 중이 아니거나, 빈 파일이거나, 첫 줄이 지금 MERGE_HEAD 와 다르면 쓸모없음 |
| `Services/GitService.cs` | `ForgetStaleMergeConflicts()` — 쓸모없으면 지운다. 잠김 등으로 못 지워도 다른 일을 막지 않는다. `CommitMerge` 뒤 · `AbortOperation`(작업 취소) 뒤에 부른다 |
| `Services/IGitService.cs` | 선언 |
| `Services/GitWorkspace.cs` | `ReadState` 처음에 부른다 — 새로고침 · 저장소 열기 · 모든 Git 작업 뒤. 터미널에서 끝낸 병합도 여기서 치운다 |
| 하네스 `MergeSaveTest` | `IsStale` 검사 6건(병합 아님 · 빈 MERGE_HEAD · 같은 병합은 둠 · 다른 병합 · 대소문자와 공백 · 빈 파일) |
### 확인한 것 (세 경우 모두 저장소를 새로 만들어)
| 경우 | 병합 중 | 끝난 뒤 |
|---|---|---|
| ① [Merge] → [작업 취소] | 파일 있음 · 진행 줄 "남은 충돌 24개 · 푼 것 2개 (2/26)" | MERGE_HEAD 없음 · **파일 없음** |
| ② [Merge] → git 으로 풀기만 → [새로고침] → [병합 커밋] | 다 풀고 새로고침해도 **파일 그대로** · "충돌 정리 끝 · 푼 것 26개" (병합 중에는 안 지운다) | 커밋 `5f9d5d9`(부모 둘) · **파일 없음** |
| ③ [Merge] → 터미널에서 풀고 커밋 → [새로고침] | 새로고침 전에는 파일 남음 | 새로고침 뒤 **파일 없음** |
MergeSaveTest 88건 통과. 빌드 경고 0 · 오류 0. 설정 파일은 바이트 그대로 되돌림.
## E-2. 폴더 비교 탭에 폴더를 하나씩 끌어다 놓으면 첫 칸 → 둘째 칸 차례로 (2026-09-14, 추가 요청, 완료)
사용자: "폴더비교 탭 첫번째 드래그 첫번째 폴더 경로에 들어가고 두번째는 드래그앤드롭은 두번째로 들어가게해줘 경로텍스트 박스에 안넣어도 그냥 화면에다 넣어둬 기능 동작하도록".
### 원인 (코드로 확인)
- 화면 아무 데나 놓으면 `FolderView.View_Drop` → `GitWorkspace.RouteDroppedPaths` → `RequestFolderCompare(왼쪽, null)`. **폴더 하나는 늘 첫 칸**으로 갔다.
두 번째 폴더를 놓아도 첫 칸을 덮어써서, 경로 칸 위에 정확히 놓지 않으면 두 폴더를 견줄 수 없었다.
- 경로 칸 위에 놓는 길(`PreviewDrop` → `DropIntoSlot`)은 그 칸에 제대로 들어갔다.
- 고치기 전 모습은 화면으로 따로 찍지 않았다 — 위 경로를 코드로 따라가 확인했다.
### 고친 것
| 파일 | 무엇 |
|---|---|
| `Services/DropSlotOrder.cs` (새 파일) | 놓는 차례. `Take()` 는 첫 칸 → 둘째 칸 → 첫 칸 … 을 주고, `Placed(칸)` 은 칸에 바로 놓은 것도 차례에 쳐서 다음을 반대 칸으로 |
| `Views/FolderView.xaml.cs` | 화면에 **폴더 하나**를 놓으면 `DropFolder(폴더, 차례 칸)` — 양쪽이 차면 바로 견준다(원래 있던 동작). 경로 칸 위에 놓으면 그 칸에 넣고 차례를 넘김(두 개를 한꺼번에 놓아 양쪽을 채웠으면 다음은 첫 칸). 두 개 · 파일을 놓는 길은 그대로(명령줄과 같은 판단) |
| 하네스 `PathTest` | 차례 검사 8건 |
### 확인한 것 (실제 탐색기 창에서 마우스로 끌어다 놓기)
경로 칸을 비우고, 탐색기에서 폴더를 끌어 **폴더 비교 화면의 빈 곳**(경로 칸이 아닌 곳)에 놓았다.
| # | 놓은 것 · 자리 | 첫 칸 | 둘째 칸 |
|---|---|---|---|
| 1 | `왼쪽폴더` · 화면 | **왼쪽폴더** | (빔) |
| 2 | `오른쪽폴더` · 화면 | 왼쪽폴더 | **오른쪽폴더** — 바로 비교, 결과에 `a.txt` |
| 3 | `세번째폴더` · 화면 | **세번째폴더** | 오른쪽폴더 |
| 4 | `왼쪽폴더` · **둘째 경로 칸 위** | 세번째폴더 | **왼쪽폴더** |
| 5 | `오른쪽폴더` · 화면 (차례: 첫 칸) | **오른쪽폴더** | 왼쪽폴더 |
PathTest 62건 통과. 빌드 경고 0 · 오류 0.
## E-3. 사용자 이름을 저장하기 전에는 화면을 쓸 수 없게 (2026-09-14, 추가 요청, 완료)
사용자: "사용자 이름을 저장하고 화면이 움직이거나 바뀌도록 잠궈야해". 저장해야만 화면을 움직이거나 바꿀 수 있게 잠근다고 읽었다.
켤 때 뜨는 이름 창은 **플랫폼**(`UbisamBase.Core/Settings/UserNameDialog`)의 것이다.
### 고치기 전에 쟀다 (켤 때 이름 창)
| 확인 | 결과 |
|---|---|
| 이름 창 주인 · 본 창 | 주인이 본 창, 본 창 입력 막힘 — 여기까지는 됐다 |
| 제목줄을 끌기 | **이름 창이 움직였다** (663,377 → 863,477) |
| 저장 없이 닫기(WM_CLOSE, Alt+F4 와 같은 길) | **이름 창이 닫히고 본 창을 그대로 쓸 수 있었다** — 설정 탭으로 옮겨짐. 이름은 안 받음 |
| 설정 폴더 | 바뀐 것 없음 |
### 고친 것 (플랫폼)
| 파일 | 무엇 |
|---|---|
| `UbisamBase.Core/Settings/UserNameDialog.xaml.cs` | `OnClosing` 에서 저장 전이면 닫기를 막고 "이름을 저장해야 시작합니다." 를 보인 뒤 이름 칸으로 포커스. 저장하면 닫힌다. 제목줄 끌어 옮기기(`DragMove`) 뺌 |
| `UbisamBase.Core/Settings/UserNameDialog.xaml` | 제목줄의 끌기 처리기 뺌 |
플랫폼 규칙대로 사용자 확인을 받고 `scripts\publish-platform.ps1` 로 `D:\UbisamPlatform\bin` 을 갱신했다(Core Release 경고 0 · 오류 0, 파일 48 · pdb 0).
MergeHub 는 빌드 때 그 폴더를 통째로 복사하므로 다시 빌드해 앱 폴더의 Core 에 새 문구가 들어간 것을 확인했다.
그 폴더를 쓰는 다른 소비 프로그램(UbisamBase.App · TestApp 등)도 다시 빌드하면 같은 잠금을 받는다 — 플랫폼 changelog 에 적었다.
### 확인한 것 (새 Core 로 앱을 켜서)
| 확인 | 결과 |
|---|---|
| 제목줄 끌기 | **움직이지 않음** |
| 저장 없이 닫기(WM_CLOSE) | **이름 창 그대로 · 본 창 입력 막힘 그대로**, 창에 "이름을 저장해야 시작합니다." |
| [저장] | 이름 창 닫힘 · 본 창 입력 가능 · 탭 옮기기 됨 |
| 설정 폴더 | 그날 로그 파일 말고 바뀐 것 없음(같은 이름으로 저장) |
MergeHub 코드는 바꾸지 않았다. 빌드 경고 0 · 오류 0.
## E-1. [해결로 저장] 이름을 [Save] 로 (2026-09-14, 추가 요청, 완료)
사용자: "해결로 저장은 LOCAL SAVE 버튼이잖아? 그냥 Save 로 이름 바꿔주고".
병합 중에만 보이는 그 단추는 LOCAL 칸 내용을 이 파일에만 쓰고 스테이징한다. 바로 아래 Save & Commit 과 짝이 되게 이름만 바꿨다. 하는 일은 그대로다.
### 바꾼 것
| 파일 | 무엇 |
|---|---|
| `Views/HomeView.xaml` | 단추 글자 `해결로 저장` → `Save` (툴팁 "이 파일만 풀어서 저장하고 스테이징합니다. 커밋은 하지 않습니다 (Ctrl+S)" 그대로) |
| `ViewModels/HomeViewModel.cs` | 작업 이름(`RunAsync`) · 알림 `Save · 파일 · 남은 충돌 N건` / `… 남은 충돌 없음 - 이제 커밋할 수 있습니다` |
| `Services/MergeSavePlan.cs` | Save & Commit 이 막힐 때 안내 "[Save] 로 이 파일만 저장해 두고 …" |
| `doc/web/index.html` | 매뉴얼 · 단축키 · 한계 · 시연 대본 · 비교표의 이름 |
지난 기록(history · 6차 지시서)은 그때 이름 그대로 두었다. 코드 주석의 옛 이름도 그 시점 설명이라 두었다.
### 확인한 것 (새로 만든 저장소, MergeHub 로 병합한 뒤)
| 확인 | 결과 |
|---|---|
| 병합 중 네 칸의 단추 | **Save** · 옆에 Save &amp; Commit. "해결로 저장" 단추 없음 · 화면 전체 글자에도 없음 |
| `Io/Unit12.cs` 충돌 1개 Take Theirs → [Save] | 알림 **"Save · Unit12.cs · 남은 충돌 23건"**. git 충돌 24 → 23, 파일 `M ` 스테이징, HEAD 그대로(커밋 없음) |
| 저장 뒤 | 남은 충돌 첫 파일(`Common/Util/Unit15.cs`)이 네 칸으로 열림 |
빌드 경고 0 · 오류 0.
**바로잡음 (A-1 때 발견)** — 처음에 "하네스는 이 파일들을 컴파일하지 않아 그대로" 라고 적었는데 틀렸다.
`MergeSaveTest` 가 `MergeSavePlan.cs` 를 컴파일하고, 막힘 안내에 "해결로 저장" 이 들어 있는지 보고 있었다.
찾을 때 하네스 폴더를 빠뜨렸고 하네스를 돌리지 않았다. A-1 에서 기대값을 `[Save]` 로 고치고 "옛 이름이 없다" 검사를 더했다.
설정 파일은 백업 → `CSharpBraceStyle` 끔 → 시험 → 바이트 그대로 되돌림(해시 같음).
(자동 조작 쪽: 앱을 새로 켠 뒤에는 충돌 목록에 고른 것이 없어 [3분할로 열기] 가 아무것도 안 열었다. 목록에서 파일을 고르고 다시 했다.)