만든 저장소(파일 303개, 충돌 22개)로 MergeHub 만으로 병합해서 커밋까지 갔다. 지시서대로 고치지 않고 적기만 했다. 제품 코드는 바꾸지 않았다. 맞게 된 것: 병합 결과가 git 과 똑같다(UU 20 AA 1 UD 1 M 7). 3분할에서 충돌 6개를 6 -> 0 으로 줄였고, 파일 단위 Accept 는 고른 쪽과 바이트까지 같다. 부모 둘인 병합 커밋이 생겼다. 걸린 것 23가지. 심각한 다섯: - 3분할로 푼 파일을 저장하면 CRLF 가 LF 로 바뀐다(커밋에 CR 0개) - Save & Commit 이 실패라는데 파일은 이미 써지고 스테이징됐다 - 충돌 파일을 고르면 상대(Theirs) 칸이 안 나온다 - 상대가 지운 파일을 3분할이 다룰 줄 모른다 - 양쪽 새 파일에 Accept Both 를 누르면 BOM 이 가운데 하나 더 들어간다 기대값은 사본에서 git 으로 먼저 받아 대조했다. 실제 소스는 열지 않았다. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
446 lines
26 KiB
Markdown
446 lines
26 KiB
Markdown
# 5차 — 실제 소스에 맞추기 (2026-09-11 ~)
|
|
|
|
지시서: [작업지시/5차.md](../작업지시/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 와 같다 - 같은 파일을 어느 경로로 열든 결과가 같아야 한다"
|
|
고 적혀 있었는데, **지키는 방법이 복사뿐이었다.** 한쪽만 고치면 조용히 어긋나고,
|
|
그러면 <b>같은 파일이 워킹트리에서는 멀쩡한데 커밋에서 열면 깨져</b> 보인다.
|
|
어느 쪽이 맞는지 알 방법도 없다. `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` 갈고리를 달고, 파일 비교 화면이 그 갈고리를 채운다.
|
|
|
|
```csharp
|
|
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`)로 모았다. 부르는 자리는 손대지 않는다.
|
|
|
|
그런데 **화면에서 해 보니 안 묻고 지나간 뒤에도 편집이 사라졌다.**
|
|
파일은 그대로인데도 저장소 상태를 다시 읽으면서 본문을 새로 그리기 때문이다.
|
|
|
|
다시 그리지 않게 막는 쪽으로도 해 봤다.
|
|
|
|
```csharp
|
|
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`) 사본으로만 했다.
|
|
|
|
|
|
---
|
|
|
|
## B-1. XAML 색칠이 사실상 없다 (2026-09-11, 완료)
|
|
|
|
### 무엇이 문제였나
|
|
|
|
실제 소스에 XAML 이 400개 · 42,477줄이다. C# 다음으로 많다.
|
|
그런데 지금까지 XAML 은 `LanguageProfile.Markup` 으로 열렸고, 그것이 아는 것은 **주석과 따옴표뿐**이었다.
|
|
태그 이름 · 속성 이름 · 속성 값이 전부 같은 색이라 사실상 평문이었다.
|
|
|
|
### 왜 기존 토크나이저에 못 얹었나
|
|
|
|
`CodeTokenizer` 는 **낱말을 읽어 예약어 표와 맞춰 보는** 방식이다. 마크업은 규칙이 다르다.
|
|
같은 `Content` 가 <b>꺾쇠 안에서는 속성 이름이고 밖에서는 그냥 글자</b>다.
|
|
낱말이 무엇이냐가 아니라 **어디에 있느냐**로 갈린다. 그래서 마크업만 따로 뒀다(`MarkupTokenizer`).
|
|
|
|
### 줄을 넘어가는 상태를 넓혀야 했다
|
|
|
|
토크나이저는 한 줄씩 따로 훑는다. 앞 줄에서 넘어오는 것은 여태 `bool inBlockComment` 하나였다.
|
|
그런데 XAML 은 **여는 태그가 여러 줄에 걸치는 것이 예외가 아니라 보통**이다.
|
|
시험에 쓴 실제 파일(`UCManualRCCOperation.xaml`)은 **첫 태그 하나가 14줄**이다.
|
|
|
|
```xml
|
|
<Button Grid.Row="1"
|
|
Content="비교"
|
|
Click="OnCompare" />
|
|
```
|
|
|
|
둘째·셋째 줄은 `<` 로 시작하지 않는다. "지금 꺾쇠 안이다" 를 넘겨주지 않으면
|
|
**속성 이름이 그냥 글자로 보인다** - XAML 은 이런 줄이 대부분이라 색칠이 없는 것과 같다.
|
|
|
|
그래서 `bool` 을 깃발 묶음(`CarryOver`)으로 넓혔다. `None` · `BlockComment` · `Tag` 셋이다.
|
|
부르는 자리 다섯(검사기 · 폴더 비교 · 화면 VM · 화면 컨트롤 · 행 모델)을 모두 바꿨다.
|
|
|
|
### 무엇을 무슨 색으로 보는가
|
|
|
|
| 무엇 | 색 | 까닭 |
|
|
|---|---|---|
|
|
| 태그 이름 | 예약어 (파랑) | 구조를 이루는 것이라 가장 먼저 눈에 들어와야 한다 |
|
|
| 속성 이름 | 메서드 (황토/연노랑) | 태그와 값 사이에서 따로 보여야 한다 |
|
|
| 속성 값 | 문자열 (빨강/주황) | 다른 언어의 문자열과 같은 색 - 같은 것이다 |
|
|
| 앞가지(`x:` · `local:`) | 타입 예약어 (청록) | XAML 에서 "어느 네임스페이스냐"는 정보다. 이름에 묻으면 `x:Name` 과 `Name` 이 같아 보인다 |
|
|
| `< > / :` | 구두점 (회색) | 뒤로 물러나야 한다 |
|
|
| `=` | 연산자 (회색) | 위와 같다 |
|
|
| `<?xml ?>` · `<!DOCTYPE>` · `<![CDATA[` | 지시문 (회색) | 파일마다 한두 줄이고 고칠 일이 없다. 안을 더 쪼개지 않는다 |
|
|
| 태그 밖의 본문 | **칠하지 않는다** | 물려받은 색 그대로 두어야 본문이 눈에 띈다 |
|
|
|
|
색을 새로 만들지 않고 **이미 있는 갈래에 얹었다.** 팔레트를 늘리면 밝은/어두운 두 벌을 다 손봐야 하고,
|
|
같은 화면에서 C# 과 XAML 이 서로 다른 색 체계를 쓰면 오히려 읽기 어렵다.
|
|
|
|
### 못 하는 것 (일부러)
|
|
|
|
속성 값이 줄을 넘어가면(닫는 따옴표가 다음 줄에 있으면) 그 줄까지만 문자열로 보고
|
|
다음 줄은 속성 자리로 돌아간다. XAML 에서 드물고, 틀려도 **색만 어긋난다.**
|
|
깃발을 하나 더 늘릴 만한 값이 아니라고 봤다.
|
|
|
|
### 하루를 날릴 뻔한 것 - 빈 화면
|
|
|
|
붙이고 나니 **파일 비교 화면이 통째로 비어서 떴다.** 단추 하나 없이 하얀 판이었다.
|
|
빌드는 경고 0 · 오류 0 이고 로그에도 아무것도 안 남았다.
|
|
|
|
까닭은 한 줄이었다. 의존 속성의 **기본값 타입이 속성 타입과 달랐다.**
|
|
|
|
```csharp
|
|
DependencyProperty.Register(nameof(InBlockComment), typeof(CarryOver), typeof(CodeTextBlock),
|
|
new FrameworkPropertyMetadata(false, OnVisualInputChanged)); // ← false 는 bool 이다
|
|
```
|
|
|
|
타입을 `bool` 에서 `CarryOver` 로 바꾸면서 **기본값을 같이 안 고쳤다.**
|
|
이러면 이 컨트롤을 처음 쓰는 순간 정적 초기화에서 터지고, 화면 전체가 안 뜬다.
|
|
빌드는 통과한다 - `object` 를 받는 자리라 컴파일러가 잡을 수 없다.
|
|
|
|
찾은 방법은 **고치기 전 상태로 되돌려 보는 것**이었다(`git stash`). 되돌리니 화면이 나왔고,
|
|
그래서 내 변경이 원인인 줄 알았다. 그 다음에 파일을 바이트로 들여다보다 `false` 를 발견했다.
|
|
같은 주의를 코드 옆에 적어 두었다.
|
|
|
|
### 확인한 것
|
|
|
|
**실제 저장소의 XAML 사본**(`UCManualRCCOperation.xaml` · 638줄)을 열어 눈으로 봤다.
|
|
|
|
| 확인 | 결과 |
|
|
|---|---|
|
|
| 14줄짜리 여는 태그의 **이어지는 줄** 속성(`d:DesignHeight` · `x:Name`) | 속성 색으로 나온다 |
|
|
| 태그 이름(`Setter` · `ControlTemplate` · `Border`) | 파랑 |
|
|
| 앞가지(`view:` · `x:` · `d:`) | 청록 |
|
|
| 속성 값(`"White"` · `"#1A22..."`) | 빨강 |
|
|
| 주석 줄(`<!--<Setter .../>-->`) | 초록 한 덩어리 |
|
|
| C# 파일 색칠 | 달라지지 않았다 (문서 주석 초록 · `public` 굵은 자홍 · 타입 청록) |
|
|
|
|
**검사를 새로 만들었다**(`MarkupTest` 65건). 여러 줄 태그 · 주석 이어짐 · 앞가지 · CDATA 와 함께,
|
|
**C# · C++ · JSON 색칠이 안 망가졌는지**도 같이 본다. 토큰이 겹치거나 거꾸로 가지 않는지도 본다.
|
|
|
|
전체 **688건 통과**(하네스 20벌) · 빌드 경고 0 · 오류 0.
|
|
**실제 저장소는 읽기만 했다** - 사본을 떠서 시험했다.
|
|
|
|
|
|
---
|
|
|
|
## B-2. 폴더 비교가 큰 저장소에서 오래 걸린다 (2026-09-11, 완료)
|
|
|
|
### 먼저 잴 것을 만들었다
|
|
|
|
실제 소스를 더 열지 않기로 했으므로(2026-09-11 결정) **시험용 묶음을 만들었다**(`FolderPerf/Corpus.cs`).
|
|
2026-09-10 에 재 둔 실제 저장소의 모양을 흉내 냈다.
|
|
|
|
| 맞춘 것 | 값 |
|
|
|---|---|
|
|
| 파일 수 | 4,500개 (양쪽 합쳐 8,922개 · 136MB) |
|
|
| 갈래 | C# 과 XAML 을 4:1 |
|
|
| 줄 수 분포 | 대부분 30~200줄, 가끔 2,000~7,500줄 |
|
|
| 폴더 깊이 | 뿌리 5개 아래 2~3단 |
|
|
| 같고 다름 | 같음 4,063 · 다름 359(주석만 294) · 한쪽만 78 |
|
|
|
|
**씨앗을 고정했다.** 몇 번을 돌려도 똑같은 묶음이 나온다. 실제 폴더는 백신·인덱서가 건드려서
|
|
잴 때마다 값이 흔들리는데, 만든 묶음은 그렇지 않다 - 고치기 전후를 견주기에는 오히려 낫다.
|
|
|
|
### 재 보니 짚이는 데가 둘이었다
|
|
|
|
**① 파일 하나마다 같은 것을 세 번 물었다.**
|
|
|
|
```csharp
|
|
Directory.GetFiles(dir) // 이름만 준다
|
|
File.Exists(leftPath) // 다시 묻는다
|
|
new FileInfo(leftPath).Length // 또 묻는다
|
|
```
|
|
|
|
`Directory.GetFiles` 는 **이름만** 준다. 그래서 파일마다 `File.Exists` 한 번과 `FileInfo` 한 번을
|
|
양쪽에 다시 물었다. 4,500개면 그것만 **만 팔천 번**이다.
|
|
|
|
`DirectoryInfo.GetFiles()` 는 목록을 얻을 때 **크기와 수정 시각이 딸려 온다.** 한 번으로 끝난다.
|
|
|
|
| 폴더 훑기만 따로 재면 | |
|
|
|---|---|
|
|
| 예전 방식 | 974ms |
|
|
| DirectoryInfo | **89ms** |
|
|
|
|
**② 파일을 여는 동안 아무것도 못 했다.**
|
|
|
|
훑으면서 그 자리에서 파일을 열어 견줬다. 그러면 한 파일을 읽는 동안 다음 폴더로 못 간다.
|
|
디스크를 기다리는 시간이 통째로 줄줄이 쌓인다.
|
|
|
|
### 어떻게 고쳤나 - 세 걸음으로 나눴다
|
|
|
|
1. **훑기** — 폴더를 돌며 트리를 세운다. 파일은 "아직 안 가린 것"으로 모아 둔다.
|
|
2. **가리기** — 모아 둔 것을 여러 갈래로 나눠 가린다. 파일을 여는 자리는 여기뿐이다.
|
|
3. **세기** — 트리 차례대로 폴더 상태를 정하고 개수를 센다.
|
|
|
|
**결과 차례는 트리가 정한다.** 몇 갈래로 돌리든 같은 순서로 나온다 -
|
|
나눠 돌린 것이 화면에 티가 나면 안 된다. 세는 일만은 반드시 한 갈래로 한다.
|
|
|
|
파일이 **64개 아래면 나누지 않는다.** 그 아래는 갈래를 세우는 값이 더 든다.
|
|
|
|
### 얼마나 빨라졌나
|
|
|
|
같은 묶음을 **한 프로그램 안에서 나란히** 돌려 견줬다. 따로 재면 디스크 캐시 상태가 달라 못 견준다.
|
|
|
|
| | 고치기 전 | 고친 뒤 | |
|
|
|---|---|---|---|
|
|
| 전체 비교 | 3,077ms | **901ms** | 3.4배 |
|
|
| 빠른 비교 | 1,832ms | **392ms** | 4.7배 |
|
|
| 앱에서 사람이 기다리는 시간 | 2,078ms | **1,086ms** | 1.9배 |
|
|
|
|
**결과는 글자 하나까지 같다.** 검사가 전후 요약을 맞춰 본다.
|
|
|
|
앱 쪽이 덜 줄어든 까닭은 **비교가 끝난 뒤 화면 트리를 세우는 데 따로 시간이 들기 때문**이다.
|
|
4,500개짜리 노드를 만드는 일이라 비교와는 다른 문제다. 여기는 손대지 않았다 -
|
|
지금도 1초 남짓이고, 건드리면 트리 동작이 바뀔 위험이 있다. 더 줄일 자리로 적어 둔다.
|
|
|
|
### 기본 동작은 바꾸지 않았다
|
|
|
|
빠른 비교(크기·수정 시각만 보기)는 **틀릴 수 있어** 그대로 사용자가 고르는 것으로 남겼다.
|
|
빨라졌다고 기본으로 켜면, 내용이 바뀌었는데 크기와 시각이 우연히 같은 파일을 놓친다.
|
|
|
|
### 확인한 것
|
|
|
|
- 전후 요약이 **글자 하나까지 같다**(전체 비교 · 빠른 비교 둘 다).
|
|
- 검사를 열 개 늘렸다(`FolderTest` 41 → 51). 파일 200개짜리로 **갈래를 나누는 길**을 지나게 하고,
|
|
**열 번을 돌려도 개수와 차례가 똑같은지**, 많은 파일에서도 **취소가 듣는지**를 본다.
|
|
- 앱을 띄워 실제로 비교해 봤다. 세 번 모두 같은 요약이 나온다.
|
|
|
|
전체 **698건 통과**(하네스 20벌) · 빌드 경고 0 · 오류 0. **실제 저장소는 열지 않았다.**
|
|
|
|
|
|
---
|
|
|
|
## C-1. 병합을 한 번 끝까지 해 본다 (2026-09-14, 완료)
|
|
|
|
지시서대로 **고치지 않고 적기만 했다.** 제품 코드는 한 줄도 안 바꿨다.
|
|
|
|
### 무엇으로 했나
|
|
|
|
실제 소스는 열지 않기로 했으므로(2026-09-11) 병합할 저장소를 만들었다. 데모는 파일이 다섯 개라
|
|
"긴 파일 여러 개에서 충돌이 여기저기 난" 상황을 한 번도 겪어 보지 못했다.
|
|
|
|
| 모양 | 값 |
|
|
|---|---|
|
|
| 파일 | 303개 (C# 240 · XAML 60 · 그 밖 3) |
|
|
| 큰 파일 | 3,000~7,000줄짜리 5개 |
|
|
| 브랜치 | `feature/mine` 에 서서 `feature/theirs` 를 MergeHub 로 병합 |
|
|
|
|
현장에서 나올 법한 충돌을 일부러 넣었다.
|
|
|
|
| | 넣은 것 | 파일 |
|
|
|---|---|---|
|
|
| A | 같은 줄을 양쪽이 다르게 고침 | 15 |
|
|
| B | 같은 파일, 다른 자리를 고침 (자동 병합되어야 함) | 7 |
|
|
| C | 양쪽이 똑같이 고침 (충돌 아님) | 3 |
|
|
| D | 큰 파일에 충돌 덩어리 6개씩 흩어 놓음 | 2 |
|
|
| E | XAML 여러 줄 태그 안에서 충돌 | 1 |
|
|
| F | 내가 고쳤는데 상대가 지움 | 1 |
|
|
| G | 양쪽이 같은 이름으로 새 파일을 만듦 | 1 |
|
|
| H | CP949 한글 파일에서 충돌 | 1 |
|
|
| I | 상대가 줄바꿈을 CRLF 에서 LF 로 바꾼 파일 | 1 |
|
|
|
|
**기대값은 사본에서 git 으로 먼저 받았다.** `git merge` 결과 UU 20 · AA 1 · UD 1 · M 7, 충돌 22개.
|
|
|
|
### 끝까지 갔다
|
|
|
|
- MergeHub 의 병합 결과가 **git 과 똑같다** (UU 20 · AA 1 · UD 1 · M 7).
|
|
- 큰 파일은 3분할로 열어 [다음 충돌] 과 Take Mine/Theirs 로 **6 → 0** 까지 줄였다. 고른 대로 들어갔다.
|
|
- 나머지는 충돌 탭의 파일 단위 Accept 로 풀었다. **고른 쪽 파일과 바이트까지 같다.**
|
|
- 22개를 다 풀자 [병합 커밋] 이 켜졌고, **부모 둘인 병합 커밋**이 생겼다. 워킹트리 Clean · 충돌 표시 남은 파일 0.
|
|
- 상대가 지운 파일은 Accept Theirs 로 지워졌다. 자동 병합 파일은 양쪽 고친 것이 다 들어갔다.
|
|
- CP949 파일은 네 칸 모두 한글이 바르게 나오고, XAML 은 네 칸 모두 B-1 색칠이 된다.
|
|
|
|
**판정 기준을 만족했다** — 만든 저장소에서 MergeHub 만으로 충돌을 풀어 커밋까지 끝냈다.
|
|
다만 **가는 길에 걸린 것이 스물세 가지**다(적을 때는 스물일곱이었는데 같은 뿌리를 합쳤다). 아래가 6차의 재료다.
|
|
|
|
### 걸린 것 — 파일이나 이력을 망가뜨리는 것
|
|
|
|
| # | 무엇 | 근거 |
|
|
|---|---|---|
|
|
| 1 | **3분할로 푼 파일을 저장하면 CRLF 가 LF 로 바뀐다.** git 에는 파일 전체가 바뀐 것으로 나온다. 상태바는 CRLF, 저장 형식은 "원본 유지"였다. CP949 파일도 같다 | 커밋에 CR 0개 (바탕은 줄마다 CRLF). 105,025B → 101,810B. 줄끝 무시하면 12줄 차이인데 3,367줄이 바뀐 것으로 나온다 |
|
|
| 2 | **Save & Commit 이 "실패"라고 하는데 파일은 이미 써지고 스테이징됐다.** 남은 충돌이 있으면 커밋에서 막히지만 그 앞 단계는 이미 끝난 뒤다 | 충돌 22 → 21. 화면은 "실패: 아직 해결되지 않은 충돌이 21건" |
|
|
| 3 | **충돌 파일을 고르면 상대(Theirs) 칸이 안 나온다.** BASE 와 내 것 두 칸뿐이다. 3-Way 콤보에서 브랜치를 골라야 세 칸이 된다. 상대가 무엇을 바꿨는지 모른 채 풀게 된다 | 충돌 갈래가 지역 변수 `threeWay` 만 켜고 `IsThreeWay` 는 안 켠다 |
|
|
| 4 | **상대가 지운 파일을 열면 REMOTE 칸이 비고, 범례는 "Theirs only 89" 라고 한다.** 실은 내 쪽에만 있는 줄이다. 진짜 물음은 "남길까 지울까" 인데 3분할에는 지우는 길도, 상대가 지웠다는 말도 없다 | 충돌 탭에만 "상대쪽 삭제" |
|
|
| 5 | **양쪽 새 파일에 Accept Both 를 누르면 바이트째 이어 붙인다.** 가운데 BOM 이 하나 더 들어가고 같은 class 가 두 번 나와 빌드가 안 된다 | 결과 파일 안에 EF BB BF 두 번 |
|
|
|
|
### 걸린 것 — 헷갈리거나 막히는 것
|
|
|
|
| # | 무엇 |
|
|
|---|---|
|
|
| 6 | "실패" 한 Save & Commit 뒤에도 화면은 **저장 안 한 편집**으로 여긴다. 다른 파일을 고르거나 Git 탭에서 Accept 를 누를 때마다 확인 창이 다시 뜬다 (2번의 다른 얼굴) |
|
|
| 7 | 그 확인 창이 **엉뚱한 파일 이름**을 댄다. 고친 것은 Unit0.cs 인데 "ToBeDeleted.cs 에 저장하지 않은 편집". 새로 고른 파일 이름을 가져다 쓴다 |
|
|
| 8 | Git 모드에는 **커밋 없이 저장하는 길이 없다.** Ctrl+S 도 Save & Commit 이다. 병합 중에 파일 하나씩 풀어 둘 수가 없다 |
|
|
| 9 | 병합 뒤 파일 비교 탭의 **파일 목록이 비어 있다.** 그 탭의 [새로고침] 을 눌러야 나온다. 앱을 새로 켜도 같다 |
|
|
| 10 | 충돌 탭 목록을 **두 번 눌러도 3분할로 안 넘어간다** |
|
|
| 11 | 안내 글이 "**홈 탭**에서 해결하세요" 라고 한다. 홈 탭은 없다 |
|
|
| 12 | 3분할의 MERGED 칸 **줄 번호가 네 자리에서 잘린다.** 2950~2959 가 전부 "295" |
|
|
| 13 | 커밋 메시지가 **처음 연 충돌 파일 것으로 굳는다.** 앱을 껐다 켜도, 병합 커밋 뒤에도 "Merge: Common/Util/Unit15.cs 충돌 해결" |
|
|
| 14 | 병합 커밋 메시지가 브랜치 이름이 아니라 **"Merge commit '1742d8f...'"** 이고, git 의 주석 줄 **"#Conflicts:" 와 파일 목록**이 그대로 남는다 |
|
|
| 15 | Take Theirs 한 자리마다 **머지 주석** "// [김승준] 2026-09-14" 가 소스에 들어간다. 병합 결과에 사람이 안 고른 줄이 생긴다 (설정으로 켠 기능 - 병합에서도 켜져야 하는지 따질 일) |
|
|
| 16 | 충돌 탭에서 Accept 할 때마다 **저장소 전체를 다시 읽는다.** 한 개 약 1.7초. 22개면 기다리는 것만 40초쯤. 여러 개를 골라 한 번에 풀 수 없다 |
|
|
| 17 | 상태바 인코딩·줄바꿈이 결과가 아니라 **충돌 표시가 들어간 워킹트리 파일**을 읽는다. 양쪽 다 CRLF 인 파일도 "혼합" 으로 나온다 |
|
|
| 18 | 상태바 "남은 차이 6줄" 은 충돌을 다 정해도 그대로다. "남은 충돌" 은 오른쪽 칸에만 나온다 |
|
|
| 19 | 파일 콤보에서 다른 파일을 고르는 **한가운데서** 확인 창이 뜬다. 고른 파일이 바뀌었는지 아닌지 불분명하다 |
|
|
| 20 | 병합 커밋 뒤 파일 비교 탭이 어정쩡하게 남는다. 목록은 비고, 3분할 틀에 병합과 상관없는 파일이 BASE 칸에만 뜬다 |
|
|
|
|
### 걸린 것 — 사소한 것
|
|
|
|
| # | 무엇 |
|
|
|---|---|
|
|
| 21 | 병합 로그에 "충돌 22건 - 해결이 필요합니다." 가 두 번 찍힌다 |
|
|
| 22 | 충돌 탭 목록의 종류 표시가 들쭉날쭉하다. 상대쪽 삭제만 표시되고 양쪽 새 파일은 표시가 없다. 몇 개 풀었는지(0/22)도 없다 |
|
|
| 23 | 로그가 CP949 를 "ks_c_5601-1987" 로 적는다. 5차 A-1 에서 모은 이름이 이 자리에는 안 갔다 |
|
|
|
|
(번호를 6차에서 쓰기 좋게 심각한 것부터 다시 매겼다. 적은 순서와는 다르다.)
|
|
|
|
### 6차로 넘길 때 보는 순서
|
|
|
|
1. **1·2·6·8 은 한 뿌리다.** 병합 중에 "파일 하나를 풀어서 저장한다" 는 동작이 없고, 커밋까지 한 번에 묶여 있다.
|
|
저장만 하는 길을 따로 두고, 그 길이 저장 형식(인코딩·줄바꿈)을 따르게 하면 네 가지가 같이 풀린다.
|
|
2. **3·9·10·11 도 한 뿌리다.** 충돌 목록에서 3분할 화면까지 가는 길이 끊겨 있다.
|
|
충돌 파일을 고르면 세 칸이 바로 뜨고, 충돌 탭에서 한 번에 넘어가게 하면 된다.
|
|
3. **4·5 는 파일 단위 충돌**(지움 · 양쪽 새 파일)을 줄 단위 화면이 다룰 줄 모르는 것이다.
|
|
|
|
### 시험 도구 쪽 사고 (제품 결함 아님)
|
|
|
|
자동 조작이 콤보 항목을 UIA 로 고르는 중에 확인 창이 떴고, 그 호출이 돌아오지 않은 채 확인 창이 입력을 안 받게 굳었다.
|
|
앱을 끄고 새로 띄웠다. 그 사이 파일에는 **아무것도 안 써졌다** (크기 · 스테이징 · 남은 충돌 수로 확인).
|
|
사람이 마우스로 고를 때는 이렇게 굳지 않는다. 다만 19번은 사람에게도 헷갈리는 자리라 남겼다.
|
|
|
|
### 다시 해 보려면
|
|
|
|
시험 저장소는 `C1Repo` 로 만든다(씨앗 고정 · 몇 번을 돌려도 같다). 실제 소스를 열지 않는다.
|
|
만든 소스에는 **진짜 코드의 지저분함이 없다** - 여기서 안 걸렸다고 실제에서 안 걸린다는 뜻은 아니다.
|
|
|
|
코드는 바꾸지 않았으므로 검사는 그대로 698건 통과(하네스 20벌) · 빌드 경고 0 · 오류 0.
|