diff --git a/doc/history/2026-09-16-8차.md b/doc/history/2026-09-16-8차.md
index 61baf06..9542bf5 100644
--- a/doc/history/2026-09-16-8차.md
+++ b/doc/history/2026-09-16-8차.md
@@ -157,3 +157,48 @@ cherry-pick 의 BASE 제목에 꼬리가 없는 것은 맞다 — 그때 BASE
### 정하지 않은 것
- git 의 `--allow-empty` 처럼 **빈 cherry-pick 을 커밋으로 남기는 길**은 지시서대로 두지 않았다. 사용자와 정한 뒤에 넣는다.
+
+## B-3. MERGED 칸 줄 번호가 결과 파일 번호가 아니다 (2026-09-16, 완료)
+
+**정한 것 (2026-09-16, 사용자)** — 두 가지를 보이고 골랐다. ① 결과 파일 번호로(고름) ② 지금처럼 내 쪽 번호로 두고 그렇다고 적기.
+결과 파일 번호는 상태바 · [이동] 칸 · 코드 검사의 "N행" 과 같은 기준이다.
+
+### 고치기 전에 다시 났다 (새로 만든 저장소, 병합 중 `Motion/Unit0.cs` 를 네 칸으로)
+
+충돌 6개를 Take Theirs 하면 고른 줄 · 머지 주석으로 결과가 늘어난다.
+
+| 확인 | 값 |
+|---|---|
+| 상태바 | **총 3369줄** |
+| [이동] 칸에 3369 | 3369행으로 감 |
+| **MERGED 칸 끝 번호** | **3363** — 여섯 줄이 모자란다 |
+| LOCAL (Mine) 칸 끝 번호 | 3361 (내 파일 그대로 · 맞다) |
+
+### 원인
+
+MERGED 칸 번호는 `MergeLine.LocalNumber` 인데, 이 값은 **정렬기가 짝지을 때 한 번 정해지고 그대로**다.
+머지 주석 줄은 새로 끼워 넣으면서 번호를 주지 않았고(`Lines.Insert`), 그 뒤의 줄들도 번호를 다시 매기지 않았다.
+그래서 결과가 길어질수록 어긋난다.
+
+### 고친 것
+
+| 파일 | 무엇 |
+|---|---|
+| `Services/MergedNumbering.cs` (새 파일) | `Assign(결과에 들어가는가[])` — 결과에 들어가는 줄만 1부터 센다. `Count` — 결과 줄 수 |
+| `ViewModels/HomeViewModel.cs` | `RenumberMerged(rows)` — 줄 목록에 결과 번호를 다시 매긴다. 두 줄 만들기(두 칸 · 네 칸) 끝과 **요약을 그릴 때마다**(고르기 · 편집 · 되돌리기 · 주석 끼우기 뒤) 부른다 |
+| 하네스 `MergeSaveTest` | 번호 검사 7건(결과에 없는 줄 · 끝 번호 = 결과 줄 수 · 빈 목록 · null 등) |
+
+결과 파일은 `BuildLocalText` 가 `HasLocal` 인 줄만 이어 붙인 것이라, 번호도 같은 기준으로 센다.
+LOCAL (Mine) 칸은 `OriginalLocalNumber`(내 파일 번호)를 그대로 쓴다 — 바꾸지 않았다.
+
+### 확인한 것 (같은 순서로 다시)
+
+| 확인 | 값 |
+|---|---|
+| 상태바 | 총 3369줄 |
+| **MERGED 칸 끝 번호** | **3369** (3352 ~ 3369) |
+| [이동] 칸에 3369 | 3369행 |
+| **[Save] 한 뒤 워킹트리 파일** | **3369줄** — 화면 번호와 같다 |
+| LOCAL (Mine) 칸 | 3346 ~ 3361 그대로 |
+
+하네스 18벌 928건 통과(MergeSaveTest 162). 빌드 경고 0 · 오류 0. 설정 파일은 바이트 그대로 되돌림.
diff --git a/doc/history/changelog.md b/doc/history/changelog.md
index 733d16e..4642212 100644
--- a/doc/history/changelog.md
+++ b/doc/history/changelog.md
@@ -8,6 +8,7 @@
| 날짜 | 항목 | 커밋 |
|---|---|---|
+| 2026-09-16 | **8차 B-3** — 네 칸 MERGED 칸 줄 번호가 정렬할 때 정해진 채 굳어, 고른 줄 · 머지 주석으로 결과가 늘어도 그대로였다(끝 번호 3363 · 상태바 총 3369줄). 사용자가 "결과 파일 번호로" 를 골라, 결과에 들어가는 줄만 1부터 다시 매긴다(MergedNumbering). 줄을 만들 때와 요약을 그릴 때마다 다시 매겨 고르기 · 편집 · 되돌리기 뒤에도 맞는다. 저장된 파일 줄 수(3369)와 화면 번호가 같아짐 | 아래 |
| 2026-09-16 | **8차 B-2** — 이미 들어 있는 커밋을 cherry-pick 하면 [커밋] 이 켜지는데 누르면 로그에 "실패: No changes; nothing to commit." 만 남고 진행 줄도 그대로라 같은 단추를 또 누르게 됐다. 커밋할 것이 있는지(HasStagedChanges)를 상태로 들고 있다가 [커밋] 을 끄고, 진행 줄 · 적용 로그가 "가져올(되돌릴) 변경이 없습니다 - [작업 취소] 로 끝냅니다" 라고 말한다. 커밋 직전에도 사람 말로 막는다. 병합 커밋은 트리가 같아도 정상이라 걸지 않음 | 아래 |
| 2026-09-16 | **8차 B-1** — rebase · cherry-pick 에서 마지막 충돌을 [Save] 하면 파일 비교 탭이 "남은 차이 6줄" 로 떨어져 충돌 탭과 다른 말을 했다(7차 A-1 이 병합 전용이었고 화면도 MERGE_HEAD 기준으로 물었다). 요약 · BASE 제목을 작업 상태(GitOperationState)에 맞춰 OperationLabels 로 옮김 — 끝말은 충돌 탭 진행 줄과 같은 FinishHint, 차이 줄은 작업별로(받은 · 올린 · 가져온 · 되돌린 변경), 제목 꼬리는 병합 "병합 전 내 쪽" · rebase "올릴 자리" 등. 병합 글은 그대로 | 아래 |
| 2026-09-16 | **8차 A-1** — 파일 비교 탭을 처음 여는 것이 [3분할로 열기] 면 고른 파일 대신 첫 충돌 파일이 보였고, 그 화면을 [Save] 하면 **고른 파일에 그 내용이 써졌다**(Unit47 이 Unit15 내용으로 스테이징). 탭을 만들 때의 목록 세우기와 고른 파일 열기가 겹쳐, 늦게 끝난 읽기가 화면을 덮은 것. 읽기는 경로 · 커밋이 바뀌었으면 버리고 한 번 더 읽게, 쓰기(저장 · Save & Commit · 파일 지우기/남기기)는 화면에 실린 파일과 다르면 아무것도 쓰지 않게 했다. 네 길 모두 고른 파일이 열리고 저장이 그 파일에만 감, 병합은 사람 순서 그대로 끝까지(줄끝 0/33 · 내용 검사 0) | 아래 |
diff --git a/doc/진행상황.md b/doc/진행상황.md
index 8fbcd06..3eed988 100644
--- a/doc/진행상황.md
+++ b/doc/진행상황.md
@@ -207,7 +207,7 @@
|---|---|
| **완료** | B-1 rebase · cherry-pick 에서 마지막 저장 뒤 "남은 차이" 가 뜬다 — 요약 · BASE 제목이 작업(병합 · rebase · cherry-pick · revert)을 따른다. rebase 는 "충돌 정리 끝 · [계속] 으로 rebase 를 이어 갑니다 · 올릴 자리 대비 올린 변경 N줄" · 제목 "올릴 자리". 병합 글은 그대로 |
| **완료** | B-2 바뀌는 것 없는 cherry-pick 에서 [커밋] 이 켜지고 누르면 실패한다 — 커밋할 것이 없으면 [커밋] 이 꺼지고 진행 줄 · 로그가 "가져올 변경이 없습니다 - [작업 취소] 로 끝냅니다". 병합 커밋은 그대로(트리가 같아도 정상). `--allow-empty` 는 사용자와 정한 뒤 |
-| 대기 | B-3 MERGED 칸 줄 번호가 결과 파일 번호가 아니다 (먼저 사용자와 정함) |
+| **완료** | B-3 MERGED 칸 줄 번호가 결과 파일 번호가 아니다 — 사용자가 "결과 파일 번호로" 를 고름(2026-09-16). 결과에 들어가는 줄만 1부터 다시 매긴다. 끝 번호 3363 → 3369 로 상태바 · [이동] 칸 · 저장된 파일(3369줄)과 같아짐 |
### C. 7차에서 남긴 것
diff --git a/src/MergeHub/Services/MergedNumbering.cs b/src/MergeHub/Services/MergedNumbering.cs
new file mode 100644
index 0000000..9ec99a0
--- /dev/null
+++ b/src/MergeHub/Services/MergedNumbering.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Collections.Generic;
+
+namespace MergeHub.Services
+{
+ ///
+ /// MERGED 칸(네 칸의 결과)에 매길 줄 번호(8차 B-3).
+ ///
+ /// 따로 뺀 까닭 - 예전에는 번호를 정렬기가 준 대로 두었다. 줄을 고르거나 머지 주석이 끼어들어
+ /// 결과 줄이 늘어도 다시 매기지 않아, MERGED 칸 끝 번호(3363)와 상태바 줄 수(3366)가 어긋났다.
+ /// 사용자가 결과 파일 번호로 맞추기로 정했다(2026-09-16) - 상태바 · [이동] 칸 · 코드 검사의 "N행" 과 같은 번호다.
+ ///
+ /// 결과 파일은 결과에 들어가는 줄만 차례로 이어 붙인 것이다(BASE 에만 있던 줄은 빠진다).
+ /// 그래서 번호도 그 줄만 1부터 센다.
+ ///
+ public static class MergedNumbering
+ {
+ /// 행마다 "결과 파일에 들어가는가" 를 받아 번호를 돌려준다. 들어가지 않는 행은 null.
+ public static int?[] Assign(IReadOnlyList inResult)
+ {
+ if (inResult is null) return Array.Empty();
+
+ var numbers = new int?[inResult.Count];
+ var n = 0;
+
+ for (var i = 0; i < inResult.Count; i++)
+ numbers[i] = inResult[i] ? ++n : (int?)null;
+
+ return numbers;
+ }
+
+ /// 결과 파일의 줄 수. 상태바가 말하는 "총 N줄" 과 같아야 한다.
+ public static int Count(IReadOnlyList inResult)
+ {
+ if (inResult is null) return 0;
+
+ var n = 0;
+ foreach (var yes in inResult) if (yes) n++;
+ return n;
+ }
+ }
+}
diff --git a/src/MergeHub/ViewModels/HomeViewModel.cs b/src/MergeHub/ViewModels/HomeViewModel.cs
index ac567fa..a819f24 100644
--- a/src/MergeHub/ViewModels/HomeViewModel.cs
+++ b/src/MergeHub/ViewModels/HomeViewModel.cs
@@ -1529,6 +1529,7 @@ namespace MergeHub.ViewModels
}
RefreshBlockCommentState(rows);
+ RenumberMerged(rows); // MERGED 칸은 결과 파일 번호로(8차 B-3)
return rows;
}
@@ -1595,6 +1596,7 @@ namespace MergeHub.ViewModels
MarkMoves(aligned, rows);
RefreshBlockCommentState(rows);
+ RenumberMerged(rows); // MERGED 칸은 결과 파일 번호로(8차 B-3)
return rows;
}
@@ -3417,6 +3419,9 @@ namespace MergeHub.ViewModels
private void UpdateSummaries()
{
+ // 줄을 고르거나 머지 주석이 끼어들면 결과 줄 수가 바뀐다. 그릴 때마다 번호를 다시 매긴다(8차 B-3).
+ RenumberMerged(Lines);
+
var total = Lines.Count;
var diff = Lines.Count(l => l.IsDifferent);
@@ -3756,6 +3761,23 @@ namespace MergeHub.ViewModels
SaveResolvedCommand.NotifyCanExecuteChanged();
}
+ ///
+ /// MERGED 칸 번호를 결과 파일 번호로 다시 매긴다(8차 B-3).
+ ///
+ /// 줄을 고르거나 머지 주석이 끼어들면 결과 줄 수가 바뀐다. 정렬기가 준 번호를 그대로 두면
+ /// 끝 번호가 상태바의 "총 N줄" 과 어긋났다. 결과에 들어가는 줄()만 1부터 센다.
+ ///
+ private static void RenumberMerged(IList rows)
+ {
+ if (rows is null || rows.Count == 0) return;
+
+ var inResult = new bool[rows.Count];
+ for (var i = 0; i < rows.Count; i++) inResult[i] = rows[i].HasLocal;
+
+ var numbers = MergedNumbering.Assign(inResult);
+ for (var i = 0; i < rows.Count; i++) rows[i].LocalNumber = numbers[i];
+ }
+
private string BuildLocalText()
{
var sb = new StringBuilder();