diff --git a/doc/history/2026-09-11-5차.md b/doc/history/2026-09-11-5차.md
new file mode 100644
index 0000000..563fdf6
--- /dev/null
+++ b/doc/history/2026-09-11-5차.md
@@ -0,0 +1,59 @@
+# 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 와 같다 - 같은 파일을 어느 경로로 열든 결과가 같아야 한다"
+고 적혀 있었는데, **지키는 방법이 복사뿐이었다.** 한쪽만 고치면 조용히 어긋나고,
+그러면 같은 파일이 워킹트리에서는 멀쩡한데 커밋에서 열면 깨져 보인다.
+어느 쪽이 맞는지 알 방법도 없다. `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월부터 있던 것)으로 같다.
\ No newline at end of file
diff --git a/doc/history/changelog.md b/doc/history/changelog.md
index 1663963..305b251 100644
--- a/doc/history/changelog.md
+++ b/doc/history/changelog.md
@@ -8,6 +8,7 @@
| 날짜 | 항목 | 커밋 |
|---|---|---|
+| 2026-09-11 | **5차 A-1** — 실제 저장소 2,091개로 인코딩을 확인(깨짐 0). 판별 코드가 두 군데 복사돼 있던 것을 한 곳으로 모으고, CP949 를 한 이름으로 부르게 함 | 아래 |
| 2026-09-10 | **4차 C-5** — 한 번 정하는 것들을 한자리에 모아 접고, 차이 이동을 개수 옆으로 | 아래 |
| 2026-09-10 | **4차 C-4** — 범례를 눌러 그 갈래로 이동. 0건은 흐리게 | 아래 |
| 2026-09-10 | **4차 C-3** — Git 탭의 원격 칸을 접을 수 있게. 로컬 경로 한 줄만 남고 세로 128px 가 목록으로 간다 | 아래 |
@@ -39,6 +40,7 @@
| 날짜 | 문서 | 내용 |
|---|---|---|
+| 2026-09-11 | **5차 A-1** — 실제 저장소 2,091개로 인코딩을 확인(깨짐 0). 판별 코드가 두 군데 복사돼 있던 것을 한 곳으로 모으고, CP949 를 한 이름으로 부르게 함 | 아래 |
| 2026-09-10 | **4차 C-5** — 한 번 정하는 것들을 한자리에 모아 접고, 차이 이동을 개수 옆으로 | 아래 |
| 2026-09-10 | **4차 C-4** — 범례를 눌러 그 갈래로 이동. 0건은 흐리게 | 아래 |
| 2026-09-10 | **4차 C-3** — Git 탭의 원격 칸을 접을 수 있게. 로컬 경로 한 줄만 남고 세로 128px 가 목록으로 간다 | 아래 |
@@ -53,6 +55,7 @@
| 2026-09-10 | **4차 A-1** — 저장하지 않은 편집이 말없이 사라지던 길 여덟 군데를 막음. 플랫폼에 세 갈래 확인 창을 새로 넣었다 | 아래 |
| 2026-09-10 | **4차 D-2** — 항목을 끝내면 상세·상태·한 줄을 각각 어디에 적을지 공통 규칙으로 못박음 | 아래 |
| 2026-09-10 | **4차 D-1** — 721줄짜리 진행상황을 `history/` 의 날짜별 파일로 나누고 지시서를 `작업지시/` 로 모음 | 아래 |
+| 2026-09-11 | [2026-09-11-5차.md](2026-09-11-5차.md) | 5차 지시서 진행 중 |
| 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/진행상황.md b/doc/진행상황.md
index 18f3100..c269282 100644
--- a/doc/진행상황.md
+++ b/doc/진행상황.md
@@ -55,6 +55,7 @@
## 5차 — 실제 소스에 맞추기 (2026-09-10 ~)
+진행 기록: [history/2026-09-11-5차.md](history/2026-09-11-5차.md)
지시서: [작업지시/5차.md](작업지시/5차.md). 지금까지는 시험용 파일로만 확인했다.
5차는 실제 소스(`LGE-OC6-Inline-CylinderTypeWelder` · 2,115개 · 426,723줄)를 재 보고 나온 것들이다.
@@ -62,7 +63,7 @@
| 상태 | 항목 |
|---|---|
-| 대기 | A-1 CP949 파일 67개를 잘못 읽으면 한글이 깨진다 |
+| **완료** | A-1 CP949 파일 67개를 잘못 읽으면 한글이 깨진다 |
| 대기 | A-2 커밋·체크아웃 뒤에 저장 안 한 편집이 조용히 사라진다 |
### B. 실제 소스에 맞추기
diff --git a/src/MergeHub/Services/GitService.cs b/src/MergeHub/Services/GitService.cs
index c3c0b0e..b052484 100644
--- a/src/MergeHub/Services/GitService.cs
+++ b/src/MergeHub/Services/GitService.cs
@@ -1543,25 +1543,14 @@ namespace MergeHub.Services
/// BOM -> UTF-8 유효성 -> 시스템 코드페이지 순으로 인코딩을 판별한다.
/// 사내 C/C++ 소스가 CP949 로 저장된 경우 UTF-8 로 강제 디코딩하면 한글이 깨진다.
///
- private static Encoding DetectEncoding(byte[] bytes)
- {
- if (bytes.Length >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF)
- return new UTF8Encoding(encoderShouldEmitUTF8Identifier: true);
- if (bytes.Length >= 2 && bytes[0] == 0xFF && bytes[1] == 0xFE)
- return Encoding.Unicode;
- if (bytes.Length >= 2 && bytes[0] == 0xFE && bytes[1] == 0xFF)
- return Encoding.BigEndianUnicode;
-
- try
- {
- Utf8Strict.GetString(bytes);
- return new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);
- }
- catch (DecoderFallbackException)
- {
- return AnsiEncoding;
- }
- }
+ ///
+ /// 인코딩 판별은 한 곳에서만 한다(5차 A-1).
+ ///
+ /// 전에는 여기에 똑같은 코드가 한 벌 더 있었다. 주석으로 "TextFile 과 같아야 한다" 고
+ /// 적어 두었지만 지키는 방법이 복사뿐이라 언젠가 어긋난다 - 같은 파일을 워킹트리로 열 때와
+ /// 커밋에서 열 때 한글이 다르게 보이면 어느 쪽이 맞는지 알 수도 없다.
+ ///
+ private static Encoding DetectEncoding(byte[] bytes) => TextFile.DetectEncoding(bytes);
private static string DecodeText(byte[] bytes)
{
@@ -1610,7 +1599,8 @@ namespace MergeHub.Services
catch (DecoderFallbackException)
{
// 시스템 코드페이지. 국내 장비 소스는 대개 CP949 다.
- return AnsiEncoding.WebName.ToUpperInvariant();
+ // 상태바와 저장 형식 목록이 같은 이름을 쓰게 한다(5차 A-1).
+ return AnsiEncoding.CodePage == 949 ? "CP949" : AnsiEncoding.WebName.ToUpperInvariant();
}
}
diff --git a/src/MergeHub/Services/TextFile.cs b/src/MergeHub/Services/TextFile.cs
index 2edd056..f4225a6 100644
--- a/src/MergeHub/Services/TextFile.cs
+++ b/src/MergeHub/Services/TextFile.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -164,6 +164,18 @@ namespace MergeHub.Services
return new TextFileInfo(NameOf(bytes), NewLineOf(Read(path)));
}
+ ///
+ /// 바이트를 보고 인코딩을 정한다. 이 프로그램에서 인코딩을 판별하는 유일한 자리다.
+ ///
+ /// 전에는 에 똑같은 코드가 한 벌 더 있었다. 주석에 "같아야 한다" 고
+ /// 적어 두었지만 지키는 방법이 복사뿐이라 언젠가 어긋난다 - 같은 파일을 워킹트리로 열 때와
+ /// 커밋에서 열 때 한글이 다르게 보이면 어느 쪽이 맞는지 알 수도 없다. 그래서 한 곳으로 모았다.
+ ///
+ /// 규칙은 넷이다. BOM 이 있으면 그대로 믿고, 없으면 엄격한 UTF-8 로 읽어 본다.
+ /// 읽히면 UTF-8, 걸리면 시스템 ANSI(한국 윈도우에서는 CP949)로 물러난다.
+ /// 실제 저장소 2,091개로 확인한 결과 깨지는 파일이 0개였다(5차 A-1).
+ ///
+ public static Encoding DetectEncoding(byte[] bytes) => Detect(bytes);
private static Encoding Detect(byte[] bytes)
{
if (bytes.Length >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF)
@@ -182,6 +194,19 @@ namespace MergeHub.Services
}
}
+ ///
+ /// 인코딩 이름을 사람이 읽는 이름으로. 화면 어디서나 같은 말을 써야 한다.
+ ///
+ /// 코드페이지 949 의 공식 이름은 KS_C_5601-1987 인데, 저장 형식 목록에는 CP949 로
+ /// 적혀 있다. 상태바만 다른 이름을 쓰면 같은 것을 두 이름으로 부르는 셈이라
+ /// "지금 이 파일이 그 CP949 라는 건가" 를 되묻게 된다(5차 A-1 에서 실제로 그렇게 보였다).
+ ///
+ private static string FriendlyName(Encoding encoding)
+ {
+ if (encoding.CodePage == 949) return "CP949";
+
+ return encoding.WebName.ToUpperInvariant();
+ }
private static string NameOf(byte[] bytes)
{
if (bytes.Length >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF) return "UTF-8 (BOM)";
@@ -195,7 +220,7 @@ namespace MergeHub.Services
}
catch (DecoderFallbackException)
{
- return Ansi.WebName.ToUpperInvariant();
+ return FriendlyName(Ansi);
}
}