Ubisam-MergeTool/doc/진행상황.md
ksj09406 93a507e3e8 A-5: Git 저장소 최상위를 고를 수 없던 문제
두 가지가 겹쳐 있었다.

하나. Git 탭의 폴더 고르기가 WinForms FolderBrowserDialog 였다. net48 에서는
주소 칸이 없는 옛날 트리 창이라 경로를 붙여넣을 수 없고, 지난번에 골랐던 깊은
폴더에서 최상위까지 손으로 거슬러 올라가야 했다.

둘. .git 은 최상위에만 있는데 LooksLikeRepository 가 고른 폴더 바로 밑만 봤다.
사람은 지금 보고 있던 깊은 폴더를 고르기 마련이라 멀쩡한 저장소를 두고
"저장소가 아니다"가 됐다.

- Services/FolderPicker.cs 신설. 폴더 고르기를 한 군데로 모았다(폴더 비교
  왼쪽/오른쪽, Git 저장소 경로, 변경분 추출 대상 폴더).
- PathUtil.FindRepositoryRoot 신설. 고른 자리에서 위로 올라가며 .git 을 찾는다.
  워크트리/서브모듈은 .git 이 파일이라 둘 다 본다. 안쪽에 저장소가 또 있으면
  가장 가까운 것을 잡는다.
- Git 탭과 시작 시 자동 열기가 같은 규칙을 쓴다.

확인: src\MergeHub\Views\GitView.xaml (네 단계 아래)을 골랐더니 경로가
D:\Sorce\MergeTool\MergeHub 로 바로잡히고 저장소가 열렸다.
PathTest 12건 추가(31 -> 43건). 전체 399건.

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

271 lines
18 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.

