Ubisam-MergeTool/doc/history/2026-09-14-7차.md
ksj09406 9bc47c0166 7차 D-1 기록: 네 길(병합 · pull · rebase · cherry-pick)을 처음부터 끝까지
코드 변경 없음. 새 시험 저장소에서 MergeHub 만으로 네 길 모두 커밋(rebase 끝)까지 갔다.
줄끝 · BOM 바뀐 파일 0, A-2 · B-1 다시 안 남, MERGED 줄 번호 안 잘림.

새로 걸린 것(고치지 않음 → 8차):
- N1 파일 비교 탭을 처음 여는 것이 [3분할로 열기] 면 첫 충돌 파일이 보이고,
  그 화면을 [Save] 하면 고른 파일에 그 내용이 써진다(Unit47 ← Unit15)
- N2 rebase · cherry-pick 마지막 저장 뒤 "남은 차이"(A-1 은 병합만)
- N3 MERGED 칸 번호가 결과 파일 번호가 아님
- N4 바뀌는 것 없는 cherry-pick 에서 [커밋] 이 켜져 실패

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 18:05:18 +09:00

503 lines
46 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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. 설정 파일은 바이트 그대로 되돌림.
## C-1. pull 이 낸 충돌에는 머지 주석 자동 풀기가 안 걸린다 (2026-09-14, 완료)
### 준비 — 원격을 흉내 낸 저장소
만든 저장소를 `git clone --bare` 로 떠서 `origin` 으로 달고, 그 bare 저장소의 `feature/mine` 을 `feature/theirs` 커밋으로 옮겼다.
내 `feature/mine` 이 `origin/feature/mine` 을 추적하게 하면 pull 이 [Merge] 와 **같은 병합 기준 · 같은 26개 충돌**을 낸다.
(bare 폴더는 지우지 않고 시각을 붙인 새 이름으로 만들었다.)
### 고치기 전에 다시 났다
| 확인 | [Merge] (6차 D-1) | **Pull** |
|---|---|---|
| 알림 | 충돌 24건 (머지 주석만 다른 2건은 자동으로 풀림) … | **없음** |
| 로그 | 충돌 26건 · 자동 해결 두 줄 · "자동으로 풀었습니다. 남은 충돌 24건." | "충돌 26건 - 해결이 필요합니다." 뿐 |
| git 충돌 | 24 | **26** (주석만 다른 2개도 남음) |
| 진행 줄 | 남은 충돌 24개 · 푼 것 2개 (2/26) | **남은 충돌 26개 · 푼 것 0개 (0/26)** |
| Git 탭 커밋 메시지 칸 | Merge branch 'feature/theirs' into feature/mine | **비어 있음** |
| 병합 커밋 제목 | - | `Merge branch 'feature/theirs' into feature/mine` — 받아 온 `origin/feature/mine` 이 아니라 같은 커밋을 가리키는 **로컬 브랜치** 이름 |
### 원인
- `GitViewModel.PullAsync` 는 `Git.Pull` 만 불렀다. 머지 주석 자동 풀기(`ResolveNoteOnlyConflicts`) · 알림 · 커밋 메시지 기본값은 모두 `MergeAsync` 안에만 있었다.
- 메시지 칸이 비면 병합 커밋이 `CleanMergeMessage` 로 상대 이름을 찾는데, 들어오는 커밋을 가리키는 **로컬 브랜치를 먼저** 본다. 이번 준비처럼 같은 커밋을 가리키는 로컬 브랜치가 있으면 그 이름이 들어간다.
### 고친 것
| 파일 | 무엇 |
|---|---|
| `ViewModels/GitViewModel.cs` | `MergeAsync` 의 뒷일을 `AfterMerge(결과, 기본 메시지)` 로 빼고 `PullAsync` 도 부른다. 기본 메시지는 병합 커밋이 남을 때(충돌 · 병합됨)만 채운다 — 이미 최신 · 앞으로 감기면 채우지 않는다 |
| `Services/MergeNames.cs` | `PullMergeMessage(추적 브랜치, 내 브랜치)` — git 과 같은 `Merge remote-tracking branch 'origin/x' into y`. 추적 브랜치를 모르면 `Merge into y` |
| 하네스 `MergeSaveTest` | pull 메시지 검사 5건(모양 · 공백 · 추적 모름 · 내 브랜치 모름 · 다시 읽으면 원격 브랜치 이름) |
추적 브랜치 이름은 작업에 들어가기 전에 화면 스레드에서 읽는다(브랜치 목록이 화면의 것이다).
### 확인한 것 (저장소 · bare 원격 새로 만들어)
| 확인 | ① Pull | ② [Merge] (회귀) |
|---|---|---|
| 알림 | **충돌 24건 (머지 주석만 다른 2건은 자동으로 풀림) - 충돌 탭에서 …** | 같음 |
| 로그 | fetch → 병합 시작 `origin/feature/mine -> feature/mine` → 충돌 26건 → 자동 해결 `Unit46.cs` · `Unit45.cs` → 남은 충돌 24건 | 병합 시작 `feature/theirs -> feature/mine` 부터 같음 |
| git 충돌 | **24** | 24 |
| 진행 줄 | **남은 충돌 24개 · 푼 것 2개 (2/26)** | 같음 |
| 커밋 메시지 칸 | **Merge remote-tracking branch 'origin/feature/mine' into feature/mine** | Merge branch 'feature/theirs' into feature/mine |
| 병합 커밋 (git 으로 풀고 새로고침 → [병합 커밋]) | `7280418` 부모 둘 · **Merge remote-tracking branch 'origin/feature/mine' into feature/mine** | - |
MergeSaveTest 93건 통과. 빌드 경고 0 · 오류 0. 설정 파일은 바이트 그대로 되돌림.
## C-2. rebase · cherry-pick 충돌을 네 칸으로 끝까지 (2026-09-15, 완료)
### 준비
시험 저장소(`C1Repo`)를 그대로 썼다. `feature/mine`(내 쪽 작업) · `feature/theirs`(상대 쪽 작업)가 같은 바탕에서 커밋 하나씩이라
**rebase**(`feature/mine` 을 `feature/theirs` 위로)도 **cherry-pick**(상대 쪽 작업을 `feature/mine` 에)도 병합과 같은 26개 충돌을 낸다.
지시서는 생성기에 갈래를 더하라고 했지만 필요 없어서 바꾸지 않았다.
### 고치기 전에 다시 났다
**rebase** (Git 탭 [선택 브랜치 위로 rebase])
| # | 무엇 | 근거 |
|---|---|---|
| R1 | 네 칸 제목이 두 쪽을 말하지 않는다. rebase 에서는 LOCAL 이 **올릴 자리(`feature/theirs`)**, REMOTE 가 **다시 올리는 내 커밋**인데 | LOCAL `feature/theirs` · REMOTE **`REMOTE · THEIRS`** · 머리 글 **`병합 충돌 · REMOTE = THEIRS`** |
| R2 | Take Theirs 가 내 커밋 줄을 가져오는데 화면은 그 사정을 모른다 | `Common/Util/Unit15.cs` Take Theirs → `feature/mine` 에만 있는 줄 1/1 들어감 |
| R3 | 로그가 코드 이름을 적는다 | `rebase 1/1 단계에서 충돌 26건 - 해결 후 RebaseContinue() 를 호출하세요.` |
| R4 | 머지 주석 자동 풀기가 안 걸린다 | 충돌 26(병합은 24) |
| - | 끝내는 길은 됐다 | 다 풀자 [계속] 켜짐 · [병합 커밋] 꺼짐, [계속] → `rebase 완료 (1/1 단계)`, 이력 `내 쪽 작업` 이 `상대 쪽 작업` 위에 |
**cherry-pick**
| # | 무엇 | 근거 |
|---|---|---|
| C1 | **다른 브랜치의 커밋을 고를 수 없다.** Git 탭 이력은 HEAD 에서 닿는 커밋만 보인다 | 이력 목록에 `상대 쪽 작업` 없음. `GetCommits` 가 시작점 없이 `CommitFilter` 를 써서 libgit2 기본(HEAD)만 |
| C2 | 이미 있는 커밋(HEAD)을 cherry-pick 하면 `CHERRY_PICK_HEAD` 가 남는데 **진행 표시도 [작업 취소] 도 없다** — 빠져나올 길이 없다 | 로그 `cherry-pick 적용됨 - 확인 후 Commit() 을 호출하세요.` · 머리 표시 없음 · [작업 취소] 꺼짐 |
| C3 | 충돌 중 머리가 **"병합 진행 중"**, REMOTE `REMOTE · THEIRS`, 머리 글 `병합 충돌 · REMOTE = THEIRS` | git 으로 cherry-pick 을 시작해 앱으로 연 화면 |
| C4 | 충돌을 다 풀면 **진행 표시 · [작업 취소] · [병합 커밋] 이 모두 사라진다.** 끝내려면 변경 · 커밋 탭에서 메시지를 직접 쳐야 한다(원래 메시지 안 채워짐) | 다 푼 뒤 머리 표시 없음 · 작업 취소 꺼짐 · 병합 커밋 꺼짐. 메시지를 쳐서 [커밋] 하면 `CHERRY_PICK_HEAD` 는 치워짐 |
### 원인
- 화면이 충돌을 모두 "병합" 으로만 다뤘다. 진행 중인지는 `IsMergeInProgress`(병합 표시 **또는 충돌 있음**)로만 봐서, cherry-pick 은 충돌이 0 이 되면 진행 중이 아니게 됐다.
- 상대 이름은 MERGE_HEAD 로만 찾았다(`GetMergeTheirsName`) — rebase · cherry-pick 에는 MERGE_HEAD 가 없어 "THEIRS".
- 머지 주석 자동 풀기 · 알림은 [Merge] · pull 에만 걸려 있었다(7차 C-1).
### 고친 것
| 파일 | 무엇 |
|---|---|
| `Models/GitModels.cs` | `GitOperationKind`(None · Merge · Rebase · CherryPick · Revert) · `GitOperationState`(두 쪽 이름 · 들어오는 커밋 · 단계) |
| `Services/OperationLabels.cs` (새 파일) | 작업별 글 — 진행 표시(`rebase 1/1` · `cherry-pick 진행 중`), LOCAL 제목(rebase 는 `올릴 자리 feature/theirs`), REMOTE 제목(`다시 올리는 내 커밋 …` · `가져오는 커밋 …` · `되돌리는 커밋 …`), 네 칸 머리 글, 로그 이름, 끝내기 단추(병합 커밋 · 계속 · 커밋), 진행 줄(병합 글은 예전 그대로) |
| `Services/GitService.cs` | `GetOperationState` — git 작업 표시로 판단: rebase 는 `rebase-merge/onto` · `head-name` · 지금 단계 커밋, cherry-pick · revert 는 `CHERRY_PICK_HEAD` · `REVERT_HEAD`. `GetPendingCommitMessage`(원래 메시지, # 줄 뺌). `CommitPendingOperation` — cherry-pick 은 git 처럼 **원래 작성자**, 커밋한 사람은 지금 사용자. `GetCommits(allBranches)`. 로그의 `Commit()` · `RebaseContinue()` 문구를 [커밋] · [계속] 으로 |
| `Services/GitWorkspace.cs` | `OperationState` · `IsOperationInProgress`(충돌 수와 따로) · `ShowAllBranchesInHistory`. 진행 표시 · 진행 줄이 작업을 따른다 |
| `ViewModels/GitViewModel.cs` | 뒷일(`AfterConflicts`)을 병합 · pull · rebase 시작 · rebase 계속 · cherry-pick · revert 가 함께 쓴다 — 머지 주석 자동 풀기 · 알림("다 풀면 [계속] 으로 …") · cherry-pick · revert 메시지 채우기. `FinishOperationCommand` — 병합 커밋 / 계속 / 커밋. [작업 취소] 가 진행 중인 작업이면 켜짐 |
| `Views/GitView.xaml` | 충돌 탭 끝내기 단추 글자 · 명령이 작업을 따른다. 진행 줄은 `IsOperationInProgress` 로 보인다. 이력 탭에 **[모든 브랜치]** 체크(사용자 결정, 기본 꺼짐) |
| `ViewModels/HomeViewModel.cs` | 충돌 파일을 열 때 작업 상태로 LOCAL · REMOTE 제목 · 머리 글 · Take 로그 이름을 정한다. 병합 커밋 메시지 자동 채우기는 병합일 때만 |
| 하네스 `MergeSaveTest` | 작업별 글 검사 32건(진행 표시 · 칸 제목 · 머리 글 · 로그 이름 · 끝내기 단추 · 진행 줄, 병합 글이 예전과 같은지) |
### 확인한 것 (모두 저장소를 새로 만들어 MergeHub 만으로)
**① rebase**
| 때 | 결과 |
|---|---|
| [선택 브랜치 위로 rebase] | 알림 **`충돌 24건 (머지 주석만 다른 2건은 자동으로 풀림) - … 다 풀면 [계속] 으로 rebase 를 이어 갑니다.`** · 로그 `… 충돌 26건 - 풀고 나서 [계속] 을 누르세요.` + 자동 해결 두 줄 |
| 충돌 중 | 머리 **`rebase 1/1`** · [작업 취소] 켜짐 · 진행 줄 `남은 충돌 24개` · 끝내기 단추 **[계속]**(꺼짐) |
| 네 칸 (`Unit15.cs`) | LOCAL **`올릴 자리 feature/theirs`** · REMOTE **`REMOTE · 다시 올리는 내 커밋 a0fead2 내 쪽 작업`** · 머리 글 `rebase 충돌 · LOCAL = 올릴 자리 feature/theirs · REMOTE = 다시 올리는 내 커밋 a0fead2` · "3-Way 안 씀" 없음 |
| Take Theirs → Save | 로그 **`10행 Theirs(커밋 a0fead2) 반영`** |
| 23개 파일 단위 Accept Theirs · `Io/LineEnding.cs` 는 네 칸에서 Take Theirs → Save | 진행 줄 **`충돌 정리 끝 - [계속] 으로 rebase 를 이어 갑니다`** · [계속] 켜짐 |
| [계속] | `rebase 완료 (1/1 단계)` · 진행 표시 · [작업 취소] 사라짐. 이력 `c1296a7 내 쪽 작업` ← `1bdc05b 상대 쪽 작업` ← 바탕 |
| 결과 커밋 (부모와 바이트로) | 바뀐 파일 32 · **줄끝 · BOM · 인코딩이 부모와 다른 파일 0** · 충돌 표식 0 |
**② cherry-pick**
| 때 | 결과 |
|---|---|
| 이력 [모든 브랜치] | 끄면 `상대 쪽 작업` 없음 → 켜면 보임, 골라 [Cherry-pick] |
| 시작 | 알림 `충돌 24건 (…) - … 다 풀면 [커밋] 으로 cherry-pick 을 끝냅니다.` · 로그 `cherry-pick 충돌 26건 - 풀고 나서 충돌 탭의 [커밋] 으로 끝냅니다.` + 자동 해결 |
| 충돌 중 | 머리 **`cherry-pick 진행 중`** · [작업 취소] 켜짐 · 끝내기 단추 **[커밋]**(꺼짐) |
| 네 칸 | LOCAL `feature/mine` · REMOTE **`REMOTE · 가져오는 커밋 0862180 상대 쪽 작업`** · 머리 글 `cherry-pick 충돌 · REMOTE = 가져오는 커밋 …` · 로그 `10행 Theirs(커밋 …) 반영` |
| 23개 Accept Theirs(24 → 1) · `LineEnding.cs` 네 칸 → Save | 진행 줄 **`충돌 정리 끝 - [커밋] 으로 cherry-pick 을 끝냅니다`** · [커밋] 켜짐 · 진행 표시 그대로 |
| [커밋] (메시지 칸 비움) | `cherry-pick 커밋: ebe2a64 상대 쪽 작업` — **원래 메시지**, 부모 하나, `CHERRY_PICK_HEAD` 없어짐, 진행 표시 · [작업 취소] 사라짐 |
| 결과 커밋 | 바뀐 파일 32 · **줄끝 · BOM · 인코딩이 부모와 다른 파일 0** · 충돌 표식 0 · `LineEnding.cs` BOM · CRLF 그대로 |
**③ 바뀌는 것 없는 cherry-pick (HEAD 커밋을 다시)**
| 때 | 결과 |
|---|---|
| [Cherry-pick] | `CHERRY_PICK_HEAD` 생김 · 머리 **`cherry-pick 진행 중`** · **[작업 취소] 켜짐** (예전에는 둘 다 없었다) |
| [작업 취소] | `CHERRY_PICK_HEAD` 없어짐 · 표시 사라짐 |
MergeSaveTest 125건 · EncTest 48 · FolderTest 62 · ReportTest 31 · SearchTest 63 통과(`GitModels.cs` 를 함께 컴파일). 빌드 경고 0 · 오류 0. 설정 파일은 바이트 그대로 되돌림.
### 확인하며 알게 된 것 · 남긴 것
- **파일 단위 Accept 는 고른 쪽 파일을 바이트째 쓴다.** 상대가 줄끝을 CRLF → LF 로 바꾼 `Io/LineEnding.cs` 를 파일 단위로 Accept Theirs 하면 그 줄끝이 그대로 들어온다(처음 확인에서 1개로 잡힘). 결함이 아니라 고른 대로다 — 6차 D-1 처럼 이런 파일은 네 칸에서 풀면 내 쪽(rebase 는 올릴 자리) 줄끝을 따른다.
- **원래 작성자 남기기는 이 저장소로 구별되지 않았다.** 시험 저장소의 모든 커밋 작성자가 같다(`시험`). 코드로만 확인했다.
- **③ 에서 [커밋] 도 켜진다.** 바뀐 것이 없어 누르면 빈 커밋 오류가 날 것으로 보인다. 로그 `적용됨 - 확인한 뒤 [커밋] 으로 끝냅니다` 도 이 경우엔 맞지 않다. 고치지 않고 적어 둔다(D-1 에서 다시 본다).
- revert 충돌은 같은 길(`AfterConflicts` · `CommitPendingOperation`)을 쓰지만 화면으로 끝까지는 안 봤다.
- 자동 조작 쪽: cherry-pick 첫 확인에서 [모든 브랜치] 를 켠 직후 목록 선택이 앱에 안 닿아 Accept 가 한 개만 풀렸다. 기다림을 넣고 다시 해 24 → 1 로 확인했다.
## D-1. 네 길을 처음부터 다시 끝까지 (2026-09-15, 완료)
지시서대로 **고치지 않고 적기만 했다.** 제품 코드는 한 줄도 안 바꿨다.
### 무엇으로 했나
- 길마다 시험 저장소를 `C1Repo` 로 새로 만들었다(실제 소스는 열지 않음). 충돌 26개(머지 주석만 다른 2개 포함) — 네 길 모두 같다.
- pull 은 저장소를 `git clone --bare` 로 떠 원격으로 달고, 그 원격의 `feature/mine` 을 상대 쪽 커밋으로 옮겨 추적하게 했다(7차 C-1 과 같은 준비).
- 네 길 모두 **같은 순서로 풀었다**(6차 D-1 과 같은 고르기):
1. 충돌 탭에서 같은 줄 충돌 15개(Unit10~24)를 골라 Accept Theirs 한 번
2. `Ui/Views/NewPanel.cs`(양쪽 새 파일)에 Accept Both → 묻는 창 [아니오]
3. [3분할로 열기] → 저장하면 저절로 열리는 다음 파일마다 [다음 충돌] + Take Theirs / Take Mine 번갈아 → [Save].
지운 파일 띠는 `ToBeDeleted` [파일 지우기] · `ToBeDeletedByMe` [파일 남기기]. 첫 파일에서 충돌이 남은 채 Save & Commit 을 한 번 누름
4. 끝내기 단추(병합 커밋 · 계속 · 커밋)
- 설정 `CSharpBraceStyle` 은 길마다 백업 → 끔 → 시험 → 원래 바이트로 되돌림(네 번 모두 해시 같음). 창 기억 파일은 "기억 없음" 으로 되돌림.
- 확인 값은 git 과 커밋 blob 바이트로 읽었다. 결과 커밋의 파일마다 BOM · CRLF · LF · UTF-8 을 첫 부모와 견주고, 충돌 표식을 찾고,
**`UnitN.cs` 안의 class 이름이 `UnitN` 인지**도 봤다(아래 N1 을 찾은 뒤 더한 검사).
### 네 길의 결과
| 확인 | ① 병합 | ② pull | ③ rebase | ④ cherry-pick |
|---|---|---|---|---|
| 시작 | [Merge → 현재 브랜치로] | 원격 [펴기] → [Pull] | [선택 브랜치 위로 rebase] | 이력 [모든 브랜치] → `상대 쪽 작업` [Cherry-pick] |
| 알림 | 충돌 24건 (머지 주석만 다른 2건은 자동으로 풀림) … | 같음 | 같음 + "다 풀면 [계속] 으로 rebase 를 이어 갑니다" | 같음 + "다 풀면 [커밋] 으로 cherry-pick 을 끝냅니다" |
| 머리 표시 · 진행 줄 | 병합 진행 중 · 남은 충돌 24개 · 푼 것 2개 (2/26) | 같음 | rebase 1/1 · 남은 충돌 24개 | cherry-pick 진행 중 · 남은 충돌 24개 |
| 파일 단위 Accept Theirs 15개 | 24 → 9, 1.0초 | 같음 | 같음 | 같음 |
| Accept Both | 먼저 묻는다 · [아니오] → git 그대로 `AA` | 같음 | 같음 | 같음 |
| 충돌 남은 채 Save & Commit | 눌림 · git 그대로 · 커밋 없음 | 같음 | 꺼져 있음 | 꺼져 있음 |
| 네 칸 REMOTE 제목 | `REMOTE · feature/theirs` | 같음 | `REMOTE · 다시 올리는 내 커밋 bf67020 내 쪽 작업` | `REMOTE · 가져오는 커밋 7fac93b 상대 쪽 작업` |
| 네 칸 머리 글 | 병합 충돌 · REMOTE = feature/theirs | 같음 | rebase 충돌 · LOCAL = 올릴 자리 feature/theirs · REMOTE = 다시 올리는 내 커밋 bf67020 | cherry-pick 충돌 · REMOTE = 가져오는 커밋 7fac93b |
| 3분할로 푼 파일(앱이 연 순서) | NewPanel · Unit47 · 한글설명 · LineEnding · Unit0(6) · 띠 둘 · View250 · Unit1(6) | 같음 | 같음 | 같음 |
| 줄 고른 로그 | 18줄 · `Theirs(feature/theirs)` · `Mine(feature/mine)` | 같음 | 18줄 · `Theirs(커밋 bf67020)` · `Mine(feature/theirs)` | 18줄 · `Theirs(커밋 7fac93b)` · `Mine(feature/mine)` |
| 마지막 Save 뒤 파일 비교 탭 | **충돌 정리 끝 · 병합 커밋을 할 수 있습니다 · 내 쪽 대비 받은 변경 3줄** · `… · 병합 전 내 쪽` | 같음 | **`2680b84 · Weld/Unit1.cs` · 남은 차이 3줄** (N2) | **`ecb7e47 · Weld/Unit1.cs` · 남은 차이 3줄** (N2) |
| 다 푼 뒤 충돌 탭 | 충돌 정리 끝 · 푼 것 26개 - 병합 커밋을 할 수 있습니다 · [병합 커밋] 켜짐 | 같음 · 메시지 칸 `Merge remote-tracking branch 'origin/feature/mine' into feature/mine` | 충돌 정리 끝 - [계속] 으로 rebase 를 이어 갑니다 · [계속] 켜짐 | 충돌 정리 끝 - [커밋] 으로 cherry-pick 을 끝냅니다 · [커밋] 켜짐 |
| 끝낸 커밋 | `fce391f` 부모 둘 · `Merge branch 'feature/theirs' into feature/mine` · 본문 없음 | `c70884c` 부모 둘 · `Merge remote-tracking branch 'origin/feature/mine' into feature/mine` | `rebase 완료 (1/1 단계)` · `e0302c2 내 쪽 작업` ← `2680b84 상대 쪽 작업` ← 바탕 | `17bbf0b` 부모 하나 · `상대 쪽 작업`(원래 메시지) |
| 줄끝 · BOM · 인코딩이 첫 부모와 다른 파일 | **0 / 33** | **0 / 33** | **0 / 31** | **0 / 33** |
| 충돌 표식 · class 이름이 다른 Unit 파일 | 0 · 0 | 0 · 0 | 0 · 0 | 0 · 0 |
| 끝난 뒤 | 워킹트리 Clean · 진행 표시 · [작업 취소] 사라짐 · `.git` 에 MERGE_HEAD · MERGEHUB_CONFLICTS 없음 | 같음 | 같음 · rebase-merge 없음 | 같음 · CHERRY_PICK_HEAD 없음 |
### 판정 기준
| 기준 | 결과 |
|---|---|
| 네 길 모두 MergeHub 만으로 커밋(또는 rebase 끝)까지 | **됐다.** 터미널 git 은 저장소 · 원격 준비와 결과 확인에만 썼다 |
| 줄끝 · BOM 이 바뀐 파일 0개 | **0.** 네 길 모두 |
| A-1 이 다시 나지 않는다 | **병합 · pull 은 안 났다. rebase · cherry-pick 에서는 같은 증상이 난다**(N2) — A-1 은 "병합 중" 일 때만 말을 바꾸도록 고쳤다 |
| A-2 가 다시 나지 않는다 | **안 났다.** 네 길 로그 72줄에 "3-Way 안 씀" 0, 네 칸 머리에 3-Way 콤보 없음 |
| B-1 이 다시 나지 않는다 | **안 났다.** 병합 커밋 · pull 병합 커밋 뒤 `.git/MERGEHUB_CONFLICTS` 없음 |
| #12 MERGED 칸 줄 번호 (`Motion/Unit0.cs` · `Weld/Unit1.cs` 끝 줄) | **잘리지 않는다.** 네 칸 화면에서 이동 칸에 끝 줄 번호를 넣고 찍음 — 3346 ~ 3363, 3770 ~ 3787 네 자리가 다 보인다. 다만 번호가 결과 파일 번호와 다르다(N3) |
### 새로 걸린 것 (고치지 않음 → 8차)
| # | 무엇 | 근거 |
|---|---|---|
| **N1** | **고른 충돌 파일 대신 다른 파일이 보이고, 그 화면을 [Save] 하면 고른 파일에 써진다.** 파일 비교 탭을 한 번도 열지 않은 채 충돌 탭에서 파일을 고르고 [3분할로 열기] 를 누르면 화면은 첫 충돌 파일(`Common/Util/Unit15.cs`)을 보인다. 거기서 Take Theirs → [Save] 하자 **`Common/Util/Unit47.cs` 가 Unit15 의 내용(123줄 · `class Unit15` · 고른 줄 · 머지 주석)으로 스테이징**됐고, Unit15 는 그대로 `UU`. 로그는 "충돌 해결(직접 편집 …): Common/Util/Unit47.cs". 화면은 30초 동안 Unit15 · "남은 충돌 1개" 그대로 | 병합 · pull 둘 다 같은 순서로 다시 났다(스크린샷 · 스테이징된 blob). 파일 비교 탭을 먼저 한 번 열어 두면 안 난다 — 네 길 확인은 그렇게 했다. 짚이는 곳: 탭이 처음 만들어질 때의 목록 세우기(첫 파일 로드)와 `OpenConflictAsync`(고른 파일)가 겹친다. `LoadFileAsync` 는 늦게 끝난 로드를 `IsGitMode` 로만 버리고 **읽는 사이 고른 파일이 바뀌었는지는 보지 않는다**(`HomeViewModel.cs:1291`). 코드를 읽어 짚은 것이라 원인은 확정하지 않았다. **남의 파일을 조용히 덮어쓰므로 8차에서 가장 먼저** |
| N2 | rebase · cherry-pick 에서 마지막 [Save] 뒤 파일 비교 탭이 두 칸으로 떨어져 **"남은 차이 3줄"** 을 보인다. 충돌 탭은 "충돌 정리 끝 - [계속] (또는 [커밋]) …" 이라 두 화면이 다른 말을 한다 — A-1 과 같은 증상 | `Weld/Unit1.cs` 저장 직후. A-1 의 `ResolvedMergeSummary` · "병합 전 내 쪽" 은 `IsMergeInProgress`(병합 표시) 일 때만 쓰인다 |
| N3 | MERGED 칸 줄 번호가 **결과 파일 번호가 아니라 원래 내 쪽 번호로 보인다.** `Motion/Unit0.cs` 는 고른 줄 · 주석으로 3줄이 늘어 상태바는 "3366행 · 총 3366줄" 인데 MERGED 칸 끝 번호는 3363 이고 줄마다 LOCAL 칸과 같은 번호 | 네 칸 스크린샷. MERGED 칸 번호는 `LocalNumberText`(정렬 때 정한 번호)라 편집 뒤 다시 매기지 않는 것으로 보인다. 설계일 수도 있어 8차에서 정한다 |
| N4 | 바뀌는 것 없는 cherry-pick(HEAD 커밋을 다시)에서 **[커밋] 이 켜지고, 누르면 로그에만 "[cherry-pick 커밋] 실패: No changes; nothing to commit."** 이 남는다. 진행 표시 · 진행 줄 "충돌 정리 끝 - [커밋] 으로 cherry-pick 을 끝냅니다" 가 그대로라 같은 단추를 또 누르게 된다. [작업 취소] 로는 빠져나온다 | C-2 에서 적어 둔 것을 다시 봤다. 알림 문장은 자동 조작이 못 읽었다 |
### 확인하며 알게 된 것 (제품 결함 아님)
- **사용자 이름 창.** 켤 때마다 뜨는 이름 창(플랫폼 E-3)이 떠 있어도 UI 자동화는 뒤의 본 창 단추를 누를 수 있다. 첫 병합 확인은 창을 닫지 않은 채 돌아 **사람이 갈 수 없는 길**이었다. 창을 [저장] 으로 닫고 병합을 다시 했다(위 표 ①). 사람의 마우스 · 키보드는 막히므로 E-3 결함은 아니다.
- **Pull 단추**는 접힌 원격 영역 안에 있다(설정 `GitRemoteExpanded` 꺼짐). 첫 pull 확인은 단추를 못 찾아 멈췄다 — 사람처럼 [▸ 원격 저장소 · 계정 펴기] 를 누르게 했다.
- 한 번 멈춘 pull 확인에서 N1 을 처음 봤다. 그때 [Save] 가 Unit47 을 Unit15 내용으로 덮었고, 끝낼 수 없어 그 저장소는 버렸다.
- 줄끝 검사는 N1 같은 **내용 뒤바뀜을 못 잡는다**(Unit15 · Unit47 둘 다 BOM · CRLF). 이번부터 class 이름 검사를 더했다.
- `Recipe/ToBeDeletedByMe.cs` 를 남기면 git 상태가 `R`(이름 바뀜)로 읽힌다 — 같은 크기의 `ToBeDeleted.cs` 를 지운 것과 짝지어 보는 git 의 이름 추적이다. 커밋 결과는 각각 지움 · 남김으로 맞다.
- 결과 확인 스크립트가 부모 하나인 커밋(rebase · cherry-pick)에서 merge-base 오류를 냈지만 줄끝 판정은 첫 부모와 제대로 했다.
코드는 바꾸지 않았으므로 검사는 그대로 · 빌드 경고 0 · 오류 0.
## E-4. 창 크기를 마음대로 조절할 수 있게 (2026-09-15, 추가 요청, 완료 · 플랫폼)
사용자: "현재는 화면크기 최대화만 지원인데 이거 크기를 마음대로 조절할수있게 할수있겠어?" — 하는 중에 두 가지를 더 정했다.
- "화면크기 최대화하면 작업표시줄 안보이게 화면을 채워줘" → 지시서의 "작업 표시줄을 덮지 않는다" 를 **덮는다** 로 바꿈.
- "화면 크기를 조절할때 엄청 깜빡거려 깜빡거리지 않게 해줘" → 깜빡임 없애기를 기준에 더함.
고친 곳은 모두 **플랫폼 셸 창**(`UbisamBase.Core/Shell`)이다. MergeHub 코드는 바꾸지 않았다.
### 고치기 전에 쟀다 (2560×1600@150% · 1920×1080@100% 두 모니터)
| 확인 | 결과 |
|---|---|
| 켠 직후 | 최대화 2560×1600, 크기 조절 테두리(WS_THICKFRAME) **없음** |
| 브랜딩 바 두 번 누르기 | 아무 일 없음 |
| 브랜딩 바 끌기 | 끄는 동안 1792×1069 로 줄었다가 **놓으면 다시 최대화** |
| Win+↓ | 2560×1528 "보통" 창 — 작업 영역만 하고 크기를 바꿀 수 없음 |
### 고친 것 (플랫폼)
| 파일 | 무엇 |
|---|---|
| `Shell/ShellWindow.xaml` | `ResizeMode=CanResize` · 최소 1280×720 · `WindowChrome`(끌어 크기 조절 테두리 6px, 제목 영역 0, 유리 틀 아래 1px) · 브랜딩 바 끌기 처리기 |
| `Shell/ShellWindow.xaml.cs` | 두 번 누르기 = 최대화 ↔ 복원 · 보통 크기에서 끌면 크기 그대로 옮김(놓아도 다시 최대화하지 않음) · 최대화에서 끌면 **실제로 움직일 때** 복원 크기로 줄여 따라옴 · 화면 이동은 보통 크기면 DIP 크기와 상대 위치를 지키고 최대화면 그 모니터를 채움 · Win+↓ 로 작업 영역만 한 창이 남으면 복원 크기로 · 마지막 크기 · 위치 · 최대화 여부 기억(창을 보이기 전에 복원) · **최대화 동안 크기 조절 틀을 떼고** 보통 크기로 돌아오면 다시 붙임 · 창 그리기 바탕을 앱 배경색으로 |
| `Shell/MonitorLayout.cs` | 창 사각형 읽기 · `WM_GETMINMAXINFO` — 최대화 = 그 모니터 전체(작업 표시줄 포함), 최대 끌기 크기도 그 모니터로, 최소 끌기 크기 |
| `Shell/WindowPlacementMath.cs` (새 파일) | 최소 크기(화면이 작으면 작업 영역에 맞춤) · 복원 크기(기억 없으면 작업 영역 80%) · 커서 아래로 줄이기 · 모니터 사이 옮기기(배율 비) · 작업 영역 안으로 · 작업 영역을 덮는지 · 기억한 자리가 지금 모니터에 보이는지 |
| `Shell/ShellWindowPlacement.cs` (새 파일) | `{설정 폴더}\ShellWindowPlacement.json` — UI 설정 파일과 따로(그쪽은 [저장] 을 눌러야 씀) |
| `Settings/UiSettingsService.cs` | `SettingsDirectory` |
| 하네스 `PlacementTest` (새로 만듦) | 계산 · 기억 파일 검사 37건 |
### 확인하며 찾아 고친 것
| # | 무엇 | 어떻게 알았나 · 고침 |
|---|---|---|
| 1 | **최대화하면 작업 표시줄이 창 위에 떠 하단 탭 바를 가림** — [화면 이동] · [종료] 가 안 눌림(사용자도 지적) | 그 자리의 창이 `Shell_TrayWnd`. 예전 셸과 창 크기 · 스타일 비트가 **같았고**, 다른 것은 크기 조절 틀(WindowChrome) 하나였다. 최대화 중 테두리만 떼기 · 작업 표시줄에 전체 화면이라고 알리기(`MarkFullscreenWindow`)는 효과가 없어 되돌렸고, **최대화 동안 틀을 떼자** 두 모니터 모두 앱 창이 받았다 |
| 2 | **크기를 바꿀 때 창이 통째로 검게 깜빡임**(사용자 지적) | 오른쪽 가장자리를 끄는 동안 40장 찍어 검은 픽셀 비율: 고치기 전 **최대 99.7%** → 창 그리기 바탕을 앱 배경색으로 + 유리 틀 1px → **최대 3.0%(파일 비교) · 2.0%(폴더 비교), 절반 넘게 검은 장 0**. 끌지 않을 때는 0% |
| 3 | 기억한 보통 크기로 켰는데 Windows 는 "최대화" 로 봄 | XAML 의 최대화가 복원보다 늦게 걸림 → 창을 보이기 전(생성자)에 보통 상태로 정함 |
| 4 | 보통 크기로 한 번도 안 쓰고 옆 모니터에서 최대화로 끄면 다음에 주 모니터로 켜짐 | 그 모니터의 복원 자리를 함께 기억 |
| 5 | 최대화로 넘어가는 순간의 모니터만 한 크기가 "보통 크기" 로 기억됨 | 작업 영역을 거의 덮는 크기는 기억하지 않음 |
| - | (측정 쪽) 옆 모니터 창이 1.5배 크게 재짐 | 앱은 모니터별 배율 인식(2)인데 측정한 PowerShell 이 시스템 배율(1)이라 좌표가 부풀려졌다. 측정 스레드를 모니터별 배율로 바꿔 다시 쟀다 |
### 확인한 것 (실제 픽셀, 배포 전 앱 폴더에 새 Core 를 넣어서)
| 확인 | 결과 |
|---|---|
| 켠 직후(기억 없음) | 최대화 2560×1600 = 모니터 전체 · 테두리 없음 · 하단 바를 앱 창이 받음 |
| 두 번 누르기 | 보통 2048×1222(작업 영역 80%, 가운데) ↔ 최대화 |
| 가장자리 · 모서리 끌기 | 가로 1920px · 세로 1080px(= **1280×720 DIP**)에서 멈춤 · 모서리로 키우기 됨 |
| 보통 크기에서 브랜딩 바 끌기 | **크기 그대로** 자리만 옮김, 보통 크기 유지 |
| 최대화에서 한 번 누르기 · 끌기 | 한 번 누르기는 그대로 · 끌면 2048×1222 로 줄어 따라오고 보통 크기로 남음 |
| Win+↑ · Win+↓ | 모니터 전체로 최대화 ↔ 기억한 보통 크기. 옆 모니터에서 Win+↑ = 1920×1080 전체 |
| 화면 이동(보통) 20번 | 150% 2048×1222px ↔ 100% 1365×815px, **DIP 1365×815 로 20번 모두 같음** |
| 화면 이동(최대화) | 주 ↔ 옆 모니터 전체로 옮김, 하단 바를 앱 창이 받음 |
| 보통 크기로 [종료] → 다시 켬 | 같은 자리 · 크기, 최대화 아님 |
| 옆 모니터 최대화로 [종료] → 다시 켬 | 옆 모니터(2560,0 1920×1080)에서 최대화 |
| 보통 크기로 내리면 | 작업 표시줄이 다시 보이고 테두리가 붙음 |
| MergeHub 네 탭 · 최소 1280×720 DIP | 폴더 비교 · 파일 비교 네 칸 · Git 충돌 · 설정 모두 **잘리지 않음**. 파일 비교 머리의 [새로고침] 과 폴더 비교 [비교] · [결과 닫기] 가 다음 줄로 접히고, ACTIONS 칸 · 충돌 목록은 스크롤 |
| MergeHub 네 탭 · 옆 모니터 최대화 1920×1080 | 모두 잘리지 않음 |
사용자 확인 뒤 `publish-platform.ps1` 로 `D:\UbisamPlatform\bin` 배포(Core · Shell Release 경고 0 · 오류 0, 파일 48 · pdb 0), MergeHub 다시 빌드(경고 0 · 오류 0, 앱 폴더 Core = 배포본). 배포본으로 켜 최대화 · 하단 바 · 두 번 누르기 · [종료] 다시 확인.
설정 폴더: 시험마다 기억 파일을 "기억 없음"(크기 0 · 최대화)으로 되돌림, 다른 설정 파일은 그대로(해시).
### 남긴 것
- 기억이 없을 때 **처음 Win+↓** 는 XAML 기본 크기(1600×900 DIP)로 내려온다. 두 번 누르기는 작업 영역 80%. 둘 다 쓸 수 있는 크기라 맞추지 않았다.
- 보통 크기 창 가장자리에 Windows 기본 테두리 선이 1px 보인다(유리 틀 1px).
- 창 끌어 스냅(화면 왼쪽 · 오른쪽 반쪽)은 따로 재지 않았다.
- **모든 소비 프로그램에 영향** — 플랫폼 changelog 에 적었다. 처음 켜면 지금처럼 최대화로 작업 표시줄을 덮으므로 크기를 안 바꾸는 사람에게는 같다.
## 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분할로 열기] 가 아무것도 안 열었다. 목록에서 파일을 고르고 다시 했다.)