Ubisam-MergeTool/doc/history/2026-09-11-5차.md
ksj09406 238910634c 5차 A-2: Git 작업을 시작하는 자리에서 묻는다
커밋·체크아웃 뒤에 저장 안 한 편집이 말없이 사라지던 것을 막았다.

막는 자리를 옮겼다. 목록을 다시 세우는 자리(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>
2026-09-11 13:21:33 +09:00

8.2 KiB

5차 — 실제 소스에 맞추기 (2026-09-11 ~)

지시서: 작업지시/5차.md


A-1. CP949 파일 67개를 잘못 읽으면 한글이 깨진다 — 완료 (2026-09-11)

지시서가 먼저 확인만 하고 틀리면 고치라고 했다. 확인부터 했다.

잰 것 — 실제 저장소 2,091개 전부

TextFile.Read 로 실제 파일을 한 개씩 읽어 보는 도구를 만들어 돌렸다(원본은 읽기만 하고, 저장 왕복은 사본에만 했다).

실제 인코딩 개수
UTF-8 (BOM) 1,441
UTF-8 (BOM 없음) 353
CP949 67
ASCII 만 230
MergeHub 로 읽었을 때 결과
한글이 깨진 파일 0개
사본에 그대로 저장했을 때 바이트가 달라진 파일 (한글 파일 1,861개) 0개

판정 기준을 이미 만족하고 있었다. 판별 규칙(BOM → 엄격한 UTF-8 → 실패하면 시스템 ANSI)이 실제 소스에서 맞게 돈다. 고칠 것이 없었다.

그래도 고친 것 둘

① 판별 코드가 두 군데에 복사돼 있었다. TextFile.Detect 와 GitService.DetectEncoding 이 글자 하나까지 같은 코드였다. TextFile 주석에는 "판별 규칙은 GitService 와 같다 - 같은 파일을 어느 경로로 열든 결과가 같아야 한다" 고 적혀 있었는데, 지키는 방법이 복사뿐이었다. 한쪽만 고치면 조용히 어긋나고, 그러면 같은 파일이 워킹트리에서는 멀쩡한데 커밋에서 열면 깨져 보인다. 어느 쪽이 맞는지 알 방법도 없다. TextFile.DetectEncoding 하나로 모으고 GitService 는 그것을 부른다.

② 같은 인코딩을 두 이름으로 부르고 있었다. 상태바는 KS_C_5601-1987(코드페이지 949 의 공식 이름), 저장 형식 목록은 CP949. 실제 CP949 파일을 열어 보다가 눈에 띄었다 - "지금 이 파일이 그 CP949 라는 건가" 를 되묻게 된다. FriendlyName 으로 949 는 언제나 CP949 라고 적는다. 두 자리 모두 고쳤다.

확인한 것

확인 결과
실제 CP949 파일(AXA.cs 사본)을 열었을 때 //========== 보드 및 모듈 정보 확인 함수 ... 그대로 읽힌다
깨진 글자(U+FFFD) 없다
상태바 인코딩 CP949 (전에는 KS_C_5601-1987)
고친 뒤 실제 저장소 2,091개 재확인 깨짐 0개 · 왕복 어긋남 0개 (고치기 전과 같다)

검사를 새로 만들었다(EncTest 16건). 실제 저장소가 없어도 도는 것이 요점이다 - BOM · BOM 없는 UTF-8 · CP949 · ASCII 를 만들어 놓고 읽기 · 왕복 저장 · 형식 바꿔 저장까지 본다. 규칙을 여기 못박아 두면 다음에 누가 고쳐도 바로 걸린다.

전체 587건 통과(하네스 18벌) · 빌드 경고 0 · 오류 0. 실제 저장소는 건드리지 않았다 - 작업 전후 모두 변경 1건(7월부터 있던 것)으로 같다.


A-2. 커밋·체크아웃 뒤에 저장 안 한 편집이 조용히 사라진다 (2026-09-11, 완료)

어디서 사라지고 있었나

4차 A-1 에서 편집이 말없이 사라지던 길 여덟 군데를 막았지만, Git 탭은 그대로 두었다. RefreshCommits → LoadCommitAsync 는 프로그램이 목록을 다시 세우는 한가운데라 거기서 확인 창을 띄우면 무엇에 답하는 창인지 알 수 없고, 취소해도 목록은 이미 새로 세워진 뒤였다.

그래서 막는 자리를 옮겼다. 목록을 다시 세우는 자리가 아니라 Git 작업을 시작하는 자리에서 묻는다. 그 순간에는 아직 아무것도 바뀌지 않았으므로 취소가 뜻을 가진다.

GitWorkspace.RunAndRefreshAsync 는 Git 탭의 작업 서른 가지가 모두 지나가는 문이다. 여기에 ConfirmDiscardEdits 갈고리를 달고, 파일 비교 화면이 그 갈고리를 채운다.

public async Task<bool> RunAndRefreshAsync(string operation, Action action)
{
    if (GitOperationKinds.TouchesFiles(operation) && ConfirmDiscardEdits is { } ask && !ask(operation)) return false;
    var ok = await RunAsync(operation, action);
    if (ok) await RefreshAsync();
    return ok;
}

처음 고른 답이 틀렸다 — 가려서 묻기

처음에는 작업을 가려서 물으려 했다. fetch · 태그 생성 · 전체 스테이징처럼 파일 내용을 안 건드리는 열두 가지는 묻지 않는 쪽이 낫다고 봤다. 쓸데없이 뜨는 창만큼 사람을 지치게 하는 것이 없기 때문이다.

첫 시도는 호출 자리마다 touchesFiles: false 를 붙이는 것이었는데, 줄을 훑어 고치는 스크립트가 엉뚱한 닫는 괄호(예: throw new InvalidOperationException(...))에 붙였다. 되돌리고, 판단을 이름표 하나(GitOperationKinds)로 모았다. 부르는 자리는 손대지 않는다.

그런데 화면에서 해 보니 안 묻고 지나간 뒤에도 편집이 사라졌다. 파일은 그대로인데도 저장소 상태를 다시 읽으면서 본문을 새로 그리기 때문이다.

다시 그리지 않게 막는 쪽으로도 해 봤다.

if (HasChanges && !threeWay && string.Equals(localText, _originalLocalText, StringComparison.Ordinal))
    return;

이것도 안 들었다. 다시 읽는 김에 고른 파일까지 바뀌는 경우가 있어 "안 바뀌었다" 를 정확히 가릴 조건을 세울 수가 없었다. 이 코드는 지웠다.

골라잡은 답 — 전부 묻는다

잃지 않는 쪽으로 갔다. TouchesFiles 는 이제 언제나 true 다.

  • 확인 창은 고치던 것이 있을 때만 뜬다. 평소에는 한 번도 보이지 않는다.
  • 틀리더라도 한 번 더 묻는 것으로 끝나지, 작업을 잃지는 않는다. 가려서 묻다 틀리면 사람이 고친 것을 잃는다. 두 실수의 값이 다르다.

다만 무엇이 안전한지 알아낸 것은 버리지 않았다. 열두 가지 이름을 GitOperationKinds.KnownSafeCandidates 에 남겨 두고, 왜 지금 쓰지 않는지를 클래스 주석에 적었다. "다시 그려도 편집이 살아남는" 구조가 되면 거기서부터 다시 시작한다.

덤으로 잡은 것 — 4차 A-1 이 새던 자리 셋

전부 묻게 만들고 나니 Git 탭을 그냥 열기만 해도 확인 창이 떴다. 사람이 한 일이 아닌데 4차 A-1 의 확인 창이 불려 나오고 있었다. 셋을 잡았다.

새던 자리 왜 떴나 어떻게 막았나
OnSelectedNodeChanged 커밋 목록을 다시 세우면 잠깐 null 이 밀려 들어온다 value is not null && !sameCommit 일 때만 묻는다
OnRemoteBranchChanged RefreshRemoteBranches 가 목록을 갈아 끼우며 값을 바꾼다 _syncingBranches 동안은 묻지 않는다
OnSelectedFileChanged Files.Clear() 가 가드 밖에 있었다 _suspendGuard 안으로 옮겼다

목록을 다시 세우면 화면이 값을 되밀어 넣는다. 그것은 사람이 고른 것이 아니다. 4차 A-1 을 넣을 때 놓친 것인데, A-2 를 화면에서 시험하다 드러났다.

확인한 것 (실제 화면, 데모 저장소)

확인 결과
LOCAL 을 고친 줄이 들어간다 OK
전체 스테이징이 묻는다 OK
취소하면 스테이징도 안 한다 (git diff --cached 비었다) OK
취소하면 고친 줄이 그대로다 OK
체크아웃이 묻는다 OK
취소하면 브랜치가 그대로다 (master) OK
체크아웃을 취소해도 고친 줄이 그대로다 OK
"저장하고 계속" 을 고르면 고친 내용이 파일에 남는다 OK
고친 것이 없으면 안 묻는다 OK

열 가지 전부 통과. 화면 시험은 UI Automation 으로 몰았다(a2screen.ps1). 확인 창이 따로 뜨는 창이 아니라 본 창 안에 덮어 그리는 판이라, 바깥 창 목록을 뒤지면 못 찾는다. 처음 두 번은 이것 때문에 "안 묻는다" 고 잘못 읽었다 — 시험 도구가 틀려서 제품을 의심했다.

검사를 GitOpTest 36건으로 고쳤다. 전체 623건 통과(하네스 19벌) · 빌드 경고 0 · 오류 0. 실제 저장소는 건드리지 않았다 — 데모 저장소(mergehub-demo) 사본으로만 했다.