From b82e40a17c91bd7da61ebe42c8de85c9e97baa55 Mon Sep 17 00:00:00 2001 From: ksj09406 Date: Thu, 10 Sep 2026 20:15:57 +0900 Subject: [PATCH] =?UTF-8?q?4=EC=B0=A8=20D-1:=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=EC=9D=B4=EB=A0=A5=EC=9D=84=20doc/history/=20=EB=A1=9C=20?= =?UTF-8?q?=EB=82=98=EB=88=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 721줄짜리 진행상황.md 를 날짜별 파일과 changelog 로 나누고 73줄로 줄였다. 지시서 네 개는 git mv 로 doc/작업지시/ 에 모았다. 링크 15개 전부 살아 있다. 옮기기만 하고 내용은 고치지 않았다. --- doc/history/2026-09-08-2주차.md | 90 +++ doc/history/2026-09-09-3차.md | 565 +++++++++++++++ doc/history/2026-09-10-4차.md | 47 ++ doc/history/changelog.md | 32 + doc/{작업지시-3차.md => 작업지시/3차.md} | 4 +- doc/{작업지시-4차.md => 작업지시/4차.md} | 6 +- .../머지툴보강.md} | 0 .../사용성보강.md} | 2 +- doc/진행상황.md | 683 +----------------- 9 files changed, 758 insertions(+), 671 deletions(-) create mode 100644 doc/history/2026-09-08-2주차.md create mode 100644 doc/history/2026-09-09-3차.md create mode 100644 doc/history/2026-09-10-4차.md create mode 100644 doc/history/changelog.md rename doc/{작업지시-3차.md => 작업지시/3차.md} (96%) rename doc/{작업지시-4차.md => 작업지시/4차.md} (96%) rename doc/{작업지시-머지툴보강.md => 작업지시/머지툴보강.md} (100%) rename doc/{작업지시-사용성보강.md => 작업지시/사용성보강.md} (99%) diff --git a/doc/history/2026-09-08-2주차.md b/doc/history/2026-09-08-2주차.md new file mode 100644 index 0000000..07493d7 --- /dev/null +++ b/doc/history/2026-09-08-2주차.md @@ -0,0 +1,90 @@ +# 2주차 — 폭 넓히기 (2026-09-08) + +기획안의 기능 덩어리 다섯 개를 "있다" 고 말할 수 있는 선까지 올린 주다. +`진행상황.md` 에 있던 것을 그대로 옮겼다. 내용은 고치지 않았다. + +--- + +## 이번 주 목표 (2주차 · 폭 넓히기) + +기획안(`문서/사내_MergeTool_개발기획안`) 기준으로 **기능 덩어리를 100% 로 채우는 것**이 목표다. +한 덩어리를 깊게 파는 것보다 여러 덩어리를 "있다"고 말할 수 있는 선까지 올린다. + +### ④ Git 조작 — **완료** (2026-09-08) + +서비스에는 이미 있는데 화면에 없던 것들이라, 다섯 가지가 하루가 아니라 반나절에 붙었다. + +| 상태 | 항목 | 내용 | 확인 | +|---|---|---|---| +| **완료** | 커밋 이름 변경(amend) | `커밋 수정` 버튼. 고쳐 쓸 메시지를 다시 치지 않게 `마지막 메시지` 버튼도 함께 뒀다 | 확인: `6e6bb78` → `0d7401f` 로 메시지가 바뀜 | +| **완료** | stash 목록 | 콤보에서 골라 `적용`/`삭제`. 목록이 바뀌면 고른 항목을 다시 잡는다 - 하나 꺼내면 번호가 당겨지는데 옛 항목을 붙들고 있으면 다음 삭제가 엉뚱한 것을 지운다 | 확인: 저장 → 목록에 뜸 → 적용 → 워킹트리 복구 | +| **완료** | 두 커밋 사이 비교 | 이력에서 `여기부터` 로 시작점을 잡으면 그 뒤 고르는 커밋이 끝점이 된다. 안 잡으면 지금까지처럼 부모와 비교 | 확인: 헤더가 `2766bc5 → 6e6bb78 (2개 파일)` | +| **완료** | blame (줄별 작성자) | `Blame` 탭 신설. 변경 목록에서 파일을 고르고 `blame` 을 누르면 경로가 넘어간다 | 확인: 31줄 전부 `커밋·날짜·작성자` 가 붙어 나옴 | +| **완료** | 변경분 파일 추출 | `GitService.ExportChangedFiles` 신설. 저장소 안 경로 구조를 그대로 만들고, 바이트 그대로 옮긴다(텍스트로 다시 쓰면 줄바꿈·인코딩이 바뀐다). 지워진 파일은 뽑을 것이 없어 건너뛴다 | 확인: `2개 파일 → ...Temp (건너뜀 0개)` | + +### ③ 3-Way 병합 — **완료** (2026-09-08) + +"볼 수 있다"에서 "끝낼 수 있다"로. + +| 상태 | 항목 | 내용 | 확인 | +|---|---|---|---| +| **완료** | 자동 병합 | 저쪽만 고친 줄(`ThreeWayState.RemoteChanged`)을 한 번에 가져온다. 충돌과 내가 고친 줄은 안 건드린다. `git merge` 가 자동으로 해 주는 일을 여기서도 해 주는 것 | `Theirs only 1 → 0`, 자동 병합 버튼이 스스로 꺼짐 | +| **완료** | 충돌만 이동 | `이전 충돌` / `다음 충돌` (`Alt+Shift+↑↓`). 아직 안 정한 충돌만 딛는다 | 3개를 차례로 딛고 마지막에 버튼이 꺼짐 | +| **완료** | 병합 완료 판정 | ACTIONS 위쪽에 `남은 충돌 N개`(빨강) → `충돌 정리 끝`(초록) | 3개 → 2 → 1 → 끝 | + +**여기서 판단이 하나 필요했다** — 충돌인지 아닌지는 BASE·MINE·REMOTE 를 견주면 저절로 나오지만, +"내 것으로 간다"고 정한 줄은 내용이 그대로라 여전히 충돌로 보인다. 그래서 `NeedsDecision` +(충돌이면서 아직 반영·수정 표시가 없는 줄)을 따로 두었다. 그 결정은 글을 바꾸지 않으므로 편집 뒤 +다시 정렬하면 사라지는데, 얼어 있는 BASE 줄 번호(없으면 MINE 줄 번호)로 붙들어 되살린다. + +**곁들여 고친 것**: `Take Mine` · `Take Theirs` 버튼이 줄을 옮겨도 켜짐/꺼짐이 그대로였다. +CommunityToolkit 커맨드는 CommandManager 를 구독하지 않는데 이 둘만 갱신 목록에서 빠져 있었다. +키보드(`Alt+←/→`)로는 되고 버튼만 안 되던 이유가 이것이다. + +### ① 폴더 비교 — **완료** (2026-09-08) + +| 상태 | 항목 | 내용 | 확인 | +|---|---|---|---| +| **완료** | 폴더째 복사·삭제 | 파일 하나씩만 되던 것을 폴더에도 열었다. 확인 창에 안에 든 파일 수를 적는다 - "폴더 하나"라고만 물으면 몇 개가 덮이는지 모르는 채 확인을 누르게 된다 | `sub`(파일 2개) 폴더째 복사 → `bsubdeep wo.txt` 까지 따라옴 | +| **완료** | 상태별 보기 필터 | 다른 것만 / 전체 / 왼쪽에만 / 오른쪽에만 / 같은 것만. 다시 비교하지 않고 목록만 다시 그린다 | 15개 → "왼쪽에만" 5개, 요약 줄은 그대로 | +| **완료** | 비교 취소 | `FolderComparer.Compare` 에 `CancellationToken`. 항목 하나를 볼 때마다 확인하므로 누르는 즉시 멈춘다. 앞서 본 결과는 그대로 남는다 | 테스트 3건 | + +**설계 하나**: 파일을 실제로 옮기고 지우는 부분을 `Services/FileOps.cs` 로 뺐다. +되돌릴 수 없는 일이라 화면 없이 검사할 수 있어야 하기 때문이다 - 겹치는 파일은 덮고, +대상에만 있던 파일은 남기고, 두 번 복사해도 같은지를 테스트로 못 박았다. + +### ② 파일 비교 — **완료** (2026-09-08) + +| 상태 | 항목 | 내용 | 확인 | +|---|---|---|---| +| **완료** | 바꾸기 (Ctrl+H) | 찾기 칸 아래에 바꿀 말 줄이 붙는다(Ctrl+F 로 열면 안 뜬다 - 찾기만 할 때 자리를 뺏지 않는다). `바꾸기` 는 고른 자리가 찾는 말일 때만 바꾸고 다음으로 넘어간다 - 엉뚱한 자리를 덮어쓰는 것보다 한 번 더 누르게 하는 편이 낫다 | `PulseUs → PulseMicros` 2개 바꿈 | +| **완료** | 덩어리 단위 반영 | `이 덩어리 반영`(`Alt+Shift+→`). 차이로 잡힌 줄이 이어지는 구간을 한 번에 | 마지막 차이에서 2줄 한 번에 반영 | +| **완료** | 인코딩·줄바꿈 변환 저장 | ACTIONS 의 `저장 형식` 두 칸. 기본 "원본 유지"라 지금까지와 똑같이 나간다. UTF-8 / UTF-8(BOM) / CP949 · CRLF / LF | 화면 확인 | +| **완료** | 바이너리·이미지 비교 | "줄 단위로 비교할 수 없습니다" 한 줄로 끝나던 것을 답이 있는 화면으로. 크기·SHA-1·처음 갈리는 바이트 위치를 알려 주고, 그림이면 나란히 띄우고 아니면 앞 4KB 를 16진으로 늘어놓는다(다른 줄만 색) | `.bin` 두 개: "왼쪽 17 B · 오른쪽 19 B · 처음 갈리는 곳 0x10" · `.png` 두 개 나란히 | + +--- + +### ⑤ 밖에서 부르기 — **완료** (2026-09-08) + +| 상태 | 항목 | 내용 | 확인 | +|---|---|---|---| +| **완료** | 명령줄 인자 수신 | `--merge/--diff --base/--local/--remote/--merged` 와 그냥 넘어온 경로. 이것이 다리의 나머지 반쪽이다 - `git config` 에 등록하는 쪽은 전부터 있었는데 받는 쪽이 없었다 | `ArgsTest` 26건 · 실제 실행 2건 | +| **완료** | 탐색기에서 부르기 | 우클릭 → 보내기 → `MergeHub 로 비교`. Git 탭 버튼 하나로 등록/해제하고 버튼 이름이 지금 상태를 말한다 | 실제 메뉴에서 확인 | + +**우클릭 메뉴에 바로 넣으려다 보내기로 돌아섰다.** 처음에는 레지스트리에 정적 verb 를 넣었다 +(`HKCU\Software\Classes\AllFilesystemObjects\shell\...`). 키는 제대로 들어갔고 +`HKEY_CLASSES_ROOT` 에도 병합돼 보였는데 탐색기 메뉴에는 뜨지 않았다. +확인해 보니 이 PC 에서 우클릭 메뉴에 나오는 다른 프로그램(Notepad++ · Git Bash · WinMerge)은 +전부 HKLM(전체 사용자)에 등록돼 있었고, HKCU 에 넣은 것은 - 우리 것이든 실험용으로 넣은 +메모장 항목이든 - 하나도 그려지지 않았다. HKLM 은 관리자 권한이 필요해 쓸 수 없다. + +그래서 보내기(SendTo) 로 갔다. `%APPDATA%\Microsoft\Windows\SendTo` 에 바로가기 +파일 하나를 두는 것이 전부라 레지스트리도 권한도 필요 없고 윈도우 판을 타지 않는다. +한 단계 더 들어가는 것이 유일한 단점인데, 대신 여러 개를 골라 한 번에 보낼 수 있다 - +미뤄 두었던 "파일 두 개 골라 비교"가 덤으로 따라왔다. + +셸 확장 DLL 은 쓰지 않았다 - DLL 은 탐색기 프로세스 안에서 돌아 잘못되면 탐색기가 통째로 죽는다. + +고른 것이 하나뿐이면 왼쪽만 채우고 오른쪽은 비워 둔 채 화면을 연다 - 사용자가 그 자리에서 +상대를 고르면 되고, 아무것도 안 열리는 것보다 낫다. + diff --git a/doc/history/2026-09-09-3차.md b/doc/history/2026-09-09-3차.md new file mode 100644 index 0000000..db92157 --- /dev/null +++ b/doc/history/2026-09-09-3차.md @@ -0,0 +1,565 @@ +# 3차 — 버그 정리 + 기능 확장 (2026-09-09) + +`진행상황.md` 에 있던 것을 그대로 옮겼다. 내용은 고치지 않았다. + +--- + +지시서: [작업지시/3차.md](../작업지시/3차.md). 실제로 써 보고 나온 문제부터 순서대로 잡는다. + +**2026-09-09 마무리.** A → B → C → D → E 를 하루에 다 걷었다. 지시서에 남은 항목이 없다. +D-4(비교 창 여러 개)만 사용자 판단으로 뺐다. + +| 묶음 | 항목 | 결과 | +|---|---|---| +| A 죽거나 망가짐 | 5 | 5 완료 | +| B 3-Way 화면 | 3 | 3 완료 | +| C 잔손질 | 3 | 3 완료 | +| D 기능 추가 | 7 | 6 완료 · 1 취소(D-4) | +| E 추가 요청 | 6 | 6 완료 | + +커밋 19개(MergeHub 16 · 플랫폼 3). 검사 하네스 16벌 **514건 통과 · 0건 실패**. +빌드 경고 0 · 오류 0. **푸시는 하지 않았다** - 사내 저장소가 생기면 그때 한 번에 올린다. + +화면에서 실제로 만져 보지 않으면 못 잡았을 것들이 오늘만 여섯 가지였다 - +경로 칸이 끌어다 놓기를 가로채던 것, 놓을 자리 자체가 없던 글자, 놓아도 다시 견주지 않던 것, +3-Way 에서 REMOTE 칸이 통째로 사라지던 것, 반영한 줄 색이 규칙을 바꾸면 뒤집히던 것, +차이로 이동할 때 코드가 바닥 글 위로 겹쳐 그려지던 것. 빌드가 되는 것과 쓸 수 있는 것은 다르다. + +### 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 탭은 최상위를 찾아 연다 | 목록에서 상위 폴더 한 번 눌러 고름 · 상위 폴더로 비교해 하위까지 나옴 · 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 비교 창 여러 개~~ (2026-09-09 사용자 판단으로 뺌) | +| **완료** | D-5 덩어리 단위 반영 (3-Way 에서 BASE 를 가져오던 것을 REMOTE 로) | +| **완료** | D-6 비교 결과 텍스트 내보내기 | +| **완료** | D-7 드래그 앤 드롭으로 열기 | + +### E. 추가 요청 (2026-09-09 오후) + +| 상태 | 항목 | +|---|---| +| **완료** | E-1 원본 세 칸에 세로 스크롤 막대 | +| **완료** | E-2 칸 폭을 줄였다 늘리면 행 높이가 안 돌아옴 | +| **완료** | E-3 경로 줄과 코드 사이의 빈 공간 | +| **완료** | E-4 2-Way 로 돌아오면 LOCAL 옆에 빈 칸이 남음 | +| **완료** | E-5 반영한 줄이 규칙을 바꾸면 "직접 수정" 색으로 바뀜 | +| **완료** | E-6 차이로 이동할 때 코드가 바닥 글 위에 겹쳐 그려짐 | + +--- + +### 곁들여. 3-Way 에서 BASE 가 비어 보이던 것 — **의도한 동작이었다** (2026-09-09) + +`WeldController.cpp` 를 3-Way 로 열면 BASE 칸이 통째로 비어 보였다. 고장이 아니다. + +두 브랜치의 공통 조상(`9b48b82`)에는 그 파일이 아예 없다. master 와 feature/cpp-tuning 이 +각자 새로 만든 파일이라 견줄 조상이 없는 것이다. 그래서 모든 줄이 "양쪽에서 새로 넣은 줄"이 되고 +충돌 6개가 나온다. 뜻은 맞다. + +**다만 화면이 그 사실을 말해 주지 않았다.** 빈 칸은 누가 봐도 고장으로 읽힌다. +BASE 머리글에 그렇게 적었다 - `BASE (merge base) · 이 파일 없음 - 양쪽에서 새로 만든 파일입니다`. +조상에 파일이 있는 경우는 지금까지처럼 파일 이름이 그대로 나온다. +--- + +### D-3. 폴더 안 전체 검색 — **완료** (2026-09-09) + +**왜 필요했나** - 폴더 비교는 "무엇이 다른가"에 답하지만 "이 함수를 쓰는 곳이 어디인가"에는 +답하지 못한다. 그때마다 탐색기 검색이나 다른 편집기를 켜야 했다. + +`Services/FolderSearcher.cs` 신설. 화면 없이 검사할 수 있게 찾는 일만 여기서 한다. + +**양쪽을 다 훑되 같은 줄은 한 번만 보여 준다.** 안 그러면 손대지 않은 파일이 전부 두 번씩 나와 +목록의 절반이 쓸모없어진다. 그래서 결과 한 줄에 "왼쪽 / 오른쪽 / 양쪽"이 붙는다 - +`양쪽` 은 두 폴더에 똑같이 있는 줄, `왼쪽`·`오른쪽` 은 한쪽에만 있는 줄이다. +이것만 봐도 "내가 고친 줄인지"를 안다. + +내용이 똑같은 파일은 오른쪽을 다시 읽지 않는다. 대신 왼쪽에서 찾아 둔 줄을 `양쪽` 으로 바꾼다. + +**결과를 누르면 그 줄에 서서 열린다.** 파일만 열리고 맨 위에 서 있으면 찾은 자리를 눈으로 +다시 찾아야 해서 검색을 한 뜻이 없어진다. `GitWorkspace.RequestPlainCompare` 에 줄 번호를 +얹고(`RequestedLine`), 파일 비교 화면이 파일을 다 읽은 뒤 그 줄로 옮긴다. +오른쪽에서만 나온 줄은 오른쪽 파일 기준 번호로 찾는다 - 한쪽에만 있는 줄이 끼면 +두 번호가 어긋나 엉뚱한 줄에 선다. + +**취소하면 여기까지 찾은 것을 그대로 남긴다.** 처음에는 예외를 던져 목록이 비었는데, +큰 폴더에서 원하는 줄이 이미 나왔는데 멈췄다고 다 지우면 처음부터 다시 해야 한다. + +- 대소문자 구분 · 확장자 필터(`*.cs;*.h` · `.cs` · `cs` 를 모두 같게 본다) · 하위 폴더 · 제외 규칙 +- 이진 파일과 8MB 넘는 파일은 건너뛴다. 훑은 파일 수를 계속 알려 준다 +- 한 줄에 여러 번 나와도 한 줄로 센다 + +**실제로 확인한 것** (화면이 잠겨 있어 UI 자동화로 조작하고 값을 읽었다) + +| 확인 | 결과 | +|---|---| +| 양쪽 같은 파일 | `양쪽 · Common.cpp:2` 한 줄만 | +| 한 파일 안에서 다른 줄 | `왼쪽 · :5 int Speed = 100;` / `오른쪽 · :5 int Speed = 250;` | +| 같은 파일 안 안 바뀐 줄 | `양쪽 · :6 Move(Speed);` | +| 한쪽에만 있는 파일 | `왼쪽 · sub\Only.cpp` / `오른쪽 · notes.txt` | +| 결과를 열었을 때 | 두 파일이 열리고 캐럿이 5번째 줄에 섰다 | +| 663개 파일을 훑는 중 | 진행 글자가 계속 바뀌고 [검색 취소]가 눌렸다 | +| 취소한 뒤 | `517줄 · 파일 63개 · 도중에 멈춤` — 찾은 것이 그대로 남았다 | + +검사 63건 추가(`SearchTest`). 전체 512건 통과. + +--- + +### D-7. 드래그 앤 드롭으로 열기 — **완료** (2026-09-09) + +탐색기에서 파일·폴더를 끌어다 놓으면 그 자리에서 비교가 열린다. + +`GitWorkspace.RouteDroppedPaths` 가 명령줄과 같은 판단을 쓴다(`StartupArgs.Parse` 를 그대로). +끌어다 놓는 것과 우클릭·명령줄이 다르게 굴면 그것부터 버그다 - 폴더면 폴더 비교, 파일이면 파일 비교로 +가고 해당 탭이 켜진다. 화면 아무 데나 놓으면 이 판단을 타고, 왼쪽·오른쪽 경로 칸에 바로 놓으면 +그 칸만 채운다. + +**실제로 탐색기에서 끌어다 놓아 보니 세 가지가 걸렸다.** 코드만 보고는 알 수 없는 것들이다. + +① 경로 칸 위에 놓으면 아무 일도 일어나지 않았다. +폴더 비교의 경로 칸은 `TextBox` 인데, `TextBoxBase` 의 클래스 처리기가 `DragOver`·`Drop` 을 +먼저 먹는다. 그쪽은 글자 끌어다 놓기만 알아서 파일 목록은 버리고 `Handled` 로 막아 버린다. +터널링 쪽(`PreviewDragOver`·`PreviewDrop`)으로 옮겨 클래스 처리기보다 먼저 받게 고쳤다. + +② 파일 비교 화면의 경로 글자는 놓을 자리 자체가 없었다. +`TextBlock` 은 `Background` 가 없으면 히트 테스트에 안 잡힌다. `Background="Transparent"` 를 줬다. + +③ 칸에 놓아도 다시 견주지 않았다. 칸 값만 바뀌고 화면은 옛 파일을 그대로 보여 줬다 - +"놓았는데 아무 일도 없다"로 읽힌다. `HomeViewModel.DropFile` 과 `FolderViewModel.DropFolder` 를 +만들어 넣은 뒤 바로 다시 견주게 했다. 폴더는 양쪽이 다 찼을 때만 견준다. + +**파일 하나씩 끌어다 놓아 짝을 만들 수 있게 했다.** 그 전에는 두 번째 파일이 첫 번째를 밀어내 +아무리 놓아도 왼쪽만 바뀌었다. 왼쪽만 차 있고 오른쪽이 비어 있으면 오른쪽을 채운다. + +**확인한 것** + +| 놓은 것 | 결과 | +|---|---| +| 폴더를 왼쪽·오른쪽 칸에 하나씩 | 두 칸이 차고 곧바로 비교가 돌았다 (`전체 4개 · 다름 1 · 왼쪽만 1 · 오른쪽만 1 · 같음 1`) | +| 폴더를 화면 몸통에 | 왼쪽 칸에 들어갔다 | +| 파일을 폴더 비교 몸통에 | 파일 비교 탭으로 넘어가고 BASE 에 들어갔다 | +| 그 뒤 파일을 하나 더 | LOCAL 에 들어가고 짝이 맞춰졌다 (`총 8 라인 · 차이 2 라인`) | + +--- + +### D-6. 비교 결과 텍스트 내보내기 — **완료** (2026-09-09) + +**왜 필요했나** - 결과를 남에게 보내거나 결재에 붙일 때 화면을 캡처하는 것 말고는 방법이 없었다. +캡처는 검색도 안 되고 줄 번호를 짚어 말하기도 어렵다. + +`Services/CompareReport.cs` 신설. 파일 비교와 폴더 비교 양쪽에서 쓴다. +글을 만드는 일만 여기서 하고, 어디에 저장할지 묻는 것은 화면 쪽 일이다 - 그래야 화면 없이 검사한다. + +파일 비교 결과는 이렇게 나온다. + +``` +MergeHub 파일 비교 결과 +만든 때: 2026-09-09 14:46:23 +왼쪽 : ...\WeldController_기준.cs +오른쪽: ...\WeldController_내작업.cs +---------------------------------------------------------------- +요약: 총 43 줄 · 다름 5 · 오른쪽에만 5 · 왼쪽에만 0 + +다른 줄 +---------------------------------------------------------------- +[8/8] 다름 + - private const int PowerWatt = 1800; + + private const int PowerWatt = 2000; +[-/28] 오른쪽에만 + + { +``` + +**줄 번호를 양쪽 다 적는다.** 한쪽에만 있는 줄이 있어 번호가 어긋나므로, 하나만 적으면 +파일에서 그 줄을 찾을 수 없다. 없는 쪽은 `-` 로 둔다. + +폴더 비교 결과는 상태와 경로를 줄마다 적는다. 하위 폴더 파일도 상대 경로로 들어가고, +폴더 자체는 줄로 적지 않는다 - 그 안의 파일이 이미 다 적혀 있다. + +**곁들여** 요약에 주석만 다른 수를 덧붙였다 - `다름 3(주석만 1)`. +D-1 로 새로 생긴 갈래가 요약에는 안 보였다. + +**확인**: 두 화면에서 각각 뽑아 메모장으로 열었다. `ReportTest` 23건 신설. 전체 **451건**. +--- + +### D-1. 주석만 다른 파일을 다른 색으로 — **완료** (2026-09-09) + +폴더 비교 결과에 `주석만 다름` 상태를 새로 두었다. 코드는 같고 주석·공백만 다른 파일이다. + +**왜 나누나** - 봐야 할 성격이 다르다. 주석만 손댄 파일은 눈으로 훑고 넘길 수 있는데 +다른 것과 같은 색으로 섞여 있으면 전부 열어 봐야 한다. + +**판단하는 법**: 주석 토큰을 걷어내고 공백을 지운 뒤 견준다. 주석 기호는 언어마다 다르므로 +확장자로 고른 언어표를 따르고, 언어를 모르는 파일(평문·표)은 이 판단을 하지 않는다 - +주석이라는 것이 없다. 양쪽 확장자가 다르면 같은 언어로 볼 근거가 없으니 역시 하지 않는다. + +줄 구분은 코드가 있는 줄에만 남긴다. 그래야 주석 한 줄을 통째로 지운 경우도 같다고 보고, +반대로 코드를 다른 줄로 옮긴 것은 다르다고 본다. + +큰 파일은 이 판단을 건너뛴다. 전체 비교에서 이미 쓰는 한도(8MB)를 그대로 따른다 - +폴더 하나에 수천 개가 있을 때 이 판단만으로 몇 분이 더 들면 안 된다. + +**곁들여 둘** + +- 색 뜻풀이 줄을 목록 위에 붙였다. 색만 칠해 두면 무슨 뜻인지 아무도 모른다 - + 파일 비교 화면의 "색 구분" 줄과 같은 성격이다. +- 보기 필터에 코드가 다른 것만을 더했다. 주석만 다른 것을 빼고 보는 쪽이 + 실제로 필요한 동작이다. + +**확인**: 주석만 고친 `.cs` 는 회색 `주석만 다름`, 값이 바뀐 `.cs` 와 평문 `.txt` 는 노란 `다름`. +`코드가 다른 것만` 을 고르면 앞의 것이 목록에서 빠진다. +`FolderTest` 4건 추가(37 → 41건). +--- + +### D-2 · D-5 — **완료** (2026-09-09) + +#### D-2. C++ 문법 강조 + +**이미 되고 있었다.** `LanguageProfile.CFamily` 가 `.c .h .cpp .hpp .cc .hh .cxx .hxx .inl` 을 +모두 맡고 있고 예약어·주석·문자열도 들어 있다. 지시서에서 확인만 하라고 적어 둔 대로 확인했다. + +**빠진 것은 전처리 지시문 하나였다.** `#include` 는 `#` 과 `include` 로 쪼개져 보통 낱말처럼 +칠해졌다. 줄 첫머리가 `#` 이면 그 줄 전체를 지시문 색으로 칠하게 했다 - C# 쪽과 같은 규칙이다. +같은 모양인데 언어마다 다르게 보이면 오히려 헷갈린다. +언어표에 `hasPreprocessor` 를 두어 C 계열에서만 켠다. 자바·자바스크립트는 `#` 이 지시문이 아니다. + +**검사**: `LangTest` 12건 추가(40 → 52건). + +#### D-5. 덩어리 단위 반영 + +**2-Way 는 이미 됐다**(`이 덩어리 반영` · `Alt+Shift+→`). 확인했다. + +**3-Way 가 잘못 동작하고 있었다.** 덩어리를 BASE 에서 가져오고 있었는데, 3-Way 에서 BASE 는 +공통 조상이다. 저쪽(REMOTE)을 가져오는 것이 `Take Theirs` 이고 덩어리 반영은 그것을 +줄 묶음으로 하는 것인데, BASE 를 가져오면 조상으로 되돌리는 셈이라 뜻이 전혀 달라진다. +덩어리를 묶는 기준도 같이 바꿨다 - 2-Way 는 "BASE 를 가져올 수 있는 줄", 3-Way 는 +"저쪽을 가져올 수 있는 줄"이 이어진 구간이다. + +**확인**: 충돌 6개짜리 C++ 파일에서 `다음 충돌` → `이 덩어리 반영` 한 번에 +네 줄이 REMOTE 값으로 바뀌고 `Conflict 6 → 2`, `머지 반영함 4`. +단축키(`Alt+Shift+→`)도 같은 명령을 부르므로 함께 고쳐졌다. +--- + +### C. 잔손질 — **완료** (2026-09-09) + +#### C-1. 코드 위 툴팁 제거 + +코드 위에 마우스를 올리면 그 줄 내용이 그대로 툴팁으로 다시 떴다. +글이 이미 보이는데 같은 글이 상자로 겹쳐 뜨니 가리기만 한다. 원본 세 칸에서 뺐다. +버튼·상태 아이콘 툴팁은 그대로 둔다 - 그쪽은 화면에 없는 정보를 알려 준다. + +#### C-2. 글자가 잘리던 곳 + +**Git 탭의 목록들** — 커밋 제목이 `...` 없이 글자 중간에서 뚝 끊겼다 +("A-5 다시: 폴더 고르는 창을 윈도우 폴더 선택 ‸"). 줄임표 설정은 이미 있었는데 듣지 않았다. +목록의 가로 스크롤이 켜져 있으면 항목이 제 글 길이만큼 넓게 잡히고, 그러면 잘릴 일이 +없으니 줄임표도 나오지 않는다(칸 밖으로 나간 부분이 안 보일 뿐이다). +가로 스크롤을 끄면 항목이 칸 너비에 갇히고 그때 비로소 줄임표가 나온다. +커밋·변경 파일·브랜치·충돌·태그·서브모듈 여섯 목록에 같이 걸었다. + +**설정 화면의 항목 이름** — 이름 칸이 200 폭으로 고정이라 긴 이름이 잘렸다 +("공백 무시 단계 (0=전부 비교, ‸"). 무슨 설정인지 알 수 없어진다. 접히게 바꿨다. +플랫폼 변경이라 모든 소비 프로그램에 적용된다. + +**못 찾은 것**: "작업 취소" 버튼과 Git 탭 입력 칸이 잘리는 것은 재현하지 못했다. +2560×1600 과 1920×1080 에서, 화면 배율 100%·120% 로 여섯 하위 탭을 모두 봤지만 +그 자리는 멀쩡했다. 다시 보이면 그때 화면을 받아 고친다. + +#### C-3. 폴더 복사 확인 창을 플랫폼 것으로 + +윈도우 기본 대화상자를 쓰고 있었다. 글꼴도 색도 배치도 혼자 달라 "이 프로그램이 묻는 것"으로 +읽히지 않는다. 플랫폼이 이미 가지고 있는 `MessageUtil.ShowYesNo` 로 바꿨다. + +묻는 것 자체는 없애지 않았다. 복사·삭제는 되돌릴 수 없다. 지시서에도 그렇게 적었다. +완료 알림은 전부터 토스트였고 그대로 둔다. +--- + +### E. 추가 요청 세 가지 — **완료** (2026-09-09) + +#### E-1. 원본 세 칸에 세로 스크롤 막대 + +가로(B-1)만 붙이고 세로가 빠져 있었다. 세 칸에 `VerticalScrollBarVisibility="Visible"` 을 켰다. +가로와 달리 세로는 이 칸들이 실제로 굴러가므로 따로 막대를 만들 필요 없이 ScrollViewer 가 +주는 것을 그대로 쓴다. 대신 막대가 자리를 차지하는 만큼 여백 보정을 걷어냈다 - +전에는 편집기 막대 폭만큼 오른쪽 여백을 줘서 글자 폭을 맞췄는데, 이제 진짜 막대가 그 자리를 +차지하므로 여백까지 주면 글자 폭이 좁아져 접히는 자리가 어긋난다. + +#### E-2. 칸 폭을 줄였다 늘리면 행 높이가 돌아오지 않던 문제 + +화면을 좁혔다 되돌리면 접혔던 행이 넓은 자리를 그대로 차지한 채 남았다. + +행 높이는 각 칸이 "내 글이 이만큼 높다"고 알려 온 값 중 가장 큰 것을 쓴다. +워드랩에서 한쪽만 접힌 행이 있으면 그쪽에 맞춰야 옆 칸과 줄이 맞물리기 때문이다. +그런데 글자를 그리는 칸이 세로로 늘어나 있어서(WPF 기본값 Stretch) 행이 한 번 커지면 +그 다음부터는 "행 높이만큼 높다"고 되돌려 보냈다. 자기가 만든 값을 자기가 다시 읽는 셈이라 +높이가 올라가기만 하고 내려오지 않았다. + +글자 칸을 위로 붙여(`VerticalAlignment="Top"`) 자기 글 높이만 알리도록 고쳤다. + +#### E-3. 경로 줄과 코드 사이의 빈 공간 + +패널 머리 높이를 상수로 못 박아 두었다(124 / 74). 실제 내용보다 훨씬 커서 경로 줄 아래가 +휑하게 비었고 그만큼 코드가 덜 보였다. 상수를 없애고 내용에 맞춰 줄어들게 한 뒤, +나란히 놓인 칸끼리는 `SharedSizeGroup` 으로 가장 큰 것에 맞추게 했다 +(REMOTE 만 "Conflict N" 줄이 하나 더 있어서 그렇게 해야 첫 줄이 같은 눈높이에 온다). +2-Way 에서는 MERGED 머리도 같은 무리에 넣고, 3-Way 에서는 뺀다 - 3-Way 의 MERGED 는 +아래 줄로 내려가 맞출 상대가 없는데, 넣어 두면 그쪽 경로 줄 때문에 위 세 칸까지 덩달아 커진다. + +**확인**: 2-Way 에서 한 화면에 보이는 코드가 29줄에서 34줄로 늘었다. +BASE·LOCAL 의 01행은 같은 높이 그대로다. + + +#### E-4. 2-Way 로 돌아오면 LOCAL 옆에 빈 칸이 남던 문제 + +3-Way 에서 구분선을 끌어 칸 폭을 바꾼 뒤 2-Way 로 돌아오면 LOCAL 오른쪽에 +칸 하나만 한 빈 자리가 남았다. 구분선을 건드리지 않으면 멀쩡했다. + +`GridSplitter` 는 끌고 나면 양옆 칸을 비율(star)이 아니라 고정 픽셀로 바꿔 놓는다. +그러면 두 가지가 어긋난다. 창 크기가 바뀌어도 칸이 따라 늘지 않고, 2-Way 로 돌아와 +REMOTE 칸이 접히면서 생긴 자리를 아무도 채우지 않는다(늘어날 star 칸이 없으니까). + +- 구분선을 놓는 순간 고정 픽셀을 같은 비율의 star 로 되돌린다. 사용자가 잡아 놓은 + 폭은 그대로 유지되면서 창 크기를 따라가게 된다. +- 2-Way ↔ 3-Way 를 오갈 때는 세 칸의 폭 지정을 아예 놓아준다. 배치가 통째로 다르기도 하고, + 특히 REMOTE 칸은 폭을 Style 이 정하는데 끌어서 생긴 값이 남아 있으면 2-Way 에서 접히지 + 않는다. + +**확인**: 3-Way 에서 구분선을 끌고 2-Way 로 돌아와도 두 칸이 폭을 꽉 채운다. +2-Way 에서 끌어 놓은 비율은 다른 모니터를 다녀와도 그대로다. + + +#### E-5. 반영한 줄이 규칙을 바꾸면 "직접 수정" 색이 되던 문제 + +줄을 반영(Accept)한 뒤 비교 규칙을 하나 건드리면 그 줄이 초록(머지 반영함)에서 +보라(직접 수정)로 바뀌었다. + +**반영 표시가 되돌리기 스택에만 있었다.** 규칙을 바꾸면 화면을 통째로 다시 만들면서 그 스택을 +비운다. 그러면 표시가 사라지고 남는 단서는 "글이 원본과 다르다"뿐이라 직접 수정으로 칠해진다. +B-3 에서 Take Mine 이 충돌을 못 지우던 것과 같은 뿌리다. + +- 반영할 때 줄 번호로도 남긴다. BASE 줄 번호, 없으면 MINE 줄 번호 - 둘 다 얼어 있는 값이라 + 다시 정렬해도 살아남는다. +- 다시 만들 때 반영 여부를 먼저 본다. 반영한 줄은 직접 수정 조건에도 걸리므로 순서가 중요하다. +- 되돌리면 그 기록도 함께 지운다. + +**곁들여** 머지 주석 줄도 같은 이유로 보라색이 됐다. 주석 줄이라는 표시 역시 줄 객체에만 있어서 +다시 만들면 사라진다. 글을 보고 알아보게 했다(`MergeNote.LooksLikeNote`). 사람이 우연히 같은 +모양으로 쓴 주석을 잘못 볼 수 있지만, 그래도 색이 달라질 뿐이고 반대로 못 알아보면 반영할 때마다 +"직접 수정"이 하나씩 늘어난다. + +**확인**: 반영 → 공백 규칙 → Aa 규칙 순으로 눌러도 `머지 반영함 2 · 직접 수정 0` 그대로. +`NoteTest` 13건 추가(16 → 29건). + +#### E-6. 차이로 이동하면 코드가 바닥 글 위에 겹쳐 그려지던 문제 + +`Alt+↑/↓` 로 차이를 옮겨 다니면 칸 아래 상태바 위에 코드가 겹쳐 그려졌다. + +표시층은 편집기 글자와 눈높이를 맞추려고 `RenderTransform` 으로 살짝 밀어 둔다. 그런데 +RenderTransform 은 배치가 아니라 그리기라 칸 밖으로 나가도 아무도 막지 않는다. +격자(Grid)의 행도 자기 칸 밖으로 나가는 자식을 자르지 않는다. + +코드가 놓이는 자리를 자르는 상자로 감쌌다(`ClipToBounds`). 네 칸 모두. + +**확인**: `Alt+↓` 8회 · `Alt+↑` 6회 눌러도 마지막 줄이 칸 경계에서 깨끗이 잘린다. + +**세 가지 모두 확인**: 세로 막대를 끌면 세 칸이 함께 움직인다. +2560×1600 ↔ 1920×1080 을 오가도, 가운데 구분선을 끌었다 되돌려도 처음과 같은 화면이 된다. +테스트 399건 그대로 통과. +--- + +### B. 3-Way 화면 — **완료** (2026-09-09) + +#### B-1. 원본 세 칸에 가로 스크롤 막대가 없었다 + +원본 칸은 ScrollViewer 를 가로로 굴리지 않는다. 굴리면 줄 번호까지 화면 밖으로 +밀려 나가고 세 칸이 서로 어긋난다. 대신 글자만 `SourceShiftX` 로 민다. 그래서 ScrollViewer 가 +만들어 주는 막대는 움직이지 않는 막대가 되어 감춰 두고 있었고, 결과적으로 긴 줄이 +있다는 사실 자체를 알 수 없었다(가로 휠을 굴려 봐야 알 수 있었다). + +막대를 따로 놓고 `SourceShiftX` 와 서로 값을 맞추게 했다. 세 칸이 늘 같이 움직이므로 +세 막대의 손잡이도 같은 자리에 있다. 워드랩을 켜면 밀 곳이 없으므로 사라진다. + +**확인**: 손잡이를 끌면 세 칸이 함께 움직이고, 가로 휠을 굴리면 손잡이가 따라온다. +워드랩을 켜면 세 막대가 사라지고 끄면 돌아온다. + +#### B-2. 워드랩을 켠 3-Way 에서 세로 스크롤이 아예 안 됐다 + +증상은 "행 수가 다르다"였는데, 재 보니 스크롤이 한 자리에 눌러앉아 있었다. +스크롤 값을 350 으로 바꿔도 곧바로 80 으로 되돌아왔다. + +원인이 둘이었다. + +**하나 — 메아리를 막는 자물쇠가 너무 일찍 풀렸다.** 편집기가 굴러가면 원본 칸을 따라 +굴리는데, 목록(ListBox)의 스크롤 알림은 배치가 끝난 다음에 온다. 자물쇠를 +`try/finally` 로 그 자리에서 풀어 버리니 그때는 이미 열려 있었고, 그 메아리가 다시 편집기를 +굴렸다. 워드랩이 꺼져 있으면 환산이 1:1 이라 같은 값이 되돌아와 조용히 끝나지만, +켜면 조금씩 어긋나 서로 밀어내다 멈춘다. 자물쇠를 그 일로 밀려서 오는 알림이 다 지나간 +뒤(`DispatcherPriority.Input`)에 풀도록 바꿨다. + +**둘 — 두 좌표계를 픽셀로 환산하고 있었다.** 위 세 칸과 아래 MERGED 는 폭이 달라 접히는 +횟수가 다르므로, 픽셀로 맞추려면 행마다 실제 높이를 알아야 한다. 그런데 목록은 가상화라 +화면에 그려진 행만 자기 높이를 알려 준다. 아직 안 그려진 행은 "한 줄"로 잡혀 환산이 어긋났다. +재지 않고 세는 쪽으로 바꿨다 - 워드랩일 때는 원본 칸을 행 단위로 굴려 +"몇 번째 행이 맨 위인가"만 주고받는다. 높이를 몰라도 정확히 맞는다. + +곁들여, 편집기 화면 맨 위의 행을 알아낼 때 스크롤 값을 줄 높이로 나누던 것을 +좌표로 물어보는 방식으로 바꿨다. 실제로 재 보니 스크롤 160 에서 편집기가 말하는 첫 줄은 +11 인데 화면 맨 위에 있는 것은 9행이었다. TextBox 의 줄 번호 계산은 그려진 것과 맞지 않는다. + +**확인**: 워드랩을 켠 3-Way 에서 네 칸이 같은 행을 맨 위에 놓는다(원본 09 / BASE 05 / +REMOTE 05 = 같은 줄, MERGED 09). 워드랩을 끈 경우도 그대로다(16 / 12 / 12 / 16). + +#### B-3. Take Mine 이 충돌을 정리하지 못했다 + +Take Theirs 는 되는데 Take Mine 을 눌러도 "남은 충돌" 이 줄지 않았다. + +"내 것으로 간다"는 결정은 글을 바꾸지 않는다(이미 내 원본과 같은 줄이다). +그래서 되돌리기 스택에 남지 않는데, 편집 뒤 정리 단계(`RefreshAppliedFlags`)가 +되돌리기 스택만 보고 반영 표시를 다시 칠하면서 방금 세운 표시를 지워 버렸다. +줄 번호로 따로 기억해 둔 결정(`_resolvedBaseNumbers` · `_resolvedMineNumbers`)을 +같이 보도록 고쳤다. 같은 판단이 두 군데 흩어져 있던 것도 `IsDecidedAsMine` 하나로 합쳤다. + +**확인**: 충돌 6개짜리 C++ 파일에서 Take Mine · Take Theirs 를 번갈아 눌러 +6 → 5 → 4 → … → 충돌 정리 끝 까지 갔다. + +**검사**: 전체 399건 그대로 통과. +--- + +### A-5. 폴더 고르는 창에서 상위 폴더를 고를 수 없던 문제 — **완료** (2026-09-09) + +**처음에 요구를 잘못 읽었다.** "최상위 단에서 봐야 하는데 가장 깊은 곳에서 폴더 열기밖에 안 된다"를 +Git 저장소 이야기로 읽고 `.git` 찾아 올라가기를 만들었는데, 실제로는 폴더 비교 탭의 +폴더 고르는 창 이야기였다. 상위 폴더를 골라 놓고 하위까지 한꺼번에 비교하고 싶은데 +그 상위 폴더를 고를 방법이 없다는 것이었다. + +**진짜 원인**: 폴더 고르기를 파일 열기 창으로 돌려쓰고 있었다(net48 WPF·WinForms 에는 +쓸 만한 폴더 선택 창이 없다). 이 방식은 "지금 서 있는 폴더"를 돌려주기 때문에, +목록에 보이는 폴더를 고르려면 반드시 그 안으로 들어가야 한다. 그래서 늘 가장 깊은 +폴더만 골라졌다. 게다가 윈도우가 기억해 둔 "이 프로그램이 마지막으로 본 폴더"에서 열려서 +엉뚱한 곳(지난번 Git 저장소를 고르러 들어갔던 자리)에서 시작하기도 했다. + +**고친 것** + +- 윈도우가 원래 가지고 있는 폴더 선택 창을 셸 COM(`IFileDialog` + `FOS_PICKFOLDERS`)으로 + 직접 부른다. 목록에서 폴더를 한 번 눌러 고를 수 있고, 주소 칸도 있고, 경로를 쳐 넣을 수도 있다. + COM 이 안 되는 환경이 있을 수 있어 그때는 전에 쓰던 파일 열기 창으로 물러선다. +- `SetDefaultFolder` 가 아니라 `SetFolder` 를 쓴다 — 전자는 윈도우가 기억한 폴더를 우선하기 때문에 + 지금 경로 칸에 적힌 폴더에서 열리지 않는다. +- 네 자리(폴더 비교 왼쪽·오른쪽, Git 저장소 경로, 변경분 추출 대상)가 이 창 하나를 쓴다. + +**곁들여**: Git 탭은 하위 폴더를 골랐더라도 위로 거슬러 올라가 `.git` 이 있는 최상위를 찾아 연다 +(`PathUtil.FindRepositoryRoot`). 워크트리·서브모듈은 `.git` 이 파일이라 둘 다 보고, +안쪽에 저장소가 또 있으면 가장 가까운 것을 잡는다. 시작할 때의 자동 열기도 같은 규칙을 쓴다. + +**확인**: 폴더 고르기 창을 열어 `위로` 한 번 올라간 뒤 목록의 `right` 폴더를 한 번 눌러 골랐고, +경로 칸에 그대로 들어왔다. 그 상태로 비교하니 `doc / src / src·core / src·ui` 가 트리로 나오고 +`src·core·weld.cs` 까지 펼쳐진다. Git 탭은 최상위에서 네 단계 아래 파일을 골라도 저장소가 열린다. +`PathTest` 에 12건 추가(31 → 43건). 전체 **399건**. +--- + +### A-2 · A-3 · A-4. 화면 이동 결함 세 가지 — **완료** (2026-09-09) + +세 가지가 아니라 하나였다. + +증상은 셋으로 보였다. 화면 이동을 눌렀는데 창이 최소화되거나, 잠금 창이 뜨거나, +파일 비교 화면에서는 옮겨졌다가 원래 모니터로 되돌아왔다. 셋 다 같은 자리에서 나온다. + +스택을 찍어 보니 이렇게 흘렀다. + +``` +ListBox.OnMouseMove + → ItemsControl.DoAutoScroll + → ListBox.FocusItem + → ListBox.MakeSingleSelection ← 여기서 옆 항목이 선택된다 + → NavList_SelectionChanged ← 그 항목의 동작이 실행된다 +``` + +**하단 탭 바는 ListBox 다.** ListBox 는 누르는 순간 항목을 고르고 마우스를 잡는다 +(끌어서 여러 개 고르기 위해서다). 손을 떼기 전에 창이 다른 모니터로 넘어가면 커서는 +목록 밖에 남게 되고, ListBox 는 그것을 "끌어서 화면 밖으로 나갔다"고 보고 옆 항목을 +대신 고른다. 하단 탭 바의 오른쪽 끝은 `화면 잠금 · 화면 이동 · 바탕화면 · 종료` 순으로 +붙어 있어서, 옆 항목이 무엇이냐에 따라 잠금 창이 뜨거나(A-4) 최소화되거나(A-2) +방금 옮긴 창이 도로 넘어온다(A-3). 어느 쪽으로 튈지 그때그때 달랐던 이유가 이것이다. + +전에 한 번 손을 댄 자리였는데, 그때는 실행을 `DispatcherPriority.Background` 로 미루는 +방식이었다. **그것으로는 막히지 않는다** — 사람이 버튼을 누르고 있는 시간(보통 100ms 이상)이 +그 지연보다 길기 때문이다. 누르고 있는 동안 창이 움직이면 똑같이 재현된다. + +**고친 것**: 동작형 항목(화면 이동·바탕화면·화면 잠금·종료)은 탭이 아니라 버튼이다. +마우스로 누를 때는 ListBox 의 선택 경로를 아예 타지 않게 `PreviewMouseLeftButtonDown` 에서 +끊고, 실행은 손을 뗄 때 한다. 창이 움직이는 순간에 눌린 버튼이 없으니 끌어서 스크롤이 +일어날 여지가 없다. 누른 뒤 밖으로 끌고 나가 떼면 실행되지 않는 것도 버튼과 같은 규칙이다. +키보드로 항목을 옮겨 고르는 경로는 그대로 두었다. + +**확인**: 두 모니터(2560×1600@150% ↔ 1920×1080@100%) 사이를 21회 연속 왕복. +버튼을 누르고 있는 시간을 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)이 그대로 +해석하고, 슬래시가 섞이면 거부하며 예외를 던진다. 그 예외가 대입할 때가 아니라 +`ShowDialog()` 안쪽에서 올라오기 때문에 대입 줄만 `try` 로 감싸 둔 곳(파일 비교 쪽)도 +똑같이 죽게 돼 있었다. 오른쪽 폴더 칸은 비어 있어서 멀쩡했던 것이고, 사실상 +"경로를 한 번이라도 채워 둔 채 다시 고르려 하면 죽는" 상태였다. + +**고친 것** + +- `Services/PathUtil.cs` 신설. 셸에 넘길 경로는 전부 여기를 거친다. 구분자를 역슬래시로 + 맞추고, 상대 경로를 펴고, 확인할 수 없는 경로는 빈 문자열로 돌려준다. 빈 문자열이면 + 부르는 쪽이 `InitialDirectory` 를 아예 지정하지 않는다. +- 폴더 고르기·파일 고르기 둘 다 `ShowDialog()` 를 감쌌다. 끊긴 네트워크 드라이브처럼 + 정규화로 걸러지지 않는 경우가 남기 때문에, 그럴 때는 시작 폴더 없이 한 번 더 연다. + 폴더 고르기 자체를 못 쓰게 되는 것보다 낫다. +- 플랫폼(`UbisamBase.Shell/App.xaml.cs`)에 전역 예외 그물을 놓았다. 화면 스레드에서 + 예외가 새어 나오면 WPF 는 프로그램을 그대로 끝내 버린다. 현장 장비에서 버튼 하나 잘못 + 눌렀다고 프로그램이 사라지면 무슨 일이 있었는지도 남지 않는다. 이제 로그에 남기고 + 알림창을 띄운 뒤 계속 돈다. 조용히 삼키지는 않는다 - 알리지 않으면 "눌렀는데 + 아무 일도 안 일어난다"가 되어 더 나쁘다. + 이 저장소만이 아니라 플랫폼을 쓰는 모든 프로그램에 적용된다. + +**검사**: `PathTest` 31건 신설(슬래시 섞인 경로 · 따옴표 · 못 쓰는 글자 · 없는 드라이브 · +아주 긴 경로 · 드라이브 뿌리). 전체 356 → **387건**. + diff --git a/doc/history/2026-09-10-4차.md b/doc/history/2026-09-10-4차.md new file mode 100644 index 0000000..a2129e9 --- /dev/null +++ b/doc/history/2026-09-10-4차.md @@ -0,0 +1,47 @@ +# 4차 — 로컬 머지툴 사용성 (2026-09-10 ~) + +지시서: [작업지시/4차.md](../작업지시/4차.md) + +--- + +## D-1. `doc/history/` 를 만들고 진행상황을 나눴다 — **완료** (2026-09-10) + +**왜** - `진행상황.md` 가 721줄이었고 한 파일에 다섯 가지가 섞여 있었다. +주차 목표 · 차수별 상태 표 · 항목별 상세 · 미룬 것 · 완료 기록. +"3차 D-3 에서 무슨 판단을 했더라" 를 찾으려면 스크롤로 뒤져야 했다. + +이렇게 나눴다. + +``` +doc/ + 진행상황.md 73줄. 지금 대기·진행 중인 것만 + 작업지시/ + 3차.md · 4차.md · 머지툴보강.md · 사용성보강.md + history/ + changelog.md 한 줄 요약 + 날짜별 문서 목록 + 2026-09-08-2주차.md 90줄 + 2026-09-09-3차.md 565줄 + 2026-09-10-4차.md 이 파일 +``` + +**옮기기만 하고 내용은 고치지 않았다.** 지시서가 그렇게 시켰다 - 다시 쓰면 그때 무슨 판단을 +했는지가 흐려진다. 옮긴 자리에 날짜와 제목만 얹었다. + +**지시서를 `doc/작업지시/` 로 옮긴 것은 `git mv` 로 했다.** 새로 쓰고 지우면 이력이 끊긴다. +`git log --follow` 로 옛 이력을 그대로 따라갈 수 있다. + +**링크는 열다섯 개가 전부 살아 있다.** 옮기면서 다섯 군데를 고쳤다. +`doc/작업지시-3차.md` → `작업지시/3차.md`, 지시서 안의 `doc/진행상황.md` → `../진행상황.md`, +`작업지시-머지툴보강.md` → `머지툴보강.md`. 문서 전체를 훑어 남은 옛 경로가 없는 것을 확인했다. + +**곁들여 하나** - 미룬 것 목록에 `최근 파일 목록` 이 남아 있었다. 4차 B-1 로 올라온 항목이라 +두 자리에 다르게 적혀 있었다. 미룬 것 쪽에서 뺐다. + +**판정 기준 확인** + +| 기준 | 결과 | +|---|---| +| 진행상황.md 200줄 아래 | 721 → **73줄** | +| 대기·진행 중인 것이 한 화면에 | 4차 열세 항목이 한 화면에 들어온다 | +| 끝난 차수 상세를 링크 한 번으로 | changelog 한 줄 → 날짜별 파일 | +| 링크가 깨지지 않음 | 15개 전부 정상 (스크립트로 확인) | \ No newline at end of file diff --git a/doc/history/changelog.md b/doc/history/changelog.md new file mode 100644 index 0000000..e70e9ad --- /dev/null +++ b/doc/history/changelog.md @@ -0,0 +1,32 @@ +# 변경 이력 + +무엇을 왜 바꿨는지 한 줄로 남긴다. 자세한 이야기는 날짜별 파일에 있다. + +플랫폼(`UbisamPlatform/doc/history/changelog.md`)과 형식은 같지만 **기준이 다르다.** +플랫폼 changelog 의 핵심은 "다른 소비 프로그램에 미치는 영향"인데, MergeHub 는 소비 프로그램이라 +그럴 대상이 없다. 여기서는 **무엇을 왜 바꿨는지**가 중심이다. + +| 날짜 | 항목 | 커밋 | +|---|---|---| +| 2026-09-10 | **4차 D-1** — 721줄짜리 진행상황을 `history/` 의 날짜별 파일로 나누고 지시서를 `작업지시/` 로 모음 | 아래 | +| 2026-09-09 | **3차 A 묶음** — 폴더 고르기 다운 · 화면 이동 결함 3가지 · 저장소 최상위 열기 | `5e667af` `af27800` `93a507e` `b5bd06c` / 플랫폼 `595aa23` `f664c31` | +| 2026-09-09 | **3차 B 묶음** — 3-Way 가로·세로 막대 · 행 어긋남 · Take Mine | `179dffb` | +| 2026-09-09 | **3차 C·E 묶음** — 코드 툴팁 · 글자 잘림 · 폴더 복사 확인 창 · 세로 막대 · 폭 되돌림 · 머리 빈 공간 · 반영 색 · 코드 겹침 | `9a9c431` `8def1ad` `05c74fc` `4f33cac` / 플랫폼 `5522ca0` | +| 2026-09-09 | **3차 D 묶음** — 주석만 다름 · C++ 전처리 · 3-Way 덩어리 반영 · 결과 내보내기 · 폴더 안 전체 검색 · 끌어다 놓기 | `5c8c942` `451b624` `32c0146` `ee5d34c` `373990b` `196af0a` | +| 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 | **⑤ 밖에서 부르기** — 명령줄 인자 · 탐색기 우클릭 | 아래 | + +--- + +## 날짜별 상세 + +| 날짜 | 문서 | 내용 | +|---|---|---| +| 2026-09-10 | **4차 D-1** — 721줄짜리 진행상황을 `history/` 의 날짜별 파일로 나누고 지시서를 `작업지시/` 로 모음 | 아래 | +| 2026-09-10 | [2026-09-10-4차.md](2026-09-10-4차.md) | 4차 지시서 진행 중 | +| 2026-09-09 | [2026-09-09-3차.md](2026-09-09-3차.md) | 3차 지시서 A~E 스물세 항목. 항목마다 원인과 판단을 적었다 | +| 2026-09-08 | [2026-09-08-2주차.md](2026-09-08-2주차.md) | 기능 덩어리 다섯 개(①~⑤)를 채운 주 | diff --git a/doc/작업지시-3차.md b/doc/작업지시/3차.md similarity index 96% rename from doc/작업지시-3차.md rename to doc/작업지시/3차.md index b24a6bf..d2b5fbb 100644 --- a/doc/작업지시-3차.md +++ b/doc/작업지시/3차.md @@ -1,7 +1,7 @@ # 작업 지시 — MergeHub 3차 (버그 정리 + 기능 확장) 대상: `D:\Sorce\MergeTool\MergeHub` (플러그인) · `D:\Sorce\MergeTool\UbisamPlatform` (셸) -전제: 1차·2차 지시서와 `진행상황.md` 의 ①~⑤ 는 끝난 상태다. 아래는 실제로 써 보고 나온 문제와 다음 요구사항이다. +전제: 1차·2차 지시서와 [../진행상황.md](../진행상황.md) 의 ①~⑤ 는 끝난 상태다. 아래는 실제로 써 보고 나온 문제와 다음 요구사항이다. --- @@ -9,7 +9,7 @@ - **A → B → C → D 순서로 진행한다.** A 는 프로그램이 죽거나 화면이 망가지는 문제라 제일 먼저다. - **항목 하나를 끝낼 때마다** 빌드하고 실제로 실행해 화면을 확인한 뒤 보고한다. 전부 끝내고 한 번에 보고하지 않는다. -- 항목 하나가 끝나면 `doc/진행상황.md` 의 대기/진행/완료를 갱신하고 **로컬 커밋**한다. 커밋 메시지는 한국어 한 줄로 무엇을 고쳤는지 적는다. +- 항목 하나가 끝나면 [../진행상황.md](../진행상황.md) 의 대기/진행/완료를 갱신하고 **로컬 커밋**한다. 커밋 메시지는 한국어 한 줄로 무엇을 고쳤는지 적는다. - **푸쉬하지 않는다.** 원격 저장소가 생기기 전까지 `.git` 에만 쌓아 둔다. - 기존 테스트 하네스(총 356건)는 계속 통과해야 한다. 로직을 건드리면 테스트를 먼저 늘린다. - 빌드 경고 0 · 오류 0 을 유지한다. 앱이 실행 중이면 빌드가 파일 잠금으로 실패하므로 `UbisamBase.Shell` 을 먼저 종료한다. diff --git a/doc/작업지시-4차.md b/doc/작업지시/4차.md similarity index 96% rename from doc/작업지시-4차.md rename to doc/작업지시/4차.md index 4718d7b..2fc0a51 100644 --- a/doc/작업지시-4차.md +++ b/doc/작업지시/4차.md @@ -1,7 +1,7 @@ # 작업 지시 — MergeHub 4차 (로컬 머지툴 사용성) 대상: `D:\Sorce\MergeTool\MergeHub` (플러그인) · `D:\Sorce\MergeTool\UbisamPlatform` (셸) -전제: 1~3차 지시서는 모두 끝났다(`진행상황.md`). 이 지시서는 **기능을 늘리는 것이 아니라 +전제: 1~3차 지시서는 모두 끝났다([../진행상황.md](../진행상황.md)). 이 지시서는 **기능을 늘리는 것이 아니라 매일 쓰는 사람의 걸림돌을 없애는 것**이 목적이다. 이 도구는 사내 로컬 환경에서 쓰는 머지툴이 우선이므로, 서버·협업 기능보다 "혼자 앉아서 파일 두 개 견주는 일"이 빨라지는 쪽을 먼저 본다. @@ -13,7 +13,7 @@ D(변경 이력 폴더)는 기록하는 자리를 바꾸는 일이라 **먼저 해 두는 편이 낫다** - 자세한 까닭은 D 묶음에 적었다. C(화면 구성)는 서로 얽히지 않아 순서를 바꿔도 되지만, 기능이 자리를 잡은 뒤에 손대는 편이 손이 덜 간다. - **항목 하나를 끝낼 때마다** 빌드하고 실제로 실행해 화면을 확인한 뒤 보고한다. 전부 끝내고 한 번에 보고하지 않는다. -- 항목 하나가 끝나면 `doc/진행상황.md` 의 대기/진행/완료를 갱신하고 **로컬 커밋**한다. 커밋 메시지는 한국어 한 줄. +- 항목 하나가 끝나면 [../진행상황.md](../진행상황.md) 의 대기/진행/완료를 갱신하고 **로컬 커밋**한다. 커밋 메시지는 한국어 한 줄. - **푸쉬하지 않는다.** 원격 저장소가 생기기 전까지 `.git` 에만 쌓아 둔다. - 기존 검사 하네스 16벌(514건)은 계속 통과해야 한다. 로직을 건드리면 **테스트를 먼저 늘린다.** - 빌드 경고 0 · 오류 0 을 유지한다. 앱이 실행 중이면 파일 잠금으로 빌드가 실패하므로 `UbisamBase.Shell` 을 먼저 종료한다. @@ -181,7 +181,7 @@ C 묶음은 서로 얽히지 않으므로 **어느 것부터 해도 된다.** ### D-1. `doc/history/` 를 만들고 진행상황을 나눈다 -- 지금 `doc/진행상황.md` 는 **690줄**이고 계속 늘어난다. 한 파일에 다섯 가지가 섞여 있다 - +- 지금 [../진행상황.md](../진행상황.md) 는 **690줄**이고 계속 늘어난다. 한 파일에 다섯 가지가 섞여 있다 - 주차 목표 · 차수별 상태 표 · 항목별 상세 설명 · 미룬 것 · 완료 기록. "3차 D-3 에서 무슨 판단을 했더라" 를 찾으려면 스크롤로 뒤져야 한다. 5차·6차가 쌓이면 더 나빠진다. - 플랫폼은 이미 나눠 두었다(`UbisamPlatform/doc/history/` 에 `changelog.md` + 날짜별 파일). diff --git a/doc/작업지시-머지툴보강.md b/doc/작업지시/머지툴보강.md similarity index 100% rename from doc/작업지시-머지툴보강.md rename to doc/작업지시/머지툴보강.md diff --git a/doc/작업지시-사용성보강.md b/doc/작업지시/사용성보강.md similarity index 99% rename from doc/작업지시-사용성보강.md rename to doc/작업지시/사용성보강.md index 7a5fc24..393c69c 100644 --- a/doc/작업지시-사용성보강.md +++ b/doc/작업지시/사용성보강.md @@ -1,7 +1,7 @@ # 작업 지시 — MergeHub 사용성 보강 (2차) 대상: `D:\Sorce\MergeTool\MergeHub` (플러그인) · `D:\Sorce\MergeTool\UbisamPlatform` (셸) -전제: 1차 지시서(`작업지시-머지툴보강.md`)의 작업 1~4 · 1-1~1-3 은 모두 끝난 상태다. +전제: 1차 지시서([머지툴보강.md](머지툴보강.md))의 작업 1~4 · 1-1~1-3 은 모두 끝난 상태다. --- diff --git a/doc/진행상황.md b/doc/진행상황.md index 376c1d2..fe4216f 100644 --- a/doc/진행상황.md +++ b/doc/진행상황.md @@ -1,662 +1,22 @@ # MergeHub 진행 상황 -작업 하나가 끝날 때마다 이 표를 고치고 **로컬 커밋**한다. -푸시는 하지 않는다 — 사내 Gitea 저장소가 만들어지면 그때 한 번에 올린다. +**지금 무엇이 남았는지만** 적는다. 끝난 것의 상세는 [history/](history/) 로 옮겼다. +항목을 끝내면 이렇게 한다. + +1. 상세는 `history/YYYY-MM-DD-주제.md` 에 적는다. +2. 이 파일은 **대기 → 완료로 상태만** 고친다. +3. [history/changelog.md](history/changelog.md) 에 한 줄을 더한다 (날짜 · 무엇을 왜 · 커밋). + +푸시는 하지 않는다 — 사내 Gitea 저장소가 만들어지면 그때 한 번에 올린다. 상태 표기: `대기` · `진행` · `완료` --- -## 이번 주 목표 (2주차 · 폭 넓히기) - -기획안(`문서/사내_MergeTool_개발기획안`) 기준으로 **기능 덩어리를 100% 로 채우는 것**이 목표다. -한 덩어리를 깊게 파는 것보다 여러 덩어리를 "있다"고 말할 수 있는 선까지 올린다. - -### ④ Git 조작 — **완료** (2026-09-08) - -서비스에는 이미 있는데 화면에 없던 것들이라, 다섯 가지가 하루가 아니라 반나절에 붙었다. - -| 상태 | 항목 | 내용 | 확인 | -|---|---|---|---| -| **완료** | 커밋 이름 변경(amend) | `커밋 수정` 버튼. 고쳐 쓸 메시지를 다시 치지 않게 `마지막 메시지` 버튼도 함께 뒀다 | 확인: `6e6bb78` → `0d7401f` 로 메시지가 바뀜 | -| **완료** | stash 목록 | 콤보에서 골라 `적용`/`삭제`. 목록이 바뀌면 고른 항목을 다시 잡는다 - 하나 꺼내면 번호가 당겨지는데 옛 항목을 붙들고 있으면 다음 삭제가 엉뚱한 것을 지운다 | 확인: 저장 → 목록에 뜸 → 적용 → 워킹트리 복구 | -| **완료** | 두 커밋 사이 비교 | 이력에서 `여기부터` 로 시작점을 잡으면 그 뒤 고르는 커밋이 끝점이 된다. 안 잡으면 지금까지처럼 부모와 비교 | 확인: 헤더가 `2766bc5 → 6e6bb78 (2개 파일)` | -| **완료** | blame (줄별 작성자) | `Blame` 탭 신설. 변경 목록에서 파일을 고르고 `blame` 을 누르면 경로가 넘어간다 | 확인: 31줄 전부 `커밋·날짜·작성자` 가 붙어 나옴 | -| **완료** | 변경분 파일 추출 | `GitService.ExportChangedFiles` 신설. 저장소 안 경로 구조를 그대로 만들고, 바이트 그대로 옮긴다(텍스트로 다시 쓰면 줄바꿈·인코딩이 바뀐다). 지워진 파일은 뽑을 것이 없어 건너뛴다 | 확인: `2개 파일 → ...Temp (건너뜀 0개)` | - -### ③ 3-Way 병합 — **완료** (2026-09-08) - -"볼 수 있다"에서 "끝낼 수 있다"로. - -| 상태 | 항목 | 내용 | 확인 | -|---|---|---|---| -| **완료** | 자동 병합 | 저쪽만 고친 줄(`ThreeWayState.RemoteChanged`)을 한 번에 가져온다. 충돌과 내가 고친 줄은 안 건드린다. `git merge` 가 자동으로 해 주는 일을 여기서도 해 주는 것 | `Theirs only 1 → 0`, 자동 병합 버튼이 스스로 꺼짐 | -| **완료** | 충돌만 이동 | `이전 충돌` / `다음 충돌` (`Alt+Shift+↑↓`). 아직 안 정한 충돌만 딛는다 | 3개를 차례로 딛고 마지막에 버튼이 꺼짐 | -| **완료** | 병합 완료 판정 | ACTIONS 위쪽에 `남은 충돌 N개`(빨강) → `충돌 정리 끝`(초록) | 3개 → 2 → 1 → 끝 | - -**여기서 판단이 하나 필요했다** — 충돌인지 아닌지는 BASE·MINE·REMOTE 를 견주면 저절로 나오지만, -"내 것으로 간다"고 정한 줄은 내용이 그대로라 여전히 충돌로 보인다. 그래서 `NeedsDecision` -(충돌이면서 아직 반영·수정 표시가 없는 줄)을 따로 두었다. 그 결정은 글을 바꾸지 않으므로 편집 뒤 -다시 정렬하면 사라지는데, 얼어 있는 BASE 줄 번호(없으면 MINE 줄 번호)로 붙들어 되살린다. - -**곁들여 고친 것**: `Take Mine` · `Take Theirs` 버튼이 줄을 옮겨도 켜짐/꺼짐이 그대로였다. -CommunityToolkit 커맨드는 CommandManager 를 구독하지 않는데 이 둘만 갱신 목록에서 빠져 있었다. -키보드(`Alt+←/→`)로는 되고 버튼만 안 되던 이유가 이것이다. - -### ① 폴더 비교 — **완료** (2026-09-08) - -| 상태 | 항목 | 내용 | 확인 | -|---|---|---|---| -| **완료** | 폴더째 복사·삭제 | 파일 하나씩만 되던 것을 폴더에도 열었다. 확인 창에 안에 든 파일 수를 적는다 - "폴더 하나"라고만 물으면 몇 개가 덮이는지 모르는 채 확인을 누르게 된다 | `sub`(파일 2개) 폴더째 복사 → `bsubdeep wo.txt` 까지 따라옴 | -| **완료** | 상태별 보기 필터 | 다른 것만 / 전체 / 왼쪽에만 / 오른쪽에만 / 같은 것만. 다시 비교하지 않고 목록만 다시 그린다 | 15개 → "왼쪽에만" 5개, 요약 줄은 그대로 | -| **완료** | 비교 취소 | `FolderComparer.Compare` 에 `CancellationToken`. 항목 하나를 볼 때마다 확인하므로 누르는 즉시 멈춘다. 앞서 본 결과는 그대로 남는다 | 테스트 3건 | - -**설계 하나**: 파일을 실제로 옮기고 지우는 부분을 `Services/FileOps.cs` 로 뺐다. -되돌릴 수 없는 일이라 화면 없이 검사할 수 있어야 하기 때문이다 - 겹치는 파일은 덮고, -대상에만 있던 파일은 남기고, 두 번 복사해도 같은지를 테스트로 못 박았다. - -### ② 파일 비교 — **완료** (2026-09-08) - -| 상태 | 항목 | 내용 | 확인 | -|---|---|---|---| -| **완료** | 바꾸기 (Ctrl+H) | 찾기 칸 아래에 바꿀 말 줄이 붙는다(Ctrl+F 로 열면 안 뜬다 - 찾기만 할 때 자리를 뺏지 않는다). `바꾸기` 는 고른 자리가 찾는 말일 때만 바꾸고 다음으로 넘어간다 - 엉뚱한 자리를 덮어쓰는 것보다 한 번 더 누르게 하는 편이 낫다 | `PulseUs → PulseMicros` 2개 바꿈 | -| **완료** | 덩어리 단위 반영 | `이 덩어리 반영`(`Alt+Shift+→`). 차이로 잡힌 줄이 이어지는 구간을 한 번에 | 마지막 차이에서 2줄 한 번에 반영 | -| **완료** | 인코딩·줄바꿈 변환 저장 | ACTIONS 의 `저장 형식` 두 칸. 기본 "원본 유지"라 지금까지와 똑같이 나간다. UTF-8 / UTF-8(BOM) / CP949 · CRLF / LF | 화면 확인 | -| **완료** | 바이너리·이미지 비교 | "줄 단위로 비교할 수 없습니다" 한 줄로 끝나던 것을 답이 있는 화면으로. 크기·SHA-1·처음 갈리는 바이트 위치를 알려 주고, 그림이면 나란히 띄우고 아니면 앞 4KB 를 16진으로 늘어놓는다(다른 줄만 색) | `.bin` 두 개: "왼쪽 17 B · 오른쪽 19 B · 처음 갈리는 곳 0x10" · `.png` 두 개 나란히 | - ---- - -### ⑤ 밖에서 부르기 — **완료** (2026-09-08) - -| 상태 | 항목 | 내용 | 확인 | -|---|---|---|---| -| **완료** | 명령줄 인자 수신 | `--merge/--diff --base/--local/--remote/--merged` 와 그냥 넘어온 경로. 이것이 다리의 나머지 반쪽이다 - `git config` 에 등록하는 쪽은 전부터 있었는데 받는 쪽이 없었다 | `ArgsTest` 26건 · 실제 실행 2건 | -| **완료** | 탐색기에서 부르기 | 우클릭 → 보내기 → `MergeHub 로 비교`. Git 탭 버튼 하나로 등록/해제하고 버튼 이름이 지금 상태를 말한다 | 실제 메뉴에서 확인 | - -**우클릭 메뉴에 바로 넣으려다 보내기로 돌아섰다.** 처음에는 레지스트리에 정적 verb 를 넣었다 -(`HKCU\Software\Classes\AllFilesystemObjects\shell\...`). 키는 제대로 들어갔고 -`HKEY_CLASSES_ROOT` 에도 병합돼 보였는데 탐색기 메뉴에는 뜨지 않았다. -확인해 보니 이 PC 에서 우클릭 메뉴에 나오는 다른 프로그램(Notepad++ · Git Bash · WinMerge)은 -전부 HKLM(전체 사용자)에 등록돼 있었고, HKCU 에 넣은 것은 - 우리 것이든 실험용으로 넣은 -메모장 항목이든 - 하나도 그려지지 않았다. HKLM 은 관리자 권한이 필요해 쓸 수 없다. - -그래서 보내기(SendTo) 로 갔다. `%APPDATA%\Microsoft\Windows\SendTo` 에 바로가기 -파일 하나를 두는 것이 전부라 레지스트리도 권한도 필요 없고 윈도우 판을 타지 않는다. -한 단계 더 들어가는 것이 유일한 단점인데, 대신 여러 개를 골라 한 번에 보낼 수 있다 - -미뤄 두었던 "파일 두 개 골라 비교"가 덤으로 따라왔다. - -셸 확장 DLL 은 쓰지 않았다 - DLL 은 탐색기 프로세스 안에서 돌아 잘못되면 탐색기가 통째로 죽는다. - -고른 것이 하나뿐이면 왼쪽만 채우고 오른쪽은 비워 둔 채 화면을 연다 - 사용자가 그 자리에서 -상대를 고르면 되고, 아무것도 안 열리는 것보다 낫다. - ---- - -## 3차 — 버그 정리 + 기능 확장 (2026-09-09) — **전부 끝** - -지시서: `doc/작업지시-3차.md`. 실제로 써 보고 나온 문제부터 순서대로 잡는다. - -**2026-09-09 마무리.** A → B → C → D → E 를 하루에 다 걷었다. 지시서에 남은 항목이 없다. -D-4(비교 창 여러 개)만 사용자 판단으로 뺐다. - -| 묶음 | 항목 | 결과 | -|---|---|---| -| A 죽거나 망가짐 | 5 | 5 완료 | -| B 3-Way 화면 | 3 | 3 완료 | -| C 잔손질 | 3 | 3 완료 | -| D 기능 추가 | 7 | 6 완료 · 1 취소(D-4) | -| E 추가 요청 | 6 | 6 완료 | - -커밋 19개(MergeHub 16 · 플랫폼 3). 검사 하네스 16벌 **514건 통과 · 0건 실패**. -빌드 경고 0 · 오류 0. **푸시는 하지 않았다** - 사내 저장소가 생기면 그때 한 번에 올린다. - -화면에서 실제로 만져 보지 않으면 못 잡았을 것들이 오늘만 여섯 가지였다 - -경로 칸이 끌어다 놓기를 가로채던 것, 놓을 자리 자체가 없던 글자, 놓아도 다시 견주지 않던 것, -3-Way 에서 REMOTE 칸이 통째로 사라지던 것, 반영한 줄 색이 규칙을 바꾸면 뒤집히던 것, -차이로 이동할 때 코드가 바닥 글 위로 겹쳐 그려지던 것. 빌드가 되는 것과 쓸 수 있는 것은 다르다. - -### 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 탭은 최상위를 찾아 연다 | 목록에서 상위 폴더 한 번 눌러 고름 · 상위 폴더로 비교해 하위까지 나옴 · 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 비교 창 여러 개~~ (2026-09-09 사용자 판단으로 뺌) | -| **완료** | D-5 덩어리 단위 반영 (3-Way 에서 BASE 를 가져오던 것을 REMOTE 로) | -| **완료** | D-6 비교 결과 텍스트 내보내기 | -| **완료** | D-7 드래그 앤 드롭으로 열기 | - -### E. 추가 요청 (2026-09-09 오후) - -| 상태 | 항목 | -|---|---| -| **완료** | E-1 원본 세 칸에 세로 스크롤 막대 | -| **완료** | E-2 칸 폭을 줄였다 늘리면 행 높이가 안 돌아옴 | -| **완료** | E-3 경로 줄과 코드 사이의 빈 공간 | -| **완료** | E-4 2-Way 로 돌아오면 LOCAL 옆에 빈 칸이 남음 | -| **완료** | E-5 반영한 줄이 규칙을 바꾸면 "직접 수정" 색으로 바뀜 | -| **완료** | E-6 차이로 이동할 때 코드가 바닥 글 위에 겹쳐 그려짐 | - ---- - -### 곁들여. 3-Way 에서 BASE 가 비어 보이던 것 — **의도한 동작이었다** (2026-09-09) - -`WeldController.cpp` 를 3-Way 로 열면 BASE 칸이 통째로 비어 보였다. 고장이 아니다. - -두 브랜치의 공통 조상(`9b48b82`)에는 그 파일이 아예 없다. master 와 feature/cpp-tuning 이 -각자 새로 만든 파일이라 견줄 조상이 없는 것이다. 그래서 모든 줄이 "양쪽에서 새로 넣은 줄"이 되고 -충돌 6개가 나온다. 뜻은 맞다. - -**다만 화면이 그 사실을 말해 주지 않았다.** 빈 칸은 누가 봐도 고장으로 읽힌다. -BASE 머리글에 그렇게 적었다 - `BASE (merge base) · 이 파일 없음 - 양쪽에서 새로 만든 파일입니다`. -조상에 파일이 있는 경우는 지금까지처럼 파일 이름이 그대로 나온다. ---- - -### D-3. 폴더 안 전체 검색 — **완료** (2026-09-09) - -**왜 필요했나** - 폴더 비교는 "무엇이 다른가"에 답하지만 "이 함수를 쓰는 곳이 어디인가"에는 -답하지 못한다. 그때마다 탐색기 검색이나 다른 편집기를 켜야 했다. - -`Services/FolderSearcher.cs` 신설. 화면 없이 검사할 수 있게 찾는 일만 여기서 한다. - -**양쪽을 다 훑되 같은 줄은 한 번만 보여 준다.** 안 그러면 손대지 않은 파일이 전부 두 번씩 나와 -목록의 절반이 쓸모없어진다. 그래서 결과 한 줄에 "왼쪽 / 오른쪽 / 양쪽"이 붙는다 - -`양쪽` 은 두 폴더에 똑같이 있는 줄, `왼쪽`·`오른쪽` 은 한쪽에만 있는 줄이다. -이것만 봐도 "내가 고친 줄인지"를 안다. - -내용이 똑같은 파일은 오른쪽을 다시 읽지 않는다. 대신 왼쪽에서 찾아 둔 줄을 `양쪽` 으로 바꾼다. - -**결과를 누르면 그 줄에 서서 열린다.** 파일만 열리고 맨 위에 서 있으면 찾은 자리를 눈으로 -다시 찾아야 해서 검색을 한 뜻이 없어진다. `GitWorkspace.RequestPlainCompare` 에 줄 번호를 -얹고(`RequestedLine`), 파일 비교 화면이 파일을 다 읽은 뒤 그 줄로 옮긴다. -오른쪽에서만 나온 줄은 오른쪽 파일 기준 번호로 찾는다 - 한쪽에만 있는 줄이 끼면 -두 번호가 어긋나 엉뚱한 줄에 선다. - -**취소하면 여기까지 찾은 것을 그대로 남긴다.** 처음에는 예외를 던져 목록이 비었는데, -큰 폴더에서 원하는 줄이 이미 나왔는데 멈췄다고 다 지우면 처음부터 다시 해야 한다. - -- 대소문자 구분 · 확장자 필터(`*.cs;*.h` · `.cs` · `cs` 를 모두 같게 본다) · 하위 폴더 · 제외 규칙 -- 이진 파일과 8MB 넘는 파일은 건너뛴다. 훑은 파일 수를 계속 알려 준다 -- 한 줄에 여러 번 나와도 한 줄로 센다 - -**실제로 확인한 것** (화면이 잠겨 있어 UI 자동화로 조작하고 값을 읽었다) - -| 확인 | 결과 | -|---|---| -| 양쪽 같은 파일 | `양쪽 · Common.cpp:2` 한 줄만 | -| 한 파일 안에서 다른 줄 | `왼쪽 · :5 int Speed = 100;` / `오른쪽 · :5 int Speed = 250;` | -| 같은 파일 안 안 바뀐 줄 | `양쪽 · :6 Move(Speed);` | -| 한쪽에만 있는 파일 | `왼쪽 · sub\Only.cpp` / `오른쪽 · notes.txt` | -| 결과를 열었을 때 | 두 파일이 열리고 캐럿이 5번째 줄에 섰다 | -| 663개 파일을 훑는 중 | 진행 글자가 계속 바뀌고 [검색 취소]가 눌렸다 | -| 취소한 뒤 | `517줄 · 파일 63개 · 도중에 멈춤` — 찾은 것이 그대로 남았다 | - -검사 63건 추가(`SearchTest`). 전체 512건 통과. - ---- - -### D-7. 드래그 앤 드롭으로 열기 — **완료** (2026-09-09) - -탐색기에서 파일·폴더를 끌어다 놓으면 그 자리에서 비교가 열린다. - -`GitWorkspace.RouteDroppedPaths` 가 명령줄과 같은 판단을 쓴다(`StartupArgs.Parse` 를 그대로). -끌어다 놓는 것과 우클릭·명령줄이 다르게 굴면 그것부터 버그다 - 폴더면 폴더 비교, 파일이면 파일 비교로 -가고 해당 탭이 켜진다. 화면 아무 데나 놓으면 이 판단을 타고, 왼쪽·오른쪽 경로 칸에 바로 놓으면 -그 칸만 채운다. - -**실제로 탐색기에서 끌어다 놓아 보니 세 가지가 걸렸다.** 코드만 보고는 알 수 없는 것들이다. - -① 경로 칸 위에 놓으면 아무 일도 일어나지 않았다. -폴더 비교의 경로 칸은 `TextBox` 인데, `TextBoxBase` 의 클래스 처리기가 `DragOver`·`Drop` 을 -먼저 먹는다. 그쪽은 글자 끌어다 놓기만 알아서 파일 목록은 버리고 `Handled` 로 막아 버린다. -터널링 쪽(`PreviewDragOver`·`PreviewDrop`)으로 옮겨 클래스 처리기보다 먼저 받게 고쳤다. - -② 파일 비교 화면의 경로 글자는 놓을 자리 자체가 없었다. -`TextBlock` 은 `Background` 가 없으면 히트 테스트에 안 잡힌다. `Background="Transparent"` 를 줬다. - -③ 칸에 놓아도 다시 견주지 않았다. 칸 값만 바뀌고 화면은 옛 파일을 그대로 보여 줬다 - -"놓았는데 아무 일도 없다"로 읽힌다. `HomeViewModel.DropFile` 과 `FolderViewModel.DropFolder` 를 -만들어 넣은 뒤 바로 다시 견주게 했다. 폴더는 양쪽이 다 찼을 때만 견준다. - -**파일 하나씩 끌어다 놓아 짝을 만들 수 있게 했다.** 그 전에는 두 번째 파일이 첫 번째를 밀어내 -아무리 놓아도 왼쪽만 바뀌었다. 왼쪽만 차 있고 오른쪽이 비어 있으면 오른쪽을 채운다. - -**확인한 것** - -| 놓은 것 | 결과 | -|---|---| -| 폴더를 왼쪽·오른쪽 칸에 하나씩 | 두 칸이 차고 곧바로 비교가 돌았다 (`전체 4개 · 다름 1 · 왼쪽만 1 · 오른쪽만 1 · 같음 1`) | -| 폴더를 화면 몸통에 | 왼쪽 칸에 들어갔다 | -| 파일을 폴더 비교 몸통에 | 파일 비교 탭으로 넘어가고 BASE 에 들어갔다 | -| 그 뒤 파일을 하나 더 | LOCAL 에 들어가고 짝이 맞춰졌다 (`총 8 라인 · 차이 2 라인`) | - ---- - -### D-6. 비교 결과 텍스트 내보내기 — **완료** (2026-09-09) - -**왜 필요했나** - 결과를 남에게 보내거나 결재에 붙일 때 화면을 캡처하는 것 말고는 방법이 없었다. -캡처는 검색도 안 되고 줄 번호를 짚어 말하기도 어렵다. - -`Services/CompareReport.cs` 신설. 파일 비교와 폴더 비교 양쪽에서 쓴다. -글을 만드는 일만 여기서 하고, 어디에 저장할지 묻는 것은 화면 쪽 일이다 - 그래야 화면 없이 검사한다. - -파일 비교 결과는 이렇게 나온다. - -``` -MergeHub 파일 비교 결과 -만든 때: 2026-09-09 14:46:23 -왼쪽 : ...\WeldController_기준.cs -오른쪽: ...\WeldController_내작업.cs ----------------------------------------------------------------- -요약: 총 43 줄 · 다름 5 · 오른쪽에만 5 · 왼쪽에만 0 - -다른 줄 ----------------------------------------------------------------- -[8/8] 다름 - - private const int PowerWatt = 1800; - + private const int PowerWatt = 2000; -[-/28] 오른쪽에만 - + { -``` - -**줄 번호를 양쪽 다 적는다.** 한쪽에만 있는 줄이 있어 번호가 어긋나므로, 하나만 적으면 -파일에서 그 줄을 찾을 수 없다. 없는 쪽은 `-` 로 둔다. - -폴더 비교 결과는 상태와 경로를 줄마다 적는다. 하위 폴더 파일도 상대 경로로 들어가고, -폴더 자체는 줄로 적지 않는다 - 그 안의 파일이 이미 다 적혀 있다. - -**곁들여** 요약에 주석만 다른 수를 덧붙였다 - `다름 3(주석만 1)`. -D-1 로 새로 생긴 갈래가 요약에는 안 보였다. - -**확인**: 두 화면에서 각각 뽑아 메모장으로 열었다. `ReportTest` 23건 신설. 전체 **451건**. ---- - -### D-1. 주석만 다른 파일을 다른 색으로 — **완료** (2026-09-09) - -폴더 비교 결과에 `주석만 다름` 상태를 새로 두었다. 코드는 같고 주석·공백만 다른 파일이다. - -**왜 나누나** - 봐야 할 성격이 다르다. 주석만 손댄 파일은 눈으로 훑고 넘길 수 있는데 -다른 것과 같은 색으로 섞여 있으면 전부 열어 봐야 한다. - -**판단하는 법**: 주석 토큰을 걷어내고 공백을 지운 뒤 견준다. 주석 기호는 언어마다 다르므로 -확장자로 고른 언어표를 따르고, 언어를 모르는 파일(평문·표)은 이 판단을 하지 않는다 - -주석이라는 것이 없다. 양쪽 확장자가 다르면 같은 언어로 볼 근거가 없으니 역시 하지 않는다. - -줄 구분은 코드가 있는 줄에만 남긴다. 그래야 주석 한 줄을 통째로 지운 경우도 같다고 보고, -반대로 코드를 다른 줄로 옮긴 것은 다르다고 본다. - -큰 파일은 이 판단을 건너뛴다. 전체 비교에서 이미 쓰는 한도(8MB)를 그대로 따른다 - -폴더 하나에 수천 개가 있을 때 이 판단만으로 몇 분이 더 들면 안 된다. - -**곁들여 둘** - -- 색 뜻풀이 줄을 목록 위에 붙였다. 색만 칠해 두면 무슨 뜻인지 아무도 모른다 - - 파일 비교 화면의 "색 구분" 줄과 같은 성격이다. -- 보기 필터에 코드가 다른 것만을 더했다. 주석만 다른 것을 빼고 보는 쪽이 - 실제로 필요한 동작이다. - -**확인**: 주석만 고친 `.cs` 는 회색 `주석만 다름`, 값이 바뀐 `.cs` 와 평문 `.txt` 는 노란 `다름`. -`코드가 다른 것만` 을 고르면 앞의 것이 목록에서 빠진다. -`FolderTest` 4건 추가(37 → 41건). ---- - -### D-2 · D-5 — **완료** (2026-09-09) - -#### D-2. C++ 문법 강조 - -**이미 되고 있었다.** `LanguageProfile.CFamily` 가 `.c .h .cpp .hpp .cc .hh .cxx .hxx .inl` 을 -모두 맡고 있고 예약어·주석·문자열도 들어 있다. 지시서에서 확인만 하라고 적어 둔 대로 확인했다. - -**빠진 것은 전처리 지시문 하나였다.** `#include` 는 `#` 과 `include` 로 쪼개져 보통 낱말처럼 -칠해졌다. 줄 첫머리가 `#` 이면 그 줄 전체를 지시문 색으로 칠하게 했다 - C# 쪽과 같은 규칙이다. -같은 모양인데 언어마다 다르게 보이면 오히려 헷갈린다. -언어표에 `hasPreprocessor` 를 두어 C 계열에서만 켠다. 자바·자바스크립트는 `#` 이 지시문이 아니다. - -**검사**: `LangTest` 12건 추가(40 → 52건). - -#### D-5. 덩어리 단위 반영 - -**2-Way 는 이미 됐다**(`이 덩어리 반영` · `Alt+Shift+→`). 확인했다. - -**3-Way 가 잘못 동작하고 있었다.** 덩어리를 BASE 에서 가져오고 있었는데, 3-Way 에서 BASE 는 -공통 조상이다. 저쪽(REMOTE)을 가져오는 것이 `Take Theirs` 이고 덩어리 반영은 그것을 -줄 묶음으로 하는 것인데, BASE 를 가져오면 조상으로 되돌리는 셈이라 뜻이 전혀 달라진다. -덩어리를 묶는 기준도 같이 바꿨다 - 2-Way 는 "BASE 를 가져올 수 있는 줄", 3-Way 는 -"저쪽을 가져올 수 있는 줄"이 이어진 구간이다. - -**확인**: 충돌 6개짜리 C++ 파일에서 `다음 충돌` → `이 덩어리 반영` 한 번에 -네 줄이 REMOTE 값으로 바뀌고 `Conflict 6 → 2`, `머지 반영함 4`. -단축키(`Alt+Shift+→`)도 같은 명령을 부르므로 함께 고쳐졌다. ---- - -### C. 잔손질 — **완료** (2026-09-09) - -#### C-1. 코드 위 툴팁 제거 - -코드 위에 마우스를 올리면 그 줄 내용이 그대로 툴팁으로 다시 떴다. -글이 이미 보이는데 같은 글이 상자로 겹쳐 뜨니 가리기만 한다. 원본 세 칸에서 뺐다. -버튼·상태 아이콘 툴팁은 그대로 둔다 - 그쪽은 화면에 없는 정보를 알려 준다. - -#### C-2. 글자가 잘리던 곳 - -**Git 탭의 목록들** — 커밋 제목이 `...` 없이 글자 중간에서 뚝 끊겼다 -("A-5 다시: 폴더 고르는 창을 윈도우 폴더 선택 ‸"). 줄임표 설정은 이미 있었는데 듣지 않았다. -목록의 가로 스크롤이 켜져 있으면 항목이 제 글 길이만큼 넓게 잡히고, 그러면 잘릴 일이 -없으니 줄임표도 나오지 않는다(칸 밖으로 나간 부분이 안 보일 뿐이다). -가로 스크롤을 끄면 항목이 칸 너비에 갇히고 그때 비로소 줄임표가 나온다. -커밋·변경 파일·브랜치·충돌·태그·서브모듈 여섯 목록에 같이 걸었다. - -**설정 화면의 항목 이름** — 이름 칸이 200 폭으로 고정이라 긴 이름이 잘렸다 -("공백 무시 단계 (0=전부 비교, ‸"). 무슨 설정인지 알 수 없어진다. 접히게 바꿨다. -플랫폼 변경이라 모든 소비 프로그램에 적용된다. - -**못 찾은 것**: "작업 취소" 버튼과 Git 탭 입력 칸이 잘리는 것은 재현하지 못했다. -2560×1600 과 1920×1080 에서, 화면 배율 100%·120% 로 여섯 하위 탭을 모두 봤지만 -그 자리는 멀쩡했다. 다시 보이면 그때 화면을 받아 고친다. - -#### C-3. 폴더 복사 확인 창을 플랫폼 것으로 - -윈도우 기본 대화상자를 쓰고 있었다. 글꼴도 색도 배치도 혼자 달라 "이 프로그램이 묻는 것"으로 -읽히지 않는다. 플랫폼이 이미 가지고 있는 `MessageUtil.ShowYesNo` 로 바꿨다. - -묻는 것 자체는 없애지 않았다. 복사·삭제는 되돌릴 수 없다. 지시서에도 그렇게 적었다. -완료 알림은 전부터 토스트였고 그대로 둔다. ---- - -### E. 추가 요청 세 가지 — **완료** (2026-09-09) - -#### E-1. 원본 세 칸에 세로 스크롤 막대 - -가로(B-1)만 붙이고 세로가 빠져 있었다. 세 칸에 `VerticalScrollBarVisibility="Visible"` 을 켰다. -가로와 달리 세로는 이 칸들이 실제로 굴러가므로 따로 막대를 만들 필요 없이 ScrollViewer 가 -주는 것을 그대로 쓴다. 대신 막대가 자리를 차지하는 만큼 여백 보정을 걷어냈다 - -전에는 편집기 막대 폭만큼 오른쪽 여백을 줘서 글자 폭을 맞췄는데, 이제 진짜 막대가 그 자리를 -차지하므로 여백까지 주면 글자 폭이 좁아져 접히는 자리가 어긋난다. - -#### E-2. 칸 폭을 줄였다 늘리면 행 높이가 돌아오지 않던 문제 - -화면을 좁혔다 되돌리면 접혔던 행이 넓은 자리를 그대로 차지한 채 남았다. - -행 높이는 각 칸이 "내 글이 이만큼 높다"고 알려 온 값 중 가장 큰 것을 쓴다. -워드랩에서 한쪽만 접힌 행이 있으면 그쪽에 맞춰야 옆 칸과 줄이 맞물리기 때문이다. -그런데 글자를 그리는 칸이 세로로 늘어나 있어서(WPF 기본값 Stretch) 행이 한 번 커지면 -그 다음부터는 "행 높이만큼 높다"고 되돌려 보냈다. 자기가 만든 값을 자기가 다시 읽는 셈이라 -높이가 올라가기만 하고 내려오지 않았다. - -글자 칸을 위로 붙여(`VerticalAlignment="Top"`) 자기 글 높이만 알리도록 고쳤다. - -#### E-3. 경로 줄과 코드 사이의 빈 공간 - -패널 머리 높이를 상수로 못 박아 두었다(124 / 74). 실제 내용보다 훨씬 커서 경로 줄 아래가 -휑하게 비었고 그만큼 코드가 덜 보였다. 상수를 없애고 내용에 맞춰 줄어들게 한 뒤, -나란히 놓인 칸끼리는 `SharedSizeGroup` 으로 가장 큰 것에 맞추게 했다 -(REMOTE 만 "Conflict N" 줄이 하나 더 있어서 그렇게 해야 첫 줄이 같은 눈높이에 온다). -2-Way 에서는 MERGED 머리도 같은 무리에 넣고, 3-Way 에서는 뺀다 - 3-Way 의 MERGED 는 -아래 줄로 내려가 맞출 상대가 없는데, 넣어 두면 그쪽 경로 줄 때문에 위 세 칸까지 덩달아 커진다. - -**확인**: 2-Way 에서 한 화면에 보이는 코드가 29줄에서 34줄로 늘었다. -BASE·LOCAL 의 01행은 같은 높이 그대로다. - - -#### E-4. 2-Way 로 돌아오면 LOCAL 옆에 빈 칸이 남던 문제 - -3-Way 에서 구분선을 끌어 칸 폭을 바꾼 뒤 2-Way 로 돌아오면 LOCAL 오른쪽에 -칸 하나만 한 빈 자리가 남았다. 구분선을 건드리지 않으면 멀쩡했다. - -`GridSplitter` 는 끌고 나면 양옆 칸을 비율(star)이 아니라 고정 픽셀로 바꿔 놓는다. -그러면 두 가지가 어긋난다. 창 크기가 바뀌어도 칸이 따라 늘지 않고, 2-Way 로 돌아와 -REMOTE 칸이 접히면서 생긴 자리를 아무도 채우지 않는다(늘어날 star 칸이 없으니까). - -- 구분선을 놓는 순간 고정 픽셀을 같은 비율의 star 로 되돌린다. 사용자가 잡아 놓은 - 폭은 그대로 유지되면서 창 크기를 따라가게 된다. -- 2-Way ↔ 3-Way 를 오갈 때는 세 칸의 폭 지정을 아예 놓아준다. 배치가 통째로 다르기도 하고, - 특히 REMOTE 칸은 폭을 Style 이 정하는데 끌어서 생긴 값이 남아 있으면 2-Way 에서 접히지 - 않는다. - -**확인**: 3-Way 에서 구분선을 끌고 2-Way 로 돌아와도 두 칸이 폭을 꽉 채운다. -2-Way 에서 끌어 놓은 비율은 다른 모니터를 다녀와도 그대로다. - - -#### E-5. 반영한 줄이 규칙을 바꾸면 "직접 수정" 색이 되던 문제 - -줄을 반영(Accept)한 뒤 비교 규칙을 하나 건드리면 그 줄이 초록(머지 반영함)에서 -보라(직접 수정)로 바뀌었다. - -**반영 표시가 되돌리기 스택에만 있었다.** 규칙을 바꾸면 화면을 통째로 다시 만들면서 그 스택을 -비운다. 그러면 표시가 사라지고 남는 단서는 "글이 원본과 다르다"뿐이라 직접 수정으로 칠해진다. -B-3 에서 Take Mine 이 충돌을 못 지우던 것과 같은 뿌리다. - -- 반영할 때 줄 번호로도 남긴다. BASE 줄 번호, 없으면 MINE 줄 번호 - 둘 다 얼어 있는 값이라 - 다시 정렬해도 살아남는다. -- 다시 만들 때 반영 여부를 먼저 본다. 반영한 줄은 직접 수정 조건에도 걸리므로 순서가 중요하다. -- 되돌리면 그 기록도 함께 지운다. - -**곁들여** 머지 주석 줄도 같은 이유로 보라색이 됐다. 주석 줄이라는 표시 역시 줄 객체에만 있어서 -다시 만들면 사라진다. 글을 보고 알아보게 했다(`MergeNote.LooksLikeNote`). 사람이 우연히 같은 -모양으로 쓴 주석을 잘못 볼 수 있지만, 그래도 색이 달라질 뿐이고 반대로 못 알아보면 반영할 때마다 -"직접 수정"이 하나씩 늘어난다. - -**확인**: 반영 → 공백 규칙 → Aa 규칙 순으로 눌러도 `머지 반영함 2 · 직접 수정 0` 그대로. -`NoteTest` 13건 추가(16 → 29건). - -#### E-6. 차이로 이동하면 코드가 바닥 글 위에 겹쳐 그려지던 문제 - -`Alt+↑/↓` 로 차이를 옮겨 다니면 칸 아래 상태바 위에 코드가 겹쳐 그려졌다. - -표시층은 편집기 글자와 눈높이를 맞추려고 `RenderTransform` 으로 살짝 밀어 둔다. 그런데 -RenderTransform 은 배치가 아니라 그리기라 칸 밖으로 나가도 아무도 막지 않는다. -격자(Grid)의 행도 자기 칸 밖으로 나가는 자식을 자르지 않는다. - -코드가 놓이는 자리를 자르는 상자로 감쌌다(`ClipToBounds`). 네 칸 모두. - -**확인**: `Alt+↓` 8회 · `Alt+↑` 6회 눌러도 마지막 줄이 칸 경계에서 깨끗이 잘린다. - -**세 가지 모두 확인**: 세로 막대를 끌면 세 칸이 함께 움직인다. -2560×1600 ↔ 1920×1080 을 오가도, 가운데 구분선을 끌었다 되돌려도 처음과 같은 화면이 된다. -테스트 399건 그대로 통과. ---- - -### B. 3-Way 화면 — **완료** (2026-09-09) - -#### B-1. 원본 세 칸에 가로 스크롤 막대가 없었다 - -원본 칸은 ScrollViewer 를 가로로 굴리지 않는다. 굴리면 줄 번호까지 화면 밖으로 -밀려 나가고 세 칸이 서로 어긋난다. 대신 글자만 `SourceShiftX` 로 민다. 그래서 ScrollViewer 가 -만들어 주는 막대는 움직이지 않는 막대가 되어 감춰 두고 있었고, 결과적으로 긴 줄이 -있다는 사실 자체를 알 수 없었다(가로 휠을 굴려 봐야 알 수 있었다). - -막대를 따로 놓고 `SourceShiftX` 와 서로 값을 맞추게 했다. 세 칸이 늘 같이 움직이므로 -세 막대의 손잡이도 같은 자리에 있다. 워드랩을 켜면 밀 곳이 없으므로 사라진다. - -**확인**: 손잡이를 끌면 세 칸이 함께 움직이고, 가로 휠을 굴리면 손잡이가 따라온다. -워드랩을 켜면 세 막대가 사라지고 끄면 돌아온다. - -#### B-2. 워드랩을 켠 3-Way 에서 세로 스크롤이 아예 안 됐다 - -증상은 "행 수가 다르다"였는데, 재 보니 스크롤이 한 자리에 눌러앉아 있었다. -스크롤 값을 350 으로 바꿔도 곧바로 80 으로 되돌아왔다. - -원인이 둘이었다. - -**하나 — 메아리를 막는 자물쇠가 너무 일찍 풀렸다.** 편집기가 굴러가면 원본 칸을 따라 -굴리는데, 목록(ListBox)의 스크롤 알림은 배치가 끝난 다음에 온다. 자물쇠를 -`try/finally` 로 그 자리에서 풀어 버리니 그때는 이미 열려 있었고, 그 메아리가 다시 편집기를 -굴렸다. 워드랩이 꺼져 있으면 환산이 1:1 이라 같은 값이 되돌아와 조용히 끝나지만, -켜면 조금씩 어긋나 서로 밀어내다 멈춘다. 자물쇠를 그 일로 밀려서 오는 알림이 다 지나간 -뒤(`DispatcherPriority.Input`)에 풀도록 바꿨다. - -**둘 — 두 좌표계를 픽셀로 환산하고 있었다.** 위 세 칸과 아래 MERGED 는 폭이 달라 접히는 -횟수가 다르므로, 픽셀로 맞추려면 행마다 실제 높이를 알아야 한다. 그런데 목록은 가상화라 -화면에 그려진 행만 자기 높이를 알려 준다. 아직 안 그려진 행은 "한 줄"로 잡혀 환산이 어긋났다. -재지 않고 세는 쪽으로 바꿨다 - 워드랩일 때는 원본 칸을 행 단위로 굴려 -"몇 번째 행이 맨 위인가"만 주고받는다. 높이를 몰라도 정확히 맞는다. - -곁들여, 편집기 화면 맨 위의 행을 알아낼 때 스크롤 값을 줄 높이로 나누던 것을 -좌표로 물어보는 방식으로 바꿨다. 실제로 재 보니 스크롤 160 에서 편집기가 말하는 첫 줄은 -11 인데 화면 맨 위에 있는 것은 9행이었다. TextBox 의 줄 번호 계산은 그려진 것과 맞지 않는다. - -**확인**: 워드랩을 켠 3-Way 에서 네 칸이 같은 행을 맨 위에 놓는다(원본 09 / BASE 05 / -REMOTE 05 = 같은 줄, MERGED 09). 워드랩을 끈 경우도 그대로다(16 / 12 / 12 / 16). - -#### B-3. Take Mine 이 충돌을 정리하지 못했다 - -Take Theirs 는 되는데 Take Mine 을 눌러도 "남은 충돌" 이 줄지 않았다. - -"내 것으로 간다"는 결정은 글을 바꾸지 않는다(이미 내 원본과 같은 줄이다). -그래서 되돌리기 스택에 남지 않는데, 편집 뒤 정리 단계(`RefreshAppliedFlags`)가 -되돌리기 스택만 보고 반영 표시를 다시 칠하면서 방금 세운 표시를 지워 버렸다. -줄 번호로 따로 기억해 둔 결정(`_resolvedBaseNumbers` · `_resolvedMineNumbers`)을 -같이 보도록 고쳤다. 같은 판단이 두 군데 흩어져 있던 것도 `IsDecidedAsMine` 하나로 합쳤다. - -**확인**: 충돌 6개짜리 C++ 파일에서 Take Mine · Take Theirs 를 번갈아 눌러 -6 → 5 → 4 → … → 충돌 정리 끝 까지 갔다. - -**검사**: 전체 399건 그대로 통과. ---- - -### A-5. 폴더 고르는 창에서 상위 폴더를 고를 수 없던 문제 — **완료** (2026-09-09) - -**처음에 요구를 잘못 읽었다.** "최상위 단에서 봐야 하는데 가장 깊은 곳에서 폴더 열기밖에 안 된다"를 -Git 저장소 이야기로 읽고 `.git` 찾아 올라가기를 만들었는데, 실제로는 폴더 비교 탭의 -폴더 고르는 창 이야기였다. 상위 폴더를 골라 놓고 하위까지 한꺼번에 비교하고 싶은데 -그 상위 폴더를 고를 방법이 없다는 것이었다. - -**진짜 원인**: 폴더 고르기를 파일 열기 창으로 돌려쓰고 있었다(net48 WPF·WinForms 에는 -쓸 만한 폴더 선택 창이 없다). 이 방식은 "지금 서 있는 폴더"를 돌려주기 때문에, -목록에 보이는 폴더를 고르려면 반드시 그 안으로 들어가야 한다. 그래서 늘 가장 깊은 -폴더만 골라졌다. 게다가 윈도우가 기억해 둔 "이 프로그램이 마지막으로 본 폴더"에서 열려서 -엉뚱한 곳(지난번 Git 저장소를 고르러 들어갔던 자리)에서 시작하기도 했다. - -**고친 것** - -- 윈도우가 원래 가지고 있는 폴더 선택 창을 셸 COM(`IFileDialog` + `FOS_PICKFOLDERS`)으로 - 직접 부른다. 목록에서 폴더를 한 번 눌러 고를 수 있고, 주소 칸도 있고, 경로를 쳐 넣을 수도 있다. - COM 이 안 되는 환경이 있을 수 있어 그때는 전에 쓰던 파일 열기 창으로 물러선다. -- `SetDefaultFolder` 가 아니라 `SetFolder` 를 쓴다 — 전자는 윈도우가 기억한 폴더를 우선하기 때문에 - 지금 경로 칸에 적힌 폴더에서 열리지 않는다. -- 네 자리(폴더 비교 왼쪽·오른쪽, Git 저장소 경로, 변경분 추출 대상)가 이 창 하나를 쓴다. - -**곁들여**: Git 탭은 하위 폴더를 골랐더라도 위로 거슬러 올라가 `.git` 이 있는 최상위를 찾아 연다 -(`PathUtil.FindRepositoryRoot`). 워크트리·서브모듈은 `.git` 이 파일이라 둘 다 보고, -안쪽에 저장소가 또 있으면 가장 가까운 것을 잡는다. 시작할 때의 자동 열기도 같은 규칙을 쓴다. - -**확인**: 폴더 고르기 창을 열어 `위로` 한 번 올라간 뒤 목록의 `right` 폴더를 한 번 눌러 골랐고, -경로 칸에 그대로 들어왔다. 그 상태로 비교하니 `doc / src / src·core / src·ui` 가 트리로 나오고 -`src·core·weld.cs` 까지 펼쳐진다. Git 탭은 최상위에서 네 단계 아래 파일을 골라도 저장소가 열린다. -`PathTest` 에 12건 추가(31 → 43건). 전체 **399건**. ---- - -### A-2 · A-3 · A-4. 화면 이동 결함 세 가지 — **완료** (2026-09-09) - -세 가지가 아니라 하나였다. - -증상은 셋으로 보였다. 화면 이동을 눌렀는데 창이 최소화되거나, 잠금 창이 뜨거나, -파일 비교 화면에서는 옮겨졌다가 원래 모니터로 되돌아왔다. 셋 다 같은 자리에서 나온다. - -스택을 찍어 보니 이렇게 흘렀다. - -``` -ListBox.OnMouseMove - → ItemsControl.DoAutoScroll - → ListBox.FocusItem - → ListBox.MakeSingleSelection ← 여기서 옆 항목이 선택된다 - → NavList_SelectionChanged ← 그 항목의 동작이 실행된다 -``` - -**하단 탭 바는 ListBox 다.** ListBox 는 누르는 순간 항목을 고르고 마우스를 잡는다 -(끌어서 여러 개 고르기 위해서다). 손을 떼기 전에 창이 다른 모니터로 넘어가면 커서는 -목록 밖에 남게 되고, ListBox 는 그것을 "끌어서 화면 밖으로 나갔다"고 보고 옆 항목을 -대신 고른다. 하단 탭 바의 오른쪽 끝은 `화면 잠금 · 화면 이동 · 바탕화면 · 종료` 순으로 -붙어 있어서, 옆 항목이 무엇이냐에 따라 잠금 창이 뜨거나(A-4) 최소화되거나(A-2) -방금 옮긴 창이 도로 넘어온다(A-3). 어느 쪽으로 튈지 그때그때 달랐던 이유가 이것이다. - -전에 한 번 손을 댄 자리였는데, 그때는 실행을 `DispatcherPriority.Background` 로 미루는 -방식이었다. **그것으로는 막히지 않는다** — 사람이 버튼을 누르고 있는 시간(보통 100ms 이상)이 -그 지연보다 길기 때문이다. 누르고 있는 동안 창이 움직이면 똑같이 재현된다. - -**고친 것**: 동작형 항목(화면 이동·바탕화면·화면 잠금·종료)은 탭이 아니라 버튼이다. -마우스로 누를 때는 ListBox 의 선택 경로를 아예 타지 않게 `PreviewMouseLeftButtonDown` 에서 -끊고, 실행은 손을 뗄 때 한다. 창이 움직이는 순간에 눌린 버튼이 없으니 끌어서 스크롤이 -일어날 여지가 없다. 누른 뒤 밖으로 끌고 나가 떼면 실행되지 않는 것도 버튼과 같은 규칙이다. -키보드로 항목을 옮겨 고르는 경로는 그대로 두었다. - -**확인**: 두 모니터(2560×1600@150% ↔ 1920×1080@100%) 사이를 21회 연속 왕복. -버튼을 누르고 있는 시간을 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)이 그대로 -해석하고, 슬래시가 섞이면 거부하며 예외를 던진다. 그 예외가 대입할 때가 아니라 -`ShowDialog()` 안쪽에서 올라오기 때문에 대입 줄만 `try` 로 감싸 둔 곳(파일 비교 쪽)도 -똑같이 죽게 돼 있었다. 오른쪽 폴더 칸은 비어 있어서 멀쩡했던 것이고, 사실상 -"경로를 한 번이라도 채워 둔 채 다시 고르려 하면 죽는" 상태였다. - -**고친 것** - -- `Services/PathUtil.cs` 신설. 셸에 넘길 경로는 전부 여기를 거친다. 구분자를 역슬래시로 - 맞추고, 상대 경로를 펴고, 확인할 수 없는 경로는 빈 문자열로 돌려준다. 빈 문자열이면 - 부르는 쪽이 `InitialDirectory` 를 아예 지정하지 않는다. -- 폴더 고르기·파일 고르기 둘 다 `ShowDialog()` 를 감쌌다. 끊긴 네트워크 드라이브처럼 - 정규화로 걸러지지 않는 경우가 남기 때문에, 그럴 때는 시작 폴더 없이 한 번 더 연다. - 폴더 고르기 자체를 못 쓰게 되는 것보다 낫다. -- 플랫폼(`UbisamBase.Shell/App.xaml.cs`)에 전역 예외 그물을 놓았다. 화면 스레드에서 - 예외가 새어 나오면 WPF 는 프로그램을 그대로 끝내 버린다. 현장 장비에서 버튼 하나 잘못 - 눌렀다고 프로그램이 사라지면 무슨 일이 있었는지도 남지 않는다. 이제 로그에 남기고 - 알림창을 띄운 뒤 계속 돈다. 조용히 삼키지는 않는다 - 알리지 않으면 "눌렀는데 - 아무 일도 안 일어난다"가 되어 더 나쁘다. - 이 저장소만이 아니라 플랫폼을 쓰는 모든 프로그램에 적용된다. - -**검사**: `PathTest` 31건 신설(슬래시 섞인 경로 · 따옴표 · 못 쓰는 글자 · 없는 드라이브 · -아주 긴 경로 · 드라이브 뿌리). 전체 356 → **387건**. - ---- ## 4차 — 로컬 머지툴 사용성 (2026-09-09 ~) -지시서: `doc/작업지시-4차.md`. 기능을 늘리는 것이 아니라 매일 쓰는 사람의 걸림돌을 없애는 것이 목적이다. +진행 기록: [history/2026-09-10-4차.md](history/2026-09-10-4차.md) +지시서: [작업지시/4차.md](작업지시/4차.md). 기능을 늘리는 것이 아니라 매일 쓰는 사람의 걸림돌을 없애는 것이 목적이다. ### A. 작업한 것이 사라지는 문제 @@ -688,7 +48,7 @@ System.ArgumentException | 상태 | 항목 | |---|---| -| 대기 | D-1 `doc/history/` 를 만들고 진행상황을 나눈다 | +| **완료** | D-1 `doc/history/` 를 만들고 진행상황을 나눈다 | | 대기 | D-2 앞으로의 기록 규칙을 정한다 | --- @@ -697,7 +57,6 @@ System.ArgumentException 디테일이라 지금 붙잡을 일이 아니다. -- 최근 파일 목록 - 로그인·권한 - 대용량 파일 성능 (수만 줄) — 실제 소스 크기를 확인한 뒤 판단 @@ -705,17 +64,11 @@ System.ArgumentException --- -## 완료 기록 +## 지난 기록 -| 날짜 | 항목 | 커밋 | -|---|---|---| -| 2026-09-09 | **3차 A 묶음** — 폴더 고르기 다운 · 화면 이동 결함 3가지 · 저장소 최상위 열기 | `5e667af` `af27800` `93a507e` `b5bd06c` / 플랫폼 `595aa23` `f664c31` | -| 2026-09-09 | **3차 B 묶음** — 3-Way 가로·세로 막대 · 행 어긋남 · Take Mine | `179dffb` | -| 2026-09-09 | **3차 C·E 묶음** — 코드 툴팁 · 글자 잘림 · 폴더 복사 확인 창 · 세로 막대 · 폭 되돌림 · 머리 빈 공간 · 반영 색 · 코드 겹침 | `9a9c431` `8def1ad` `05c74fc` `4f33cac` / 플랫폼 `5522ca0` | -| 2026-09-09 | **3차 D 묶음** — 주석만 다름 · C++ 전처리 · 3-Way 덩어리 반영 · 결과 내보내기 · 폴더 안 전체 검색 · 끌어다 놓기 | `5c8c942` `451b624` `32c0146` `ee5d34c` `373990b` `196af0a` | -| 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 | **⑤ 밖에서 부르기** — 명령줄 인자 · 탐색기 우클릭 | 아래 | +| 날짜 | 문서 | +|---|---| +| 2026-09-09 | [3차 — 버그 정리 + 기능 확장](history/2026-09-09-3차.md) — 스물세 항목, 전부 끝 | +| 2026-09-08 | [2주차 — 폭 넓히기](history/2026-09-08-2주차.md) — 기능 덩어리 ①~⑤ | + +한 줄 요약은 [history/changelog.md](history/changelog.md) 에 있다.