From 21a2dc9b79a834f08fdab98c9b87ae444c94b0f2 Mon Sep 17 00:00:00 2001 From: ksj09406 Date: Mon, 14 Sep 2026 12:02:08 +0900 Subject: [PATCH] =?UTF-8?q?6=EC=B0=A8=20B-3:=20=EB=B3=91=ED=95=A9=20?= =?UTF-8?q?=EC=BB=A4=EB=B0=8B=20=EB=A9=94=EC=8B=9C=EC=A7=80=EB=A5=BC=20?= =?UTF-8?q?=EB=B8=8C=EB=9E=9C=EC=B9=98=20=EC=9D=B4=EB=A6=84=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=B1=84=EC=9A=B0=EA=B3=A0=20#=20=EC=A4=84?= =?UTF-8?q?=EC=9D=84=20=EB=BA=80=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5 --- doc/history/2026-09-14-6차.md | 41 +++++++++++++++++++++ doc/history/changelog.md | 1 + doc/진행상황.md | 2 +- src/MergeHub/Services/GitService.cs | 5 ++- src/MergeHub/Services/MergeNames.cs | 47 ++++++++++++++++++++++++ src/MergeHub/ViewModels/HomeViewModel.cs | 32 +++++++++++++++- 6 files changed, 124 insertions(+), 4 deletions(-) diff --git a/doc/history/2026-09-14-6차.md b/doc/history/2026-09-14-6차.md index 2b697be..6ac7c47 100644 --- a/doc/history/2026-09-14-6차.md +++ b/doc/history/2026-09-14-6차.md @@ -426,3 +426,44 @@ B-1 확인 때는 늘 파일 비교 탭을 먼저 열어 두었다. 순서를 | `Io/Unit20.cs` 를 고치고 `Io/Unit12.cs` 로 옮기며 [저장하고 계속] | 확인 창이 **Io/Unit20.cs** 이름을 댐. git 충돌 22, **풀린 것은 `Io/Unit20.cs`**(`M`, 안의 형식 `Unit20`, 상대 줄 들어감), **`Io/Unit12.cs` 는 `UU` 그대로**(안의 형식 `Unit12`), 그다음 `Io/Unit12.cs` 가 열림 | 화면 확인 **18건 통과**. 고친 파일(`HomeViewModel`)은 검사 하네스가 쓰지 않아 앞의 785건이 그대로 유효하다. 빌드 경고 0 · 오류 0. + +## B-3. 커밋 메시지가 엉뚱하게 남는다 (2026-09-14, 완료) + +### 고치기 전에 다시 났다 + +새로 만든 저장소(충돌 23개)를 MergeHub 로 병합했다. + +| 본 것 | 값 | +|---|---| +| 파일 비교 탭에서 `Common/Util/Unit15.cs` 를 연 뒤 `Io/Unit12.cs` · `Motion/Unit0.cs` 로 옮김 | 메시지 칸이 끝까지 "**Merge: Common/Util/Unit15.cs 충돌 해결**" — 처음 연 파일 이름이 굳음 | +| 충돌을 다 풀고 앱을 다시 켠 뒤 충돌 탭 [병합 커밋] | 첫 줄 "**Merge commit 'fc8b8f8…'**", 그 아래 **`#` 로 시작하는 줄 24개**(`#Conflicts:` 와 파일 목록). 부모 2개 | + +### 무엇이 원인이었나 + +- 파일 비교 탭은 메시지 칸이 **비어 있을 때만** 채웠다. 첫 파일에서 한 번 채우면 그 뒤로는 사람이 적은 글과 구별이 안 돼 그대로 남았다. 병합이 끝나도 지우는 곳이 없었다. +- 메시지 없이 커밋하면 git 이 남긴 `MERGE_MSG` 를 **그대로** 썼다. git 명령은 커밋할 때 `#` 줄을 지우지만 MergeHub 는 파일을 통째로 읽어 넣었다. + 또 MergeHub 는 브랜치를 커밋으로 풀어 병합하므로(libgit2) git 이 첫 줄을 해시로 적는다 — 이력에서 무엇을 합쳤는지 안 보였다. + +### 고친 것 + +| 자리 | 무엇 | +|---|---| +| `MergeNames.BranchMergeMessage` | 기본 메시지 `Merge branch '상대' into 내 브랜치`. 파일 이름은 넣지 않는다 — 병합 커밋은 병합 전체를 담는다 | +| `MergeNames.CleanMergeMessage` | `MERGE_MSG` 에서 `#` 줄을 빼고 앞뒤 빈 줄을 정리. 첫 줄이 `Merge commit '해시'` 이고 상대 브랜치 이름을 알면 브랜치 모양으로 바꾼다. 남는 것이 없으면 기본 메시지 | +| `GitService.CommitMerge` | 메시지가 비면 `CleanMergeMessage` 를 거친 것을 쓴다 | +| `HomeViewModel` | 스스로 채운 글을 `_autoCommitMessage` 로 기억한다. 칸이 비었거나 **내가 채운 글 그대로**일 때만 다시 채우고, 사람이 고친 글은 건드리지 않는다. 병합이 끝나면(새로고침에서 병합 중이 아님 · 병합 아닌 파일을 열 때 · 커밋 성공) 내가 채운 글만 지운다 | + +`MergeSaveTest` 에 메시지 경우를 넣어 **62건 통과**. + +### 확인한 것 (새로 만든 저장소) + +| 확인 | 결과 | +|---|---| +| 병합 뒤 `Io/Unit12.cs` 를 엶 | 칸 `Merge branch 'feature/theirs' into feature/mine` | +| `Motion/Unit0.cs` 로 옮김 | 같은 글. 파일 이름 없음 | +| 칸에 "냉각 로직 병합 - 사람이 적음" 을 쓰고 `Io/Unit20.cs` 로 옮김 | 적은 글 그대로 | +| 칸을 비우고 `Io/Unit12.cs` 로 옮김 | 기본 메시지가 다시 채워짐 | +| 충돌을 git 으로 다 풀고 **앱을 다시 켠 뒤** 충돌 탭 [병합 커밋] | 커밋 메시지 `Merge branch 'feature/theirs' into feature/mine` 한 줄, `#` 줄 **0개**, 부모 2개, MERGE_HEAD 사라짐 | +| 저장소를 새로 만들어 병합 → 파일을 열어 칸이 채워진 채로 충돌을 풀고 **앱을 끄지 않고** [병합 커밋] | 커밋 메시지 같은 한 줄, `#` 줄 0개, 부모 2개. **파일 비교 탭 메시지 칸이 비워짐** | + +화면 확인 **7건 통과**. 빌드 경고 0 · 오류 0. diff --git a/doc/history/changelog.md b/doc/history/changelog.md index 2ed6333..008d900 100644 --- a/doc/history/changelog.md +++ b/doc/history/changelog.md @@ -8,6 +8,7 @@ | 날짜 | 항목 | 커밋 | |---|---|---| +| 2026-09-14 | **6차 B-3** — 병합 커밋 기본 메시지를 `Merge branch '상대' into 내 브랜치` 로. 첫 파일 이름이 굳어 남던 것, 커밋에 `#Conflicts:` 줄 24개와 해시 첫 줄이 들어가던 것을 고침. 병합이 끝나면 스스로 채운 글만 지운다 | 아래 | | 2026-09-14 | **6차 B-2** — 파일을 바꿀 때 확인 창이 고친 파일 이름을 대고, [저장하고 계속] 이 엉뚱한 파일(새로 고른 파일)에 쓰던 것을 막음. [취소] 뒤 콤보가 제자리로. 저장소를 먼저 열면 목록이 0개이던 B-1 빈틈도 | 아래 | | 2026-09-14 | **6차 B-1** — 새로고침 뒤 파일 목록을 게이트가 풀릴 때까지 기다렸다 세움(병합 뒤 304개 → 23개). 충돌 목록 두 번 누르기·Enter·[3분할로 열기] 로 파일 비교 탭 네 칸에서 연다. 없는 '홈 탭' 글을 고침 | 아래 | | 2026-09-14 | **6차 A-5** — 파일 단위 Accept Both 가 두 벌을 글로 풀어 이어 붙이고 내 쪽 형식으로 쓴다(가운데 BOM 사라짐). 누르기 전에 무엇이 일어나는지 묻는다. A 묶음 끝 | 아래 | diff --git a/doc/진행상황.md b/doc/진행상황.md index 374191a..b788c11 100644 --- a/doc/진행상황.md +++ b/doc/진행상황.md @@ -105,7 +105,7 @@ |---|---| | **완료** | B-1 충돌 목록에서 3분할 화면까지 가는 길이 끊겨 있다 — 병합 뒤 목록이 바로 서고, 두 번 누르기·Enter·[3분할로 열기] | | **완료** | B-2 확인 창이 엉뚱한 파일 이름을 댄다 — 이름·[저장하고 계속] 모두 실린 파일 기준, [취소] 뒤 콤보도 돌아온다 | -| 대기 | B-3 커밋 메시지가 엉뚱하게 남는다 | +| **완료** | B-3 커밋 메시지가 엉뚱하게 남는다 — 기본값 `Merge branch '상대' into 내 브랜치`, `#` 줄 뺌, 병합이 끝나면 칸을 비움 | | 대기 | B-4 MERGED 칸 줄 번호가 네 자리에서 잘린다 | | 대기 | B-5 머지 주석은 소스에 넣고, 병합 뒤에는 그 줄을 무시한다 | | 대기 | B-6 충돌 탭에서 하나씩 누를 때마다 오래 기다린다 | diff --git a/src/MergeHub/Services/GitService.cs b/src/MergeHub/Services/GitService.cs index deb5378..5ae0b93 100644 --- a/src/MergeHub/Services/GitService.cs +++ b/src/MergeHub/Services/GitService.cs @@ -944,8 +944,11 @@ namespace MergeHub.Services if (remaining.Count > 0) throw new InvalidOperationException($"아직 해결되지 않은 충돌이 {remaining.Count}건 있습니다."); + // 사람이 적은 메시지가 없으면 git 이 남긴 병합 메시지를 다듬어 쓴다(6차 B-3). + // 예전에는 MERGE_MSG 를 통째로 써서 "Merge commit '해시'" 와 "#Conflicts:" 파일 목록이 이력에 남았다. + // MergeHub 는 브랜치를 커밋으로 풀어 병합하므로 git 이 첫 줄을 해시로 적는다 - 상대 이름으로 바꾼다. if (string.IsNullOrWhiteSpace(message)) - message = ReadMergeMessage() ?? $"Merge into {Repo.Head.FriendlyName}"; + message = MergeNames.CleanMergeMessage(ReadMergeMessage(), GetMergeTheirsName(), Repo.Head.FriendlyName); var signature = BuildSignature(); var commit = Repo.Commit(message, signature, signature); diff --git a/src/MergeHub/Services/MergeNames.cs b/src/MergeHub/Services/MergeNames.cs index 3da3d5a..9f4c6d0 100644 --- a/src/MergeHub/Services/MergeNames.cs +++ b/src/MergeHub/Services/MergeNames.cs @@ -1,3 +1,5 @@ +using System; +using System.Linq; using System.Text.RegularExpressions; namespace MergeHub.Services @@ -32,5 +34,50 @@ namespace MergeHub.Services var name = match.Groups[1].Value.Trim(); return name.Length == 0 ? null : name; } + + /// + /// 병합 커밋의 기본 메시지. git 명령으로 브랜치를 병합할 때와 같은 모양이다(6차 B-3). + /// 상대 이름을 모르면 Merge into 브랜치 로 줄인다. + /// + /// 파일 이름을 넣지 않는 까닭 - 병합 커밋은 파일 하나가 아니라 병합 전체를 담는다. 예전에는 파일 비교 탭이 + /// "Merge: 첫 충돌 파일 충돌 해결" 을 채우고, 다른 파일로 옮겨도 그 이름이 굳어 남았다. + /// + public static string BranchMergeMessage(string? theirs, string? head) + { + var into = string.IsNullOrWhiteSpace(head) ? string.Empty : $" into {head!.Trim()}"; + return string.IsNullOrWhiteSpace(theirs) + ? $"Merge{(into.Length == 0 ? " commit" : into)}" + : $"Merge branch '{theirs!.Trim()}'{into}"; + } + + /// + /// git 이 남긴 병합 메시지(MERGE_MSG)를 커밋에 쓸 모양으로 다듬는다(6차 B-3). + /// + /// + /// # 로 시작하는 줄은 뺀다. git 명령은 커밋할 때 이 주석 줄(#Conflicts: 와 파일 목록)을 + /// 지우는데, MergeHub 는 파일을 통째로 써서 커밋 이력에 그대로 남았다. + /// 첫 줄이 Merge commit '해시' 이고 상대 이름을 알면 Merge branch '이름' into 브랜치 로 바꾼다. + /// MergeHub 는 브랜치를 커밋으로 풀어 병합해서 git 이 해시로 적는다 - 이력에서 무엇을 합쳤는지 안 보였다. + /// 남는 것이 없으면 를 쓴다. + /// + /// + public static string CleanMergeMessage(string? raw, string? theirs, string? head) + { + var lines = (raw ?? string.Empty).Replace("\r\n", "\n").Split('\n') + .Where(l => !l.TrimStart().StartsWith("#", StringComparison.Ordinal)) + .ToList(); + + // 앞뒤 빈 줄 정리 + while (lines.Count > 0 && lines[0].Trim().Length == 0) lines.RemoveAt(0); + while (lines.Count > 0 && lines[lines.Count - 1].Trim().Length == 0) lines.RemoveAt(lines.Count - 1); + + if (lines.Count == 0) return BranchMergeMessage(theirs, head); + + var byCommit = Regex.IsMatch(lines[0].Trim(), @"^Merge\s+commit\s+'[0-9a-fA-F]{7,40}'"); + if (byCommit && !string.IsNullOrWhiteSpace(theirs) && !Regex.IsMatch(theirs!.Trim(), @"^[0-9a-fA-F]{7,40}$")) + lines[0] = BranchMergeMessage(theirs, head); + + return string.Join("\n", lines); + } } } diff --git a/src/MergeHub/ViewModels/HomeViewModel.cs b/src/MergeHub/ViewModels/HomeViewModel.cs index b0ff4eb..d2951c2 100644 --- a/src/MergeHub/ViewModels/HomeViewModel.cs +++ b/src/MergeHub/ViewModels/HomeViewModel.cs @@ -336,6 +336,12 @@ namespace MergeHub.ViewModels [ObservableProperty] private string _diffSummary = "-"; [ObservableProperty] private string _editSummary = "-"; [ObservableProperty] private string _commitMessage = string.Empty; + + /// + /// 프로그램이 채워 넣은 커밋 메시지(6차 B-3). 지금 칸의 글이 이것과 같으면 사람이 손대지 않은 것이라 + /// 바꾸거나 비워도 된다. 다르면 사람이 적은 글이므로 건드리지 않는다. + /// + private string _autoCommitMessage = string.Empty; [ObservableProperty] private bool _hasChanges; /// 커밋 트리 ListBox 가 바인딩하는 선택 항목(그래프 포함). @@ -671,6 +677,7 @@ namespace MergeHub.ViewModels _redo.Clear(); HasChanges = false; CommitMessage = string.Empty; + _autoCommitMessage = string.Empty; MessageUtil.ShowSuccessToast( sha is null ? "커밋 완료" : $"커밋 완료 ({sha.Substring(0, 7)})"); }); @@ -878,8 +885,20 @@ namespace MergeHub.ViewModels // ================= 로드 ================= + /// 병합이 끝났으면 프로그램이 채운 병합 메시지를 비운다. 사람이 적은 글이면 두고 간다. + private void ClearAutoCommitMessage() + { + if (_autoCommitMessage.Length == 0) return; + if (CommitMessage == _autoCommitMessage) CommitMessage = string.Empty; + _autoCommitMessage = string.Empty; + } + private void OnWorkspaceRefreshed() { + // 병합 커밋이나 병합 취소로 병합이 끝났으면 병합 메시지를 남겨 두지 않는다(6차 B-3). + // 예전에는 병합 커밋 뒤에도 "Merge: Common/Util/Unit15.cs 충돌 해결" 이 칸에 남았다. + if (!_workspace.IsMergeInProgress) ClearAutoCommitMessage(); + IsGitMode = _workspace.IsRepositoryOpen; RefreshRemoteBranches(); @@ -1161,8 +1180,17 @@ namespace MergeHub.ViewModels _redo.Clear(); SelectedLine = Lines.FirstOrDefault(l => l.IsDifferent) ?? Lines.FirstOrDefault(); - if (isConflict && string.IsNullOrWhiteSpace(CommitMessage)) - CommitMessage = $"Merge: {path} 충돌 해결"; + // 병합 커밋 메시지 기본값은 병합 전체의 이름이다(6차 B-3). 예전에는 "Merge: 첫 충돌 파일 충돌 해결" 을 + // 채우고, 다른 파일로 옮겨도 그 이름이 굳어 남았다. 사람이 고쳐 적은 글은 건드리지 않는다. + if (isConflict && (string.IsNullOrWhiteSpace(CommitMessage) || CommitMessage == _autoCommitMessage)) + { + _autoCommitMessage = MergeNames.BranchMergeMessage(theirsName, _workspace.CurrentBranch); + CommitMessage = _autoCommitMessage; + } + else if (!isConflict && !_workspace.IsMergeInProgress) + { + ClearAutoCommitMessage(); + } UpdateSummaries(); RaiseCanExecuteChanged();