5차 A-1: 실제 소스 인코딩 확인 - 깨지는 파일 0개

실제 저장소 2091개를 MergeHub 로 읽어 봤다. CP949 67개 포함해 깨짐 0, 저장 왕복 어긋남 0.
판별 코드가 TextFile 과 GitService 두 군데에 복사돼 있던 것을 한 곳으로 모았다.
상태바가 KS_C_5601-1987, 목록이 CP949 로 같은 것을 다르게 부르던 것도 맞췄다.
This commit is contained in:
김승준 2026-09-11 08:44:16 +09:00
parent dafb606f8f
commit 8e28a16fc2
5 changed files with 101 additions and 23 deletions

View File

@ -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 와 같다 - 같은 파일을 어느 경로로 열든 결과가 같아야 한다"
고 적혀 있었는데, **지키는 방법이 복사뿐이었다.** 한쪽만 고치면 조용히 어긋나고,
그러면 <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월부터 있던 것)으로 같다.

View File

@ -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) | 기능 덩어리 다섯 개(①~⑤)를 채운 주 |

View File

@ -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. 실제 소스에 맞추기

View File

@ -1543,25 +1543,14 @@ namespace MergeHub.Services
/// BOM -> UTF-8 유효성 -> 시스템 코드페이지 순으로 인코딩을 판별한다.
/// 사내 C/C++ 소스가 CP949 로 저장된 경우 UTF-8 로 강제 디코딩하면 한글이 깨진다.
/// </summary>
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;
}
}
/// <summary>
/// 인코딩 판별은 <see cref="TextFile.DetectEncoding"/> 한 곳에서만 한다(5차 A-1).
///
/// 전에는 여기에 똑같은 코드가 한 벌 더 있었다. 주석으로 "TextFile 과 같아야 한다" 고
/// 적어 두었지만 지키는 방법이 <b>복사</b>뿐이라 언젠가 어긋난다 - 같은 파일을 워킹트리로 열 때와
/// 커밋에서 열 때 한글이 다르게 보이면 어느 쪽이 맞는지 알 수도 없다.
/// </summary>
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();
}
}

View File

@ -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)));
}
/// <summary>
/// 바이트를 보고 인코딩을 정한다. <b>이 프로그램에서 인코딩을 판별하는 유일한 자리다.</b>
///
/// 전에는 <see cref="GitService"/> 에 똑같은 코드가 한 벌 더 있었다. 주석에 "같아야 한다" 고
/// 적어 두었지만 지키는 방법이 <b>복사</b>뿐이라 언젠가 어긋난다 - 같은 파일을 워킹트리로 열 때와
/// 커밋에서 열 때 한글이 다르게 보이면 어느 쪽이 맞는지 알 수도 없다. 그래서 한 곳으로 모았다.
///
/// 규칙은 넷이다. BOM 이 있으면 그대로 믿고, 없으면 <b>엄격한 UTF-8 로 읽어 본다.</b>
/// 읽히면 UTF-8, 걸리면 시스템 ANSI(한국 윈도우에서는 CP949)로 물러난다.
/// 실제 저장소 2,091개로 확인한 결과 깨지는 파일이 0개였다(5차 A-1).
/// </summary>
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
}
}
/// <summary>
/// 인코딩 이름을 <b>사람이 읽는 이름</b>으로. 화면 어디서나 같은 말을 써야 한다.
///
/// 코드페이지 949 의 공식 이름은 <c>KS_C_5601-1987</c> 인데, 저장 형식 목록에는 <c>CP949</c> 로
/// 적혀 있다. 상태바만 다른 이름을 쓰면 <b>같은 것을 두 이름으로 부르는 셈</b>이라
/// "지금 이 파일이 그 CP949 라는 건가" 를 되묻게 된다(5차 A-1 에서 실제로 그렇게 보였다).
/// </summary>
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);
}
}