# MergeHub 진행 상황
작업 하나가 끝날 때마다 이 표를 고치고 **로컬 커밋**한다.
푸시는 하지 않는다 — 사내 Gitea 저장소가 만들어지면 그때 한 번에 올린다.
상태 표기: `대기` · `진행` · `완료`
---
## 이번 주 목표 (2주차 · 폭 넓히기)
기획안(`문서/사내_MergeTool_개발기획안`) 기준으로 **기능 덩어리를 100% 로 채우는 것**이 목표다.
한 덩어리를 깊게 파는 것보다 여러 덩어리를 "있다"고 말할 수 있는 선까지 올린다.
### ④ Git 조작 — **완료** (2026-09-08)
서비스에는 이미 있는데 화면에 없던 것들이라, 다섯 가지가 하루가 아니라 반나절에 붙었다.
| 상태 | 항목 | 내용 | 확인 |
|---|---|---|---|
| **완료** | 커밋 이름 변경(amend) | `커밋 수정` 버튼. 고쳐 쓸 메시지를 다시 치지 않게 `마지막 메시지` 버튼도 함께 뒀다 | 확인: `6e6bb78` → `0d7401f` 로 메시지가 바뀜 |
| **완료** | stash 목록 | 콤보에서 골라 `적용`/`삭제`. 목록이 바뀌면 고른 항목을 다시 잡는다 - 하나 꺼내면 번호가 당겨지는데 옛 항목을 붙들고 있으면 <b>다음 삭제가 엉뚱한 것을 지운다</b> | 확인: 저장 → 목록에 뜸 → 적용 → 워킹트리 복구 |
| **완료** | 두 커밋 사이 비교 | 이력에서 `여기부터` 로 시작점을 잡으면 그 뒤 고르는 커밋이 끝점이 된다. 안 잡으면 지금까지처럼 부모와 비교 | 확인: 헤더가 `2766bc5 → 6e6bb78 (2개 파일)` |
| **완료** | blame (줄별 작성자) | `Blame` 탭 신설. 변경 목록에서 파일을 고르고 `blame` 을 누르면 경로가 넘어간다 | 확인: 31줄 전부 `커밋·날짜·작성자` 가 붙어 나옴 |
| **완료** | 변경분 파일 추출 | `GitService.ExportChangedFiles` 신설. 저장소 안 경로 구조를 그대로 만들고, <b>바이트 그대로</b> 옮긴다(텍스트로 다시 쓰면 줄바꿈·인코딩이 바뀐다). 지워진 파일은 뽑을 것이 없어 건너뛴다 | 확인: `2개 파일 → ...Temp (건너뜀 0개)` |
### ③ 3-Way 병합 — **완료** (2026-09-08)
"볼 수 있다"에서 "끝낼 수 있다"로.
| 상태 | 항목 | 내용 | 확인 |
|---|---|---|---|
| **완료** | 자동 병합 | 저쪽만 고친 줄(`ThreeWayState.RemoteChanged`)을 한 번에 가져온다. 충돌과 내가 고친 줄은 안 건드린다. `git merge` 가 자동으로 해 주는 일을 여기서도 해 주는 것 | `Theirs only 1 → 0`, 자동 병합 버튼이 스스로 꺼짐 |
| **완료** | 충돌만 이동 | `이전 충돌` / `다음 충돌` (`Alt+Shift+↑↓`). <b>아직 안 정한 충돌만</b> 딛는다 | 3개를 차례로 딛고 마지막에 버튼이 꺼짐 |
| **완료** | 병합 완료 판정 | ACTIONS 위쪽에 `남은 충돌 N개`(빨강) → `충돌 정리 끝`(초록) | 3개 → 2 → 1 → 끝 |
**여기서 판단이 하나 필요했다** — 충돌인지 아닌지는 BASE·MINE·REMOTE 를 견주면 저절로 나오지만,
"내 것으로 간다"고 정한 줄은 <b>내용이 그대로라 여전히 충돌로 보인다.</b> 그래서 `NeedsDecision`
(충돌이면서 아직 반영·수정 표시가 없는 줄)을 따로 두었다. 그 결정은 글을 바꾸지 않으므로 편집 뒤
다시 정렬하면 사라지는데, 얼어 있는 <b>BASE 줄 번호</b>(없으면 MINE 줄 번호)로 붙들어 되살린다.
**곁들여 고친 것**: `Take Mine` · `Take Theirs` 버튼이 <b>줄을 옮겨도 켜짐/꺼짐이 그대로였다.</b>
CommunityToolkit 커맨드는 CommandManager 를 구독하지 않는데 이 둘만 갱신 목록에서 빠져 있었다.
키보드(`Alt+←/→`)로는 되고 버튼만 안 되던 이유가 이것이다.
### ① 폴더 비교 — **완료** (2026-09-08)
| 상태 | 항목 | 내용 | 확인 |
|---|---|---|---|
| **완료** | 폴더째 복사·삭제 | 파일 하나씩만 되던 것을 폴더에도 열었다. 확인 창에 <b>안에 든 파일 수</b>를 적는다 - "폴더 하나"라고만 물으면 몇 개가 덮이는지 모르는 채 확인을 누르게 된다 | `sub`(파일 2개) 폴더째 복사 → `bsubdeep wo.txt` 까지 따라옴 |
| **완료** | 상태별 보기 필터 | 다른 것만 / 전체 / 왼쪽에만 / 오른쪽에만 / 같은 것만. <b>다시 비교하지 않고</b> 목록만 다시 그린다 | 15개 → "왼쪽에만" 5개, 요약 줄은 그대로 |
| **완료** | 비교 취소 | `FolderComparer.Compare` 에 `CancellationToken`. 항목 하나를 볼 때마다 확인하므로 누르는 즉시 멈춘다. 앞서 본 결과는 그대로 남는다 | 테스트 3건 |
**설계 하나**: 파일을 실제로 옮기고 지우는 부분을 `Services/FileOps.cs` 로 뺐다.
되돌릴 수 없는 일이라 <b>화면 없이 검사할 수 있어야</b> 하기 때문이다 - 겹치는 파일은 덮고,
대상에만 있던 파일은 남기고, 두 번 복사해도 같은지를 테스트로 못 박았다.
### ② 파일 비교 — **완료** (2026-09-08)
| 상태 | 항목 | 내용 | 확인 |
|---|---|---|---|
| **완료** | 바꾸기 (Ctrl+H) | 찾기 칸 아래에 바꿀 말 줄이 붙는다(Ctrl+F 로 열면 안 뜬다 - 찾기만 할 때 자리를 뺏지 않는다). `바꾸기` 는 고른 자리가 <b>찾는 말일 때만</b> 바꾸고 다음으로 넘어간다 - 엉뚱한 자리를 덮어쓰는 것보다 한 번 더 누르게 하는 편이 낫다 | `PulseUs → PulseMicros` 2개 바꿈 |
| **완료** | 덩어리 단위 반영 | `이 덩어리 반영`(`Alt+Shift+→`). 차이로 잡힌 줄이 이어지는 구간을 한 번에 | 마지막 차이에서 2줄 한 번에 반영 |
| **완료** | 인코딩·줄바꿈 변환 저장 | ACTIONS 의 `저장 형식` 두 칸. 기본 "원본 유지"라 지금까지와 똑같이 나간다. UTF-8 / UTF-8(BOM) / CP949 · CRLF / LF | 화면 확인 |
| **완료** | 바이너리·이미지 비교 | "줄 단위로 비교할 수 없습니다" 한 줄로 끝나던 것을 <b>답이 있는 화면</b>으로. 크기·SHA-1·처음 갈리는 바이트 위치를 알려 주고, 그림이면 나란히 띄우고 아니면 앞 4KB 를 16진으로 늘어놓는다(다른 줄만 색) | `.bin` 두 개: "왼쪽 17 B · 오른쪽 19 B · 처음 갈리는 곳 0x10" · `.png` 두 개 나란히 |
---
### ⑤ 밖에서 부르기 — **완료** (2026-09-08)
| 상태 | 항목 | 내용 | 확인 |
|---|---|---|---|
| **완료** | 명령줄 인자 수신 | `--merge/--diff --base/--local/--remote/--merged` 와 그냥 넘어온 경로. <b>이것이 다리의 나머지 반쪽</b>이다 - `git config` 에 등록하는 쪽은 전부터 있었는데 받는 쪽이 없었다 | `ArgsTest` 26건 · 실제 실행 2건 |
| **완료** | 탐색기에서 부르기 | 우클릭 → <b>보내기</b> → `MergeHub 로 비교`. Git 탭 버튼 하나로 등록/해제하고 버튼 이름이 지금 상태를 말한다 | 실제 메뉴에서 확인 |
**우클릭 메뉴에 바로 넣으려다 보내기로 돌아섰다.** 처음에는 레지스트리에 정적 verb 를 넣었다
(`HKCU\Software\Classes\AllFilesystemObjects\shell\...`). 키는 제대로 들어갔고
`HKEY_CLASSES_ROOT` 에도 병합돼 보였는데 <b>탐색기 메뉴에는 뜨지 않았다.</b>
확인해 보니 이 PC 에서 우클릭 메뉴에 나오는 다른 프로그램(Notepad++ · Git Bash · WinMerge)은
전부 HKLM(전체 사용자)에 등록돼 있었고, HKCU 에 넣은 것은 - 우리 것이든 실험용으로 넣은
메모장 항목이든 - <b>하나도 그려지지 않았다.</b> HKLM 은 관리자 권한이 필요해 쓸 수 없다.
그래서 <b>보내기(SendTo)</b> 로 갔다. `%APPDATA%\Microsoft\Windows\SendTo` 에 바로가기
파일 하나를 두는 것이 전부라 레지스트리도 권한도 필요 없고 윈도우 판을 타지 않는다.
한 단계 더 들어가는 것이 유일한 단점인데, 대신 <b>여러 개를 골라 한 번에 보낼 수 있다</b> -
미뤄 두었던 "파일 두 개 골라 비교"가 덤으로 따라왔다.
셸 확장 DLL 은 쓰지 않았다 - DLL 은 탐색기 프로세스 안에서 돌아 잘못되면 탐색기가 통째로 죽는다.
고른 것이 하나뿐이면 왼쪽만 채우고 오른쪽은 비워 둔 채 화면을 연다 - 사용자가 그 자리에서
상대를 고르면 되고, 아무것도 안 열리는 것보다 낫다.
---
## 3차 — 버그 정리 + 기능 확장 (2026-09-09 ~)
지시서: `doc/작업지시-3차.md`. 실제로 써 보고 나온 문제부터 순서대로 잡는다.
### A. 죽거나 화면이 망가지는 문제
| 상태 | 항목 | 내용 | 확인 |
|---|---|---|---|
| **완료** | A-1 폴더 고르기에서 프로그램이 죽음 | 아래 따로 적음 | 왼쪽·오른쪽 10회 연속 · 파일 비교 쪽 2회 |
| **완료** | A-2 화면 이동 시 종료·최소화 | 아래 A-2~A-4 에 함께 적음 | 21회 연속 이동 (누름 60·250·600ms) |
| **완료** | A-3 파일 비교에서 화면 이동이 원래 모니터로 복귀 | A-2 와 같은 원인 | 파일 비교 탭에서 6회 왕복 |
| **완료** | A-4 화면 이동 시 잠금 팝업 | A-2 와 같은 원인 | 팝업 0회 · 잠금/바탕화면/종료 버튼은 그대로 동작 |
| **완료** | A-5 Git 저장소 최상위를 고를 수 없음 | 폴더 고르기 창을 바꾸고, 고른 자리에서 위로 올라가 최상위를 찾는다 | 4단계 아래 파일을 골라도 최상위가 열림 · PathTest 12건 추가 |
### B. 3-Way 화면
| 상태 | 항목 |
|---|---|
| 대기 | B-1 가로 스크롤 막대가 없음 |
| 대기 | B-2 아래로 내리면 칸마다 보이는 행 수가 다름 |
| 대기 | B-3 Take Mine / Take Theirs 3-Way 동작 확인 |
### C. 잔손질
| 상태 | 항목 |
|---|---|
| 대기 | C-1 코드 위 툴팁 제거 |
| 대기 | C-2 Git 탭 버튼·입력칸 잘림 |
| 대기 | C-3 폴더 복사 팝업을 토스트 모양으로 |
### D. 기능 추가
| 상태 | 항목 |
|---|---|
| 대기 | D-1 주석만 다른 파일을 다른 색으로 |
| 대기 | D-2 C++ 문법 강조 |
| 대기 | D-3 폴더 안 전체 검색 |
| 대기 | D-4 비교 창 여러 개 |
| 대기 | D-5 덩어리 단위 반영 (2-Way 는 이미 됨 · 3-Way 확인 필요) |
| 대기 | D-6 비교 결과 텍스트 내보내기 |
| 대기 | D-7 드래그 앤 드롭으로 열기 |
---
### A-5. Git 저장소 최상위를 고를 수 없던 문제 — **완료** (2026-09-09)
두 가지가 겹쳐 있었다.
**하나 — 폴더 고르는 창이 옛날 것이었다.** Git 탭의 `폴더...` 는 WinForms 의
`FolderBrowserDialog` 를 썼는데, net48 에서는 <b>주소 칸이 없는 옛날 트리 창</b>이다.
경로를 붙여넣을 수도 없고, 지난번에 골랐던 깊은 폴더가 기억돼 있으면 거기서부터
저장소 최상위까지 트리를 손으로 거슬러 올라가야 했다.
**둘 — 고른 자리만 보고 판단했다.** `.git` 은 저장소 최상위에만 있는데
`LooksLikeRepository` 는 고른 폴더 바로 밑만 봤다. 사람은 지금 보고 있던 깊은 폴더를
고르기 마련이라, 멀쩡한 저장소를 두고 "저장소가 아니다"가 됐다.
**고친 것**
- `Services/FolderPicker.cs` 신설. 폴더 고르기를 한 군데로 모았다(폴더 비교 왼쪽·오른쪽,
Git 저장소 경로, 변경분 추출 대상 폴더 — 네 자리가 같은 것을 쓴다).
주소 칸이 있어 경로를 붙여넣을 수 있고, 파일 이름 칸을 그대로 둔 채 열기를 누르면
<b>지금 보고 있는 폴더</b>가 선택된다. 최상위든 어디든 한 번에 갈 수 있다.
- `PathUtil.FindRepositoryRoot` 신설. 고른 자리에서 <b>위로 거슬러 올라가며</b> `.git` 을 찾는다.
워크트리·서브모듈은 `.git` 이 폴더가 아니라 <b>파일</b>이라 둘 다 본다.
안쪽에 저장소가 또 있으면 가장 가까운 것을 잡는다.
- Git 탭에서 폴더를 고르면 그 자리에서 최상위로 바로잡아 연다. 시작할 때의 자동 열기도
같은 규칙을 쓰므로, 설정에 하위 폴더가 저장돼 있어도 이제 열린다.
**확인**: `src\MergeHub\Views\GitView.xaml`(최상위에서 네 단계 아래)을 골랐더니
경로가 `D:\Sorce\MergeTool\MergeHub\` 로 바로잡히고 저장소가 열렸다
(`HEAD master af27800 · Working Tree 6 files`). `PathTest` 에 12건 추가(31 → 43건). 전체 **399건**.
---
### A-2 · A-3 · A-4. 화면 이동 결함 세 가지 — **완료** (2026-09-09)
세 가지가 아니라 <b>하나</b>였다.
증상은 셋으로 보였다. 화면 이동을 눌렀는데 창이 최소화되거나, 잠금 창이 뜨거나,
파일 비교 화면에서는 옮겨졌다가 원래 모니터로 되돌아왔다. 셋 다 같은 자리에서 나온다.
스택을 찍어 보니 이렇게 흘렀다.
```
ListBox.OnMouseMove
→ ItemsControl.DoAutoScroll
→ ListBox.FocusItem
→ ListBox.MakeSingleSelection ← 여기서 옆 항목이 선택된다
→ NavList_SelectionChanged ← 그 항목의 동작이 실행된다
```
**하단 탭 바는 ListBox 다.** ListBox 는 <b>누르는 순간</b> 항목을 고르고 마우스를 잡는다
(끌어서 여러 개 고르기 위해서다). 손을 떼기 전에 창이 다른 모니터로 넘어가면 커서는
목록 밖에 남게 되고, ListBox 는 그것을 "끌어서 화면 밖으로 나갔다"고 보고 <b>옆 항목을
대신 고른다.</b> 하단 탭 바의 오른쪽 끝은 `화면 잠금 · 화면 이동 · 바탕화면 · 종료` 순으로
붙어 있어서, 옆 항목이 무엇이냐에 따라 잠금 창이 뜨거나(A-4) 최소화되거나(A-2)
방금 옮긴 창이 도로 넘어온다(A-3). <b>어느 쪽으로 튈지 그때그때 달랐던 이유</b>가 이것이다.
전에 한 번 손을 댄 자리였는데, 그때는 실행을 `DispatcherPriority.Background` 로 미루는
방식이었다. **그것으로는 막히지 않는다** — 사람이 버튼을 누르고 있는 시간(보통 100ms 이상)이
그 지연보다 길기 때문이다. 누르고 있는 동안 창이 움직이면 똑같이 재현된다.
**고친 것**: 동작형 항목(화면 이동·바탕화면·화면 잠금·종료)은 탭이 아니라 <b>버튼</b>이다.
마우스로 누를 때는 ListBox 의 선택 경로를 아예 타지 않게 `PreviewMouseLeftButtonDown` 에서
끊고, 실행은 <b>손을 뗄 때</b> 한다. 창이 움직이는 순간에 눌린 버튼이 없으니 끌어서 스크롤이
일어날 여지가 없다. 누른 뒤 밖으로 끌고 나가 떼면 실행되지 않는 것도 버튼과 같은 규칙이다.
키보드로 항목을 옮겨 고르는 경로는 그대로 두었다.
**확인**: 두 모니터(2560×1600@150% ↔ 1920×1080@100%) 사이를 <b>21회 연속</b> 왕복.
버튼을 누르고 있는 시간을 60ms · 250ms · 600ms 로 나눠 각각 7회씩 했고 실패 0건.
파일 비교 화면에서도 6회 왕복 확인. 바탕화면·화면 잠금·종료 세 버튼도 그대로 동작한다.
**플랫폼 변경이라 모든 소비 프로그램에 적용된다.**
---
### A-1. 폴더 고르기에서 프로그램이 죽던 원인 — **완료** (2026-09-09)
"왼쪽 폴더..." 를 누르면 창이 하얗게 굳었다가 그대로 사라졌다. 윈도우 이벤트 로그에 남은 것:
```
System.ArgumentException
MS.Internal.AppModel.ShellUtil.GetShellItemForPath(System.String)
Microsoft.Win32.FileDialog.PrepareVistaDialog(...)
Microsoft.Win32.CommonDialog.ShowDialog()
MergeHub.ViewModels.FolderViewModel.AskForFolder(...)
```
**원인은 경로에 섞인 슬래시였다.** 왼쪽 폴더 칸에 들어 있던 값이
`C:/Users/Ubisam/AppData/Local/Temp/plain-compare` 였는데, .NET 의 `Directory.Exists` 는
이런 경로도 통과시킨다. 그런데 대화상자의 `InitialDirectory` 는 윈도우 셸(COM)이 그대로
해석하고, 슬래시가 섞이면 거부하며 예외를 던진다. 그 예외가 <b>대입할 때가 아니라
`ShowDialog()` 안쪽에서</b> 올라오기 때문에 대입 줄만 `try` 로 감싸 둔 곳(파일 비교 쪽)도
똑같이 죽게 돼 있었다. 오른쪽 폴더 칸은 비어 있어서 멀쩡했던 것이고, 사실상
"경로를 한 번이라도 채워 둔 채 다시 고르려 하면 죽는" 상태였다.
**고친 것**
- `Services/PathUtil.cs` 신설. 셸에 넘길 경로는 전부 여기를 거친다. 구분자를 역슬래시로
맞추고, 상대 경로를 펴고, 확인할 수 없는 경로는 빈 문자열로 돌려준다. 빈 문자열이면
부르는 쪽이 `InitialDirectory` 를 아예 지정하지 않는다.
- 폴더 고르기·파일 고르기 둘 다 `ShowDialog()` 를 감쌌다. 끊긴 네트워크 드라이브처럼
정규화로 걸러지지 않는 경우가 남기 때문에, 그럴 때는 시작 폴더 없이 한 번 더 연다.
폴더 고르기 자체를 못 쓰게 되는 것보다 낫다.
- 플랫폼(`UbisamBase.Shell/App.xaml.cs`)에 <b>전역 예외 그물</b>을 놓았다. 화면 스레드에서
예외가 새어 나오면 WPF 는 프로그램을 그대로 끝내 버린다. 현장 장비에서 버튼 하나 잘못
눌렀다고 프로그램이 사라지면 무슨 일이 있었는지도 남지 않는다. 이제 로그에 남기고
알림창을 띄운 뒤 계속 돈다. <b>조용히 삼키지는 않는다</b> - 알리지 않으면 "눌렀는데
아무 일도 안 일어난다"가 되어 더 나쁘다.
이 저장소만이 아니라 <b>플랫폼을 쓰는 모든 프로그램에 적용된다.</b>
**검사**: `PathTest` 31건 신설(슬래시 섞인 경로 · 따옴표 · 못 쓰는 글자 · 없는 드라이브 ·
아주 긴 경로 · 드라이브 뿌리). 전체 356 → **387건**.
---
## 3~4주차로 미룬 것
디테일이라 지금 붙잡을 일이 아니다.
- 파일·폴더 드래그&드롭, 최근 파일 목록
- 로그인·권한
- 비교 결과 보고서 내보내기
- 대용량 파일 성능 (수만 줄) — 실제 소스 크기를 확인한 뒤 판단
---
## 완료 기록
| 날짜 | 항목 | 커밋 |
|---|---|---|
| 2026-09-08 | 저장소 개설 (MergeHub · UbisamPlatform 각각) | `7719094` / `36ee902` |
| 2026-09-08 | **④ Git 조작 5가지** — amend · stash 목록 · 커밋 간 비교 · blame · 변경분 추출 | `9956567` |
| 2026-09-08 | **③ 3-Way** — 자동 병합 · 충돌만 이동 · 병합 완료 판정 | `85c5e9e` |
| 2026-09-08 | **① 폴더 비교** — 폴더째 복사·삭제 · 보기 필터 · 비교 취소 | `524a6a7` |
| 2026-09-08 | **② 파일 비교** — 바꾸기 · 덩어리 반영 · 저장 형식 · 이진/그림 비교 | `de1a43c` `55d9deb` |
| 2026-09-08 | **⑤ 밖에서 부르기** — 명령줄 인자 · 탐색기 우클릭 | 아래 |