훑기 - 가리기 - 세기로 나눴다. 훑으면서 그 자리에서 파일을 열어
견주던 것을, 트리를 먼저 세우고 가리는 일만 여러 갈래로 돌린다.
결과 차례는 트리가 정하므로 몇 갈래로 돌리든 같은 순서로 나온다.
세는 일만은 한 갈래로 한다. 파일이 64개 아래면 나누지 않는다 -
그 아래는 갈래를 세우는 값이 더 든다.
폴더를 훑는 방식도 바꿨다. Directory.GetFiles 는 이름만 주므로
파일마다 File.Exists 와 FileInfo 를 양쪽에 다시 물었다. 4,500개면
만 팔천 번이다. DirectoryInfo.GetFiles 는 크기와 수정 시각이
딸려 오므로 한 번으로 끝난다. 974ms 가 89ms 가 됐다.
재는 데 쓴 묶음은 우리가 만들었다(실제 소스를 열지 않는다).
2026-09-10 에 재 둔 모양을 흉내 냈고 씨앗을 고정해서 몇 번을
돌려도 똑같다.
전체 비교 3,077ms -> 901ms (3.4배)
빠른 비교 1,832ms -> 392ms (4.7배)
앱에서 기다리는 시간 2,078ms -> 1,086ms (1.9배)
결과는 글자 하나까지 같다. 앱 쪽이 덜 줄어든 것은 비교가 끝난 뒤
화면 트리를 세우는 데 따로 드는 시간 때문이고, 거기는 손대지 않았다.
빠른 비교를 기본으로 켜지도 않았다 - 틀릴 수 있는 방법이다.
FolderTest 41 -> 51 (갈래 나누는 길, 열 번 돌려 같은 결과, 취소).
전체 698건 통과(하네스 20벌). 경고 0 · 오류 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
여기는 머지툴을 만드는 자리다. 남의 저장소를 읽을 까닭이 더 남아
있지 않고, 읽는 동안 사고가 날 여지만 있다. 이미 받아 둔 숫자
(XAML 400개, CP949 67개, 4,503개에 12.2초)는 그대로 쓴다.
B-2 와 C-1 을 그에 맞게 다시 썼다. 둘 다 큰 재료가 필요한데,
이제 우리가 만들어 쓴다. 만든 묶음은 몇 번을 돌려도 같은 값이
나와서 재기에는 오히려 낫다. C-1 에는 그 한계도 같이 적었다 -
만든 소스에는 진짜 코드의 지저분함이 없다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
실제 소스에 XAML 이 400개 42,477줄인데 여태 주석과 따옴표만 알아봐서
사실상 평문이었다. 마크업 전용 토크나이저를 따로 뒀다.
기존 토크나이저에 못 얹은 까닭은 규칙이 다르기 때문이다. 그쪽은 낱말을
읽어 예약어 표와 맞춰 보는데, 마크업은 같은 Content 가 꺾쇠 안에서는
속성 이름이고 밖에서는 그냥 글자다. 낱말이 무엇이냐가 아니라 어디에
있느냐로 갈린다.
줄 넘김 상태를 bool 하나에서 깃발 묶음(CarryOver)으로 넓혔다. XAML 은
여는 태그가 여러 줄에 걸치는 것이 보통이라 - 시험에 쓴 실제 파일은 첫
태그 하나가 14줄이다 - "지금 꺾쇠 안이다"를 안 넘기면 이어지는 줄의
속성이 그냥 글자로 보인다. 부르는 자리 다섯을 모두 바꿨다.
색은 새로 만들지 않고 이미 있는 갈래에 얹었다. 태그는 예약어 색,
속성 이름은 메서드 색, 값은 문자열 색, 앞가지는 타입 색이다. 태그
밖의 본문은 일부러 안 칠한다.
붙이는 중에 파일 비교 화면이 통째로 비어서 뜨는 일이 있었다. 의존
속성 타입을 bool 에서 CarryOver 로 바꾸면서 기본값 false 를 같이 안
고쳤다. 빌드도 통과하고 로그에도 안 남는다. 같은 주의를 코드 옆에
적어 뒀다.
실제 저장소 XAML 사본(638줄)으로 화면 확인. C# 색칠은 그대로.
MarkupTest 65건. 전체 688건 통과(하네스 20벌). 경고 0 · 오류 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
커밋·체크아웃 뒤에 저장 안 한 편집이 말없이 사라지던 것을 막았다.
막는 자리를 옮겼다. 목록을 다시 세우는 자리(RefreshCommits →
LoadCommitAsync)가 아니라 Git 작업이 시작되는 문(GitWorkspace.
RunAndRefreshAsync)에서 묻는다. 그 순간에는 아직 아무것도 바뀌지
않았으므로 취소가 뜻을 가진다.
작업을 가려서 묻는 쪽은 접었다. fetch·태그·스테이징처럼 파일을 안
건드리는 열두 가지는 안 물으려 했는데, 해 보니 그런 작업 뒤에도
편집이 사라졌다. 파일이 그대로여도 저장소를 다시 읽으면서 본문을
새로 그리기 때문이다. 다시 그리지 않게 막는 쪽도 해 봤지만 고른
파일까지 바뀌는 경우가 있어 조건을 세울 수 없었다. 잃지 않는 쪽으로
갔다 - 확인 창은 고치던 것이 있을 때만 뜨고, 틀려도 한 번 더 묻는
것으로 끝난다. 알아낸 열두 이름은 KnownSafeCandidates 에 남겼다.
덤으로 4차 A-1 이 새던 자리 셋을 막았다. 목록을 다시 세우면 화면이
값을 되밀어 넣는데, 그것을 사람이 고른 것으로 보고 확인 창을 띄우고
있었다. OnSelectedNodeChanged(null·같은 커밋), OnRemoteBranchChanged
(_syncingBranches), Files.Clear()(_suspendGuard 안으로).
화면 시험 10건 통과(데모 저장소). GitOpTest 36건. 전체 623건 통과
(하네스 19벌). 빌드 경고 0 · 오류 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
실제 저장소 2091개를 MergeHub 로 읽어 봤다. CP949 67개 포함해 깨짐 0, 저장 왕복 어긋남 0.
판별 코드가 TextFile 과 GitService 두 군데에 복사돼 있던 것을 한 곳으로 모았다.
상태바가 KS_C_5601-1987, 목록이 CP949 로 같은 것을 다르게 부르던 것도 맞췄다.
항목을 끝내면 상세는 history 날짜 파일, 진행상황은 상태만, changelog 는 한 줄.
진행 순서를 D → A → B → C 로 고쳤다. D 를 먼저 하라는 말이 D 묶음 안에만 있었다.
플랫폼 changelog 경로를 풀어 적었다. 같은 이름 파일이 둘이 되어 헷갈린다.
경로 칸(TextBox)이 Drop 을 먼저 먹어 놓아도 아무 일이 없었다 - Preview 로 옮겼다.
파일 비교의 경로 글자는 Background 가 없어 놓을 자리 자체가 없었다.
칸에 놓아도 다시 견주지 않던 것을 DropFile / DropFolder 로 고쳤다.
파일을 하나씩 놓아 짝을 만들 수 있게 했다.
검색 결과 칸에 머리글을 넣고 줄 간격을 목록과 맞췄다.
결과를 남에게 보내거나 결재에 붙일 때 화면을 캡처하는 것 말고는 방법이 없었다.
캡처는 검색도 안 되고 줄 번호를 짚어 말하기도 어렵다.
Services/CompareReport.cs 신설. 파일 비교와 폴더 비교 양쪽에서 쓴다. 글을
만드는 일만 여기서 하고, 어디에 저장할지 묻는 것은 화면 쪽 일이다. 그래야
화면 없이 검사한다.
파일 쪽은 요약과 다른 줄 목록을 적는다. 줄 번호는 양쪽을 다 적는다. 한쪽에만
있는 줄이 있어 번호가 어긋나므로 하나만 적으면 파일에서 그 줄을 찾을 수 없다.
폴더 쪽은 상태와 상대 경로를 줄마다 적는다. 폴더 자체는 적지 않는다. 그 안의
파일이 이미 다 적혀 있다.
곁들여 요약에 주석만 다른 수를 덧붙였다. "다름 3(주석만 1)". D-1 로 새로 생긴
갈래가 요약에는 안 보였다.
확인: 두 화면에서 각각 뽑아 메모장으로 열었다. ReportTest 23건 신설.
전체 451건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
WeldController.cpp 를 3-Way 로 열면 BASE 칸이 통째로 비어 보였다. 고장이
아니다. 두 브랜치의 공통 조상(9b48b82)에는 그 파일이 아예 없다. master 와
feature/cpp-tuning 이 각자 새로 만든 파일이라 견줄 조상이 없다. 그래서 모든
줄이 "양쪽에서 새로 넣은 줄"이 되고 충돌 6개가 나온다.
다만 화면이 그 사실을 말해 주지 않았다. 빈 칸은 누가 봐도 고장으로 읽힌다.
BASE 머리글에 "이 파일 없음 - 양쪽에서 새로 만든 파일입니다" 라고 적는다.
조상에 파일이 있으면 지금까지처럼 파일 이름이 나온다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
폴더 비교에 "주석만 다름" 상태를 새로 두었다. 코드는 같고 주석/공백만 다른
파일이다. 봐야 할 성격이 다르다. 주석만 손댄 파일은 눈으로 훑고 넘길 수
있는데 다른 것과 같은 색으로 섞여 있으면 전부 열어 봐야 한다.
판단은 주석 토큰을 걷어내고 공백을 지운 뒤 견주는 것이다. 주석 기호는 언어마다
다르므로 확장자로 고른 언어표를 따르고, 언어를 모르는 파일은 하지 않는다.
양쪽 확장자가 다르면 같은 언어로 볼 근거가 없으니 역시 하지 않는다.
줄 구분은 코드가 있는 줄에만 남긴다. 큰 파일은 건너뛴다.
곁들여 색 뜻풀이 줄을 목록 위에 붙이고, 보기 필터에 "코드가 다른 것만"을
더했다. 주석만 다른 것을 빼고 보는 쪽이 실제로 필요한 동작이다.
확인: 주석만 고친 .cs 는 회색 "주석만 다름", 값이 바뀐 .cs 와 평문 .txt 는
노란 "다름". "코드가 다른 것만"을 고르면 앞의 것이 빠진다.
FolderTest 4건 추가. 전체 428건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
D-2. C++ 문법 강조는 이미 되고 있었다. LanguageProfile.CFamily 가 확장자
아홉 가지를 맡고 예약어/주석/문자열도 들어 있다. 빠진 것은 전처리 지시문
하나였다. #include 가 # 과 include 로 쪼개져 보통 낱말처럼 칠해졌다. 줄
첫머리가 # 이면 그 줄 전체를 지시문 색으로 칠한다. C# 쪽과 같은 규칙이다.
언어표에 hasPreprocessor 를 두어 C 계열에서만 켠다.
D-5. 2-Way 는 이미 됐다. 3-Way 가 잘못 동작하고 있었다. 덩어리를 BASE 에서
가져오고 있었는데 3-Way 에서 BASE 는 공통 조상이다. 저쪽(REMOTE)을 가져오는
것이 Take Theirs 이고 덩어리 반영은 그것을 줄 묶음으로 하는 것인데, BASE 를
가져오면 조상으로 되돌리는 셈이라 뜻이 전혀 달라진다. 덩어리를 묶는 기준도
모드에 맞췄다.
확인: 충돌 6개짜리 C++ 파일에서 다음 충돌 -> 이 덩어리 반영 한 번에 네 줄이
REMOTE 값으로 바뀌고 Conflict 6 -> 2. LangTest 12건 추가. 전체 424건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
E-5. 줄을 반영한 뒤 비교 규칙을 하나 건드리면 초록(머지 반영함)이던 줄이
보라(직접 수정)로 바뀌었다.
반영 표시가 되돌리기 스택에만 있었다. 규칙을 바꾸면 화면을 통째로 다시
만들면서 그 스택을 비운다. 그러면 표시가 사라지고 남는 단서는 "글이 원본과
다르다"뿐이라 직접 수정으로 칠해진다. B-3 에서 Take Mine 이 충돌을 못 지우던
것과 같은 뿌리다.
- 반영할 때 줄 번호로도 남긴다. BASE 줄 번호, 없으면 MINE 줄 번호.
- 다시 만들 때 반영 여부를 먼저 본다. 반영한 줄은 직접 수정 조건에도 걸린다.
- 되돌리면 그 기록도 함께 지운다.
곁들여 머지 주석 줄도 같은 이유로 보라색이 됐다. 주석 줄이라는 표시 역시 줄
객체에만 있어서 다시 만들면 사라진다. 글을 보고 알아보게 했다
(MergeNote.LooksLikeNote).
E-6. Alt+위/아래로 차이를 옮겨 다니면 칸 아래 상태바 위에 코드가 겹쳐
그려졌다. 표시층은 편집기 글자와 눈높이를 맞추려고 RenderTransform 으로 살짝
밀어 두는데, 그건 배치가 아니라 그리기라 칸 밖으로 나가도 막는 것이 없다.
격자의 행도 자기 칸 밖으로 나가는 자식을 자르지 않는다. 코드가 놓이는 자리를
자르는 상자로 감쌌다. 네 칸 모두.
확인: 반영 후 공백/Aa 규칙을 눌러도 머지 반영함 2 · 직접 수정 0 그대로.
Alt+아래 8회, Alt+위 6회 눌러도 마지막 줄이 경계에서 깨끗이 잘린다.
NoteTest 13건 추가. 전체 399 -> 412건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
C-1. 코드 위에 마우스를 올리면 그 줄 내용이 그대로 툴팁으로 다시 떴다. 글이
이미 보이는데 같은 글이 상자로 겹쳐 뜨니 가리기만 한다. 원본 세 칸에서 뺐다.
버튼과 상태 아이콘 툴팁은 그대로 둔다. 그쪽은 화면에 없는 정보를 알려 준다.
C-2. Git 탭 목록의 글자가 ... 없이 중간에서 뚝 끊겼다. 줄임표 설정은 이미
있었는데 듣지 않았다. 목록의 가로 스크롤이 켜져 있으면 항목이 제 글 길이만큼
넓게 잡히고, 그러면 잘릴 일이 없으니 줄임표도 나오지 않는다. 가로 스크롤을
끄면 항목이 칸 너비에 갇히고 그때 비로소 줄임표가 나온다. 커밋/변경 파일/
브랜치/충돌/태그/서브모듈 여섯 목록에 같이 걸었다.
"작업 취소" 버튼과 Git 탭 입력 칸이 잘리는 것은 재현하지 못했다. 2560x1600 과
1920x1080 에서 배율 100%/120% 로 여섯 하위 탭을 모두 봤지만 멀쩡했다.
C-3. 폴더 복사 확인 창을 윈도우 기본 대화상자에서 플랫폼 것으로 바꿨다.
글꼴도 색도 배치도 혼자 달라 "이 프로그램이 묻는 것"으로 읽히지 않았다.
묻는 것 자체는 없애지 않았다. 복사/삭제는 되돌릴 수 없다.
확인: 코드 위 3초 머물러도 툴팁 없음. 이력 목록이 ... 로 줄어듦. 폴더 복사
확인 창과 완료 토스트가 프로그램의 다른 알림과 같은 모양.
테스트 399건 그대로 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3-Way 에서 구분선을 끌어 칸 폭을 바꾼 뒤 2-Way 로 돌아오면 LOCAL 오른쪽에 칸
하나만 한 빈 자리가 남았다. 구분선을 건드리지 않으면 멀쩡했다.
GridSplitter 는 끌고 나면 양옆 칸을 비율(star)이 아니라 고정 픽셀로 바꿔 놓는다.
그러면 창 크기가 바뀌어도 칸이 따라 늘지 않고, 2-Way 로 돌아와 REMOTE 칸이
접히면서 생긴 자리를 아무도 채우지 않는다. 늘어날 star 칸이 없기 때문이다.
- 구분선을 놓는 순간 고정 픽셀을 같은 비율의 star 로 되돌린다. 잡아 놓은 폭은
유지되면서 창 크기를 따라간다.
- 2-Way <-> 3-Way 를 오갈 때는 세 칸의 폭 지정을 놓아준다. 배치가 통째로 다르고,
REMOTE 칸은 폭을 Style 이 정하는데 끌어서 생긴 값이 남으면 2-Way 에서 접히지
않는다.
확인: 3-Way 에서 구분선을 끌고 2-Way 로 돌아와도 두 칸이 폭을 꽉 채운다.
테스트 399건 그대로 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
E-1. 원본 세 칸에 세로 스크롤 막대를 켰다.
가로와 달리 세로는 이 칸들이 실제로 굴러가므로 ScrollViewer 가 주는 막대를
그대로 쓴다. 대신 막대가 자리를 차지하는 만큼 여백 보정을 걷어냈다. 전에는
편집기 막대 폭만큼 오른쪽 여백을 줘서 글자 폭을 맞췄는데, 이제 진짜 막대가 그
자리를 차지하므로 여백까지 주면 글자 폭이 좁아져 접히는 자리가 어긋난다.
E-2. 칸 폭을 줄였다 늘리면 행 높이가 돌아오지 않던 문제.
행 높이는 각 칸이 알려 온 글 높이 중 가장 큰 것을 쓴다. 그런데 글자를 그리는
칸이 세로로 늘어나 있어서(WPF 기본값 Stretch) 행이 한 번 커지면 그 다음부터는
"행 높이만큼 높다"고 되돌려 보냈다. 자기가 만든 값을 자기가 다시 읽는 셈이라
높이가 올라가기만 하고 내려오지 않았다. 글자 칸을 위로 붙여 자기 글 높이만
알리도록 고쳤다.
E-3. 경로 줄과 코드 사이의 빈 공간.
패널 머리 높이를 상수로 못 박아 두어(124 / 74) 실제 내용보다 훨씬 컸다.
상수를 없애고 내용에 맞춰 줄어들게 한 뒤, 나란히 놓인 칸끼리는 SharedSizeGroup
으로 가장 큰 것에 맞춘다(REMOTE 만 Conflict 줄이 하나 더 있다). 2-Way 에서는
MERGED 머리도 같은 무리에 넣고 3-Way 에서는 뺀다.
2-Way 에서 한 화면에 보이는 코드가 29줄에서 34줄로 늘었다.
확인: 세로 막대를 끌면 세 칸이 함께 움직인다. 2560x1600 <-> 1920x1080 을
오가도, 가운데 구분선을 끌었다 되돌려도 처음과 같은 화면이 된다.
테스트 399건 그대로 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
B-1. 원본 세 칸에 가로 스크롤 막대 신설.
이 칸들은 ScrollViewer 를 가로로 굴리지 않고(줄 번호까지 밀려 나간다) 글자만
SourceShiftX 로 민다. 그래서 ScrollViewer 가 만드는 막대는 움직이지 않는
막대가 되어 감춰 두고 있었고, 긴 줄이 있다는 사실 자체를 알 수 없었다.
막대를 따로 놓고 SourceShiftX 와 값을 서로 맞춘다.
B-2. 워드랩을 켠 3-Way 에서 세로 스크롤이 아예 안 되던 문제.
원인이 둘이었다.
- 메아리를 막는 자물쇠를 try/finally 로 그 자리에서 풀고 있었다. 목록의 스크롤
알림은 배치가 끝난 다음에 오므로 그때는 이미 열려 있었고, 그 메아리가 다시
편집기를 굴렸다. 이제 그 일로 밀려서 오는 알림이 다 지나간 뒤에 푼다.
- 두 좌표계를 픽셀로 환산하고 있었는데, 목록이 가상화라 안 그려진 행의 높이를
알 수 없어 환산이 어긋났다. 워드랩일 때는 행 단위로 굴려 "몇 번째 행이 맨
위인가"만 주고받는다.
곁들여, 편집기 맨 위 행을 스크롤 값 나눗셈이 아니라 좌표로 물어본다.
B-3. Take Mine 을 눌러도 남은 충돌이 줄지 않던 문제.
"내 것으로 간다"는 결정은 글을 바꾸지 않아 되돌리기 스택에 남지 않는데,
RefreshAppliedFlags 가 스택만 보고 반영 표시를 다시 칠하면서 그 표시를 지웠다.
줄 번호로 기억해 둔 결정을 같이 보도록 고치고, 같은 판단을 IsDecidedAsMine
하나로 합쳤다.
확인: 막대 끌기/가로 휠/워드랩 토글, 워드랩 켠 채 네 칸이 같은 행을 맨 위에
놓는 것, 충돌 6개를 Take Mine/Theirs 번갈아 눌러 "충돌 정리 끝"까지.
테스트 399건 그대로 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
요구를 잘못 읽었었다. Git 저장소 이야기로 보고 .git 찾아 올라가기를 만들었는데,
실제로는 폴더 비교 탭의 폴더 고르는 창 이야기였다. 상위 폴더를 골라 놓고
하위까지 한꺼번에 비교하고 싶은데 그 상위 폴더를 고를 방법이 없었다.
원인은 폴더 고르기를 파일 열기 창으로 돌려쓰고 있던 것이다. 이 방식은 지금 서
있는 폴더를 돌려주기 때문에, 목록에 보이는 폴더를 고르려면 반드시 그 안으로
들어가야 한다. 그래서 늘 가장 깊은 폴더만 골라졌다.
- 윈도우 폴더 선택 창을 셸 COM(IFileDialog + FOS_PICKFOLDERS)으로 직접 부른다.
목록에서 폴더를 한 번 눌러 고를 수 있다. COM 이 안 되면 전에 쓰던 방식으로
물러선다.
- SetDefaultFolder 가 아니라 SetFolder 를 쓴다. 전자는 윈도우가 기억한 폴더를
우선해서 지금 경로 칸에 적힌 폴더에서 열리지 않는다.
- D-4(비교 창 여러 개)는 사용자 판단으로 취소. 번호는 그대로 둔다.
확인: 위로 한 번 올라가 목록의 right 폴더를 한 번 눌러 골랐고 경로 칸에 그대로
들어왔다. 그 상태로 비교하니 doc / src / src·core / src·ui 트리가 나오고
src·core·weld.cs 까지 펼쳐진다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
두 가지가 겹쳐 있었다.
하나. 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>
원인은 경로에 섞인 슬래시였다. 폴더 칸에 C:/Users/... 처럼 슬래시가 섞인
값이 들어 있으면 Directory.Exists 는 통과시키지만, 대화상자의
InitialDirectory 는 윈도우 셸(COM)이 그대로 해석하다 ArgumentException 을
던진다. 그 예외는 대입할 때가 아니라 ShowDialog() 안쪽에서 올라오기 때문에
대입 줄만 try 로 감싸 둔 파일 비교 쪽도 똑같이 죽게 돼 있었다.
- Services/PathUtil.cs 신설. 셸에 넘길 경로는 전부 여기를 거친다.
- 폴더·파일 고르기 둘 다 ShowDialog() 를 감쌌다. 끊긴 네트워크 드라이브처럼
정규화로 걸러지지 않는 경우에는 시작 폴더 없이 한 번 더 연다.
- 3차 작업 지시서와 진행 상황 문서 신설·갱신.
검사: PathTest 31건 신설(356 -> 387건). 실제 실행으로 왼쪽·오른쪽 폴더 10회
연속, 파일 비교의 기준·내 파일 각 1회 확인.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
git config 에 mergetool/difftool 로 등록하는 쪽은 전부터 있었는데 정작 그 인자를
받는 쪽이 없어 다리가 반만 놓여 있었다. 나머지 반쪽을 놓고, 탐색기에서도 부를 수 있게 했다.
- Services/StartupArgs: 켤 때 넘어온 인자를 읽는다.
이름표 붙은 것(--merge --base B --local L --remote R --merged M)과 그냥 넘어온 경로
둘 다 받는다. 경로가 폴더면 폴더 비교로, 파일이면 파일 비교로 보낸다.
difftool 의 LOCAL/REMOTE 와 셸이 주는 "먼저 고른 것/나중 것" 은 뜻이 달라 따로 담는다 -
이 도구의 왼쪽은 늘 "기준"이라 difftool 에서는 REMOTE 가 왼쪽이 된다.
- Services/ShellIntegration: 탐색기 "보내기" 메뉴에 바로가기를 둔다.
처음에는 우클릭 메뉴에 바로 넣으려고 레지스트리에 정적 verb 를 등록했다
(HKCU\Software\Classes\AllFilesystemObjects\shell\...). 키는 제대로 들어갔고
HKEY_CLASSES_ROOT 에도 병합돼 보였는데 탐색기 메뉴에는 뜨지 않았다.
확인해 보니 이 PC 에서 우클릭 메뉴에 나오는 다른 프로그램(Notepad++ · Git Bash ·
WinMerge)은 전부 HKLM 에 등록돼 있었고, HKCU 에 넣은 것은 - 우리 것이든 확인용으로
넣어 본 메모장 항목이든 - 하나도 그려지지 않았다. HKLM 은 관리자 권한이 필요해 쓸 수 없다.
그래서 보내기(SendTo)로 갔다. %APPDATA%\Microsoft\Windows\SendTo 에 바로가기 파일
하나를 두는 것이 전부라 레지스트리도 권한도 필요 없고 윈도우 판을 타지 않는다.
한 단계 더 들어가는 것이 유일한 단점인데, 대신 여러 개를 골라 한 번에 보낼 수 있어
미뤄 두었던 "파일 두 개 골라 비교" 가 덤으로 따라왔다.
등록/해제할 때 예전 판이 레지스트리에 남긴 흔적도 같이 지운다.
셸 확장 DLL 은 쓰지 않았다 - DLL 은 탐색기 프로세스 안에서 돌아 잘못되면 탐색기가 죽는다.
- GitWorkspace.RouteStartupArgs 가 요청을 화면으로 보낸다. 창이 다 만들어진 뒤에 탭을
옮겨야 해서 ApplicationIdle 로 미루고, 요청 자체는 남겨 두어 늦게 만들어진 화면이
스스로 집어 가게 했다(폴더 비교 쪽도 파일 비교와 같은 방식으로 추가).
- 곁들여: 파일 비교가 "양쪽 경로가 모두 있을 때만" 열리고 있었다. 셸은 하나만 줄 수도
있으므로 한쪽만 있어도 열도록 고쳤다 - 없는 쪽은 빈 내용이 되어 전부 추가로 보인다.
ArgsTest 26건 신설. 전체 356건 통과.
실행 확인: 폴더 경로로 켜면 폴더 비교 탭에 그 폴더가 왼쪽에, 파일 두 개로 켜면 파일 비교
탭에 나란히. 등록 버튼을 누르면 보내기 메뉴에 항목이 생기고 버튼 이름이 "탐색기 보내기
해제" 로 바뀐다(실제 탐색기 메뉴에서 확인).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
git config 에 mergetool/difftool 로 등록하는 쪽은 전부터 있었는데 정작 그 인자를
받는 쪽이 없어 다리가 반만 놓여 있었다. 나머지 반쪽을 놓는다.
- Services/StartupArgs: 켤 때 넘어온 인자를 읽는다.
이름표 붙은 것(--merge --base B --local L --remote R --merged M)과 그냥 넘어온 경로
둘 다 받는다. 탐색기는 "%1" 하나만 주므로 후자가 그 자리다. 경로가 폴더면 폴더 비교로,
파일이면 파일 비교로 보낸다.
difftool 의 LOCAL/REMOTE 와 탐색기의 "먼저 고른 것/나중 것" 은 뜻이 달라 따로 담는다 -
이 도구의 왼쪽은 늘 "기준"이라 difftool 에서는 REMOTE 가 왼쪽이 된다.
- Services/ShellIntegration: 탐색기 우클릭 메뉴를 파일·폴더·폴더 배경 세 자리에 넣는다.
HKEY_CURRENT_USER 에만 쓰므로 관리자 권한이 필요 없고, 지울 때도 이 사람 것만 지운다 -
사내 PC 에 권한 없이 깔리는 것이 전제다. 셸 확장 DLL 을 만들지 않은 것도 같은 이유로,
DLL 은 탐색기 프로세스 안에서 돌아 잘못되면 탐색기가 통째로 죽는다.
폴더 배경에서는 %1 이 아니라 %V 가 그 폴더다.
- GitWorkspace.RouteStartupArgs 가 요청을 화면으로 보낸다. 창이 다 만들어진 뒤에 탭을
옮겨야 해서(그 전에는 옮길 곳이 없다) ApplicationIdle 로 미루고, 요청 자체는 남겨 두어
늦게 만들어진 화면이 스스로 집어 가게 했다(폴더 비교 쪽도 파일 비교와 같은 방식으로 추가).
- 곁들여: 파일 비교가 "양쪽 경로가 모두 있을 때만" 열리고 있었다. 탐색기는 하나만 주므로
한쪽만 있어도 열도록 고쳤다 - 없는 쪽은 빈 내용이 되어 전부 추가로 보인다.
ArgsTest 26건 신설. 전체 356건 통과.
실행 확인: 폴더 경로로 켜면 폴더 비교 탭에 그 폴더가 왼쪽에, 파일 두 개로 켜면
파일 비교 탭에 나란히. 등록 버튼을 누르면 레지스트리 3개 키가 생기고 버튼 이름이
"탐색기 우클릭 해제" 로 바뀐다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"줄 단위로 비교할 수 없습니다" 한 줄로 끝내면 정작 알고 싶은 "같은지 다른지" 를
모른 채 화면을 닫게 된다. 답이 있는 화면으로 바꿨다.
- 크기 · SHA-1 · 처음 갈리는 바이트 위치를 한 줄로 알려 준다. 크기가 같아도 내용이
다를 수 있어 해시로 본다. 앞부분이 같고 길이만 다르면 짧은 쪽이 끝나는 자리가
갈리는 지점이다.
- 그림(.png/.jpg/.bmp/.gif/.ico/.tif)이면 나란히 띄운다 - 16진으로 보는 것보다 훨씬 빠르다.
- 그 밖에는 앞 4KB 를 16진으로 나란히 놓고, 16바이트 단위로 다른 줄에만 색을 준다.
볼 수 있는 글자는 오른쪽 칸에 그대로 적는다 - 16진만 보면 무슨 파일인지 모른다.
수십 MB 짜리를 전부 그리면 화면이 굳으므로 앞부분만 그리고 잘렸다고 알린다.
BinaryTest 25건 신설(같음·가운데 다름·길이 다름·16진 표기·줄 수 제한·글자 칸·
없는 파일·그림 판별). 전체 330건 통과.
실행 확인: .bin 두 개 -> "다릅니다 · 왼쪽 17 B · 오른쪽 19 B · 처음 갈리는 곳 0x10"
+ 16진 두 줄(0x10 줄만 색). .png 두 개 -> 나란히 표시.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- 바꾸기: 찾기 칸 아래에 바꿀 말 줄이 붙는다. Ctrl+F 로 열면 안 뜬다 -
찾기만 할 때 자리를 뺏지 않기 위해서다. "바꾸기" 는 고른 자리가 찾는 말일 때만
바꾸고 다음으로 넘어간다 - 엉뚱한 자리를 덮어쓰는 것보다 한 번 더 누르게 하는 편이 낫다.
"모두" 는 한 번에 바꾸고 몇 개인지 알려 준다(되돌리기는 편집기의 Ctrl+Z 가 맡는다).
- 덩어리 단위 반영: 줄 하나씩만 되면 연속 다섯 줄을 다섯 번 눌러야 한다.
WinMerge·Beyond Compare 가 덩어리를 기본 단위로 쓰는 이유다. 덩어리는
"차이로 잡힌 줄이 이어지는 구간"이고, 캐럿이 그 안에 있을 때만 켜진다. Alt+Shift+→.
- 저장 형식: 사내 소스에 CP949 로 저장된 파일이 섞여 있어 새로 만드는 파일과 형식이
갈린다. 읽어서 보여 주기만 하면 그 갈림을 못 고치므로 저장할 때 고를 수 있게 했다.
기본값 "원본 유지" 라 지금까지와 동작이 같다. Save & Commit 경로도 같은 값을 쓴다
(전에는 File.WriteAllText 로 UTF-8 고정이었다).
실행 확인: PulseUs → PulseMicros 2개 바꿈 · 마지막 차이에서 2줄 한 번에 반영.
단축키 61 → 63건, 전체 305건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
비교만 되고 정리는 안 되던 화면이었다. 셋을 채워 "보고 → 고르고 → 맞춘다" 까지 닫는다.
- 폴더째 복사·삭제: 파일 하나씩만 되던 것을 폴더에도 열었다. 확인 창에 안에 든
파일 수를 적는다 - "폴더 하나"라고만 물으면 몇 개가 덮이는지 모르는 채 확인을
누르게 된다. 복사는 겹치는 파일만 덮고 대상에만 있던 파일은 남긴다(동기화가 아니다).
- 보기 필터: 다른 것만 / 전체 / 왼쪽에만 / 오른쪽에만 / 같은 것만.
다시 비교하지 않고 마지막 결과로 목록만 다시 그린다 - 큰 폴더를 다시 훑는 것은
필터를 만지는 값으로 너무 비싸다. 기존 "같은 파일도 보기" 체크는 이 필터가 대신한다.
- 비교 취소: FolderComparer.Compare 에 CancellationToken 을 넣고 항목마다 확인한다.
누르는 즉시 멈추고, 앞서 본 결과는 그대로 둔다(실패가 아니라 사용자의 결정이다).
설계 하나 - 파일을 실제로 옮기고 지우는 부분을 Services/FileOps.cs 로 뺐다.
되돌릴 수 없는 일이라 화면 없이 검사할 수 있어야 한다.
실행 확인: sub(파일 2개) 폴더째 복사 → b\sub\deep\two.txt 까지 따라옴.
목록 15개 → "왼쪽에만" 5개(요약 줄은 그대로 = 다시 비교하지 않음).
FolderTest 27 → 37건(취소 3 · 파일 세기 2 · 폴더 복사 5). 전체 300건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
지금까지는 안 겹치는 변경까지 사람이 하나씩 눌러야 해서, 정작 판단이 필요한
서너 줄이 그 속에 묻혔다. git merge 가 자동으로 해 주는 일을 여기서도 해 준다.
- 자동 병합: 저쪽만 고친 줄(ThreeWayState.RemoteChanged)을 한 번에 가져온다.
충돌과 내가 고친 줄은 건드리지 않는다. 이미 가져온 줄은 더 이상 RemoteChanged 가
아니므로 여러 번 눌러도 안전하다.
- 충돌만 이동: Alt+Shift+↑↓. "다음 차이" 와 달리 아직 안 정한 충돌만 딛는다.
- 완료 판정: ACTIONS 에 "남은 충돌 N개"(빨강) → "충돌 정리 끝"(초록).
판단이 하나 필요했다 - 충돌 여부는 BASE·MINE·REMOTE 를 견주면 저절로 나오지만,
"내 것으로 간다"고 정한 줄은 내용이 그대로라 여전히 충돌로 보인다. 그래서
MergeLine.NeedsDecision(충돌이면서 반영·수정 표시가 없는 줄)을 따로 두었다.
그 결정은 글을 바꾸지 않아 편집 뒤 재정렬하면 사라지므로, 얼어 있는 BASE 줄 번호
(없으면 MINE 줄 번호)로 붙들어 되살린다.
곁들여 고친 것: Take Mine / Take Theirs 버튼이 줄을 옮겨도 켜짐·꺼짐이 그대로였다.
CommunityToolkit 커맨드는 CommandManager 를 구독하지 않는데 이 둘만 갱신 목록에서
빠져 있었다 - 키보드(Alt+←/→)로는 되고 버튼만 안 되던 이유다.
실행 확인(mergehub-demo, 3-Way = feature/cooling): 자동 병합으로 Theirs only 1→0,
다음 충돌+Take Theirs 를 반복해 남은 충돌 3→2→1→"충돌 정리 끝".
테스트 290 → 293건 전부 통과(단축키 3건 추가).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
기획안 4p 의 "Git 조작" 항목 중 화면에 없던 것들을 채웠다. 다섯 중 넷은
GitService 에 이미 구현돼 있었고 버튼만 없던 것이라, 서비스를 새로 만든 것은
변경분 추출 하나뿐이다.
- 커밋 수정(amend): 오타 난 메시지나 빠뜨린 파일을 새 커밋을 쌓지 않고 바로잡는다.
고쳐 쓸 메시지를 처음부터 다시 치지 않게 "마지막 메시지" 버튼을 곁들였다.
- stash 목록: 저장/적용만 있어 "맨 위 것"에만 손이 닿았다. 골라서 적용·삭제한다.
하나 꺼내면 번호가 한 칸씩 당겨지므로, 목록이 바뀌면 고른 항목을 다시 잡는다 -
안 그러면 다음 삭제가 엉뚱한 것을 지운다.
- 두 커밋 사이 비교: "여기부터" 로 시작점을 잡으면 그 뒤 고르는 커밋이 끝점이 된다.
안 잡으면 지금까지처럼 부모와 비교한다(기존 동작 유지).
- blame: 머지 중에 "이 줄 왜 이렇게 됐지"를 묻게 되는데, 이력을 뒤지는 것보다 빠르다.
- 변경분 파일 추출: 장비에 소스를 통째로 넣지 못하는 현장에서 바뀐 것만 들고 갈 때 쓴다.
텍스트로 다시 쓰면 줄바꿈과 인코딩이 바뀌므로 바이트 그대로 옮긴다.
곁들여 고친 것: 상태 배지 줄의 ItemHeight 가 24 로 묶여 있어 "mergetool 등록"
버튼이 반쯤 잘려 보였다. 줄 높이는 내용이 정하게 두었다.
실행 확인(데모 저장소): 메시지 수정으로 sha 가 바뀜 · stash 저장→목록→적용으로
워킹트리 복구 · 헤더가 "2766bc5 → 6e6bb78 (2개 파일)" · blame 31줄 전부 표시 ·
변경분 2개 파일 추출.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
작업 하나가 끝날 때마다 상태를 고치고 커밋한다. 대기/진행/완료로 구분해
"지금 어디까지 왔나"를 한 눈에 본다.
이번 주는 폭을 넓히는 주다 - 한 덩어리를 깊게 파는 대신 Git 조작·3-Way·
폴더 비교·파일 비교를 각각 "있다"고 말할 수 있는 선까지 올린다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>