From bb6bd655a50b7e50363a2f31380b4f0ad0ae157a Mon Sep 17 00:00:00 2001 From: ksj09406 Date: Mon, 14 Sep 2026 17:34:10 +0900 Subject: [PATCH] =?UTF-8?q?7=EC=B0=A8=20B-1:=20=EB=B3=91=ED=95=A9=EC=9D=B4?= =?UTF-8?q?=20=EB=81=9D=EB=82=98=EB=A9=B4=20.git/MERGEHUB=5FCONFLICTS=20?= =?UTF-8?q?=EB=A5=BC=20=EC=B9=98=EC=9A=B4=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 병합 때 적는 처음 충돌 목록이 병합 커밋 · 작업 취소 · 터미널에서 끝낸 병합 뒤에도 남았다. MERGE_HEAD 가 없거나 다른 병합의 것이면 병합 커밋 · 작업 취소 직후와 상태를 다시 읽을 때마다 지운다. 병합 중에는 그대로라 "푼 것 M개 (M/전체)" 는 이어진다. 세 경우 화면에서 확인, MergeSaveTest 88건 통과. Co-Authored-By: Claude Opus 5 --- doc/history/2026-09-14-7차.md | 37 ++++++++++++++++++++ doc/history/changelog.md | 1 + doc/진행상황.md | 2 +- src/MergeHub/Services/GitService.cs | 24 +++++++++++++ src/MergeHub/Services/GitWorkspace.cs | 3 ++ src/MergeHub/Services/IGitService.cs | 3 ++ src/MergeHub/Services/MergeConflictRecord.cs | 27 ++++++++++++++ 7 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 src/MergeHub/Services/MergeConflictRecord.cs diff --git a/doc/history/2026-09-14-7차.md b/doc/history/2026-09-14-7차.md index 22e4bbf..caa622a 100644 --- a/doc/history/2026-09-14-7차.md +++ b/doc/history/2026-09-14-7차.md @@ -86,6 +86,43 @@ MergeSaveTest 70건 통과. 빌드 경고 0 · 오류 0. 설정 파일은 바이 MergeSaveTest 82건 통과. 빌드 경고 0 · 오류 0. 설정 파일은 바이트 그대로 되돌림. 콤보로 브랜치를 고르는 3-Way 는 병합이 아닐 때만 쓰는 길이라 화면으로는 따로 안 봤다 — 문구가 그대로인 것은 하네스로 확인했다. +## B-1. 병합 커밋 뒤에도 `.git/MERGEHUB_CONFLICTS` 가 남는다 (2026-09-14, 완료) + +### 고치기 전에 다시 났다 (새로 만든 저장소, MergeHub 로 병합) + +| 때 | MERGE_HEAD | MERGEHUB_CONFLICTS | +|---|---|---| +| 병합 전 | 없음 | 없음 | +| MergeHub [Merge] 뒤 | 있음 | 있음 | +| [작업 취소] 뒤 | 없음 | **남음** | +| 다시 병합 → 터미널에서 풀고 커밋 → 앱 [새로고침] | 없음 | **남음** | +| (6차 D-1) MergeHub [병합 커밋] 뒤 | 없음 | **남음** | + +### 원인 + +`GitService.RememberMergeConflicts` 가 병합 때 적기만 하고, 병합 커밋 · 작업 취소 · 상태 다시 읽기 어디에도 치우는 코드가 없었다. +첫 줄의 MERGE_HEAD 와 대조하므로(`GetMergeConflictTotal`) 다음 병합의 수가 틀리지는 않았다 - 남의 `.git` 에 우리 파일이 남는 것만 문제였다. + +### 고친 것 + +| 파일 | 무엇 | +|---|---| +| `Services/MergeConflictRecord.cs` (새 파일) | `IsStale(MERGE_HEAD, 첫 줄)` — 병합 중이 아니거나, 빈 파일이거나, 첫 줄이 지금 MERGE_HEAD 와 다르면 쓸모없음 | +| `Services/GitService.cs` | `ForgetStaleMergeConflicts()` — 쓸모없으면 지운다. 잠김 등으로 못 지워도 다른 일을 막지 않는다. `CommitMerge` 뒤 · `AbortOperation`(작업 취소) 뒤에 부른다 | +| `Services/IGitService.cs` | 선언 | +| `Services/GitWorkspace.cs` | `ReadState` 처음에 부른다 — 새로고침 · 저장소 열기 · 모든 Git 작업 뒤. 터미널에서 끝낸 병합도 여기서 치운다 | +| 하네스 `MergeSaveTest` | `IsStale` 검사 6건(병합 아님 · 빈 MERGE_HEAD · 같은 병합은 둠 · 다른 병합 · 대소문자와 공백 · 빈 파일) | + +### 확인한 것 (세 경우 모두 저장소를 새로 만들어) + +| 경우 | 병합 중 | 끝난 뒤 | +|---|---|---| +| ① [Merge] → [작업 취소] | 파일 있음 · 진행 줄 "남은 충돌 24개 · 푼 것 2개 (2/26)" | MERGE_HEAD 없음 · **파일 없음** | +| ② [Merge] → git 으로 풀기만 → [새로고침] → [병합 커밋] | 다 풀고 새로고침해도 **파일 그대로** · "충돌 정리 끝 · 푼 것 26개" (병합 중에는 안 지운다) | 커밋 `5f9d5d9`(부모 둘) · **파일 없음** | +| ③ [Merge] → 터미널에서 풀고 커밋 → [새로고침] | 새로고침 전에는 파일 남음 | 새로고침 뒤 **파일 없음** | + +MergeSaveTest 88건 통과. 빌드 경고 0 · 오류 0. 설정 파일은 바이트 그대로 되돌림. + ## E-2. 폴더 비교 탭에 폴더를 하나씩 끌어다 놓으면 첫 칸 → 둘째 칸 차례로 (2026-09-14, 추가 요청, 완료) 사용자: "폴더비교 탭 첫번째 드래그 첫번째 폴더 경로에 들어가고 두번째는 드래그앤드롭은 두번째로 들어가게해줘 경로텍스트 박스에 안넣어도 그냥 화면에다 넣어둬 기능 동작하도록". diff --git a/doc/history/changelog.md b/doc/history/changelog.md index 095d92b..38de13d 100644 --- a/doc/history/changelog.md +++ b/doc/history/changelog.md @@ -8,6 +8,7 @@ | 날짜 | 항목 | 커밋 | |---|---|---| +| 2026-09-14 | **7차 B-1** — 병합 때 적는 처음 충돌 목록(`.git/MERGEHUB_CONFLICTS`)이 병합 커밋 · 작업 취소 · 터미널에서 끝낸 병합 뒤에도 남았다. MERGE_HEAD 가 없거나 다른 병합의 것이면 병합 커밋 · 작업 취소 직후와 상태를 다시 읽을 때마다 지운다. 병합 중에는 그대로라 "푼 것 M개" 는 이어진다 | 아래 | | 2026-09-14 | **7차 A-2** — 병합 충돌 네 칸에서 Take Theirs 로그가 "10행 (3-Way 안 씀) 반영" 이고 Take Mine 은 로그가 없었다. 이제 "10행 Theirs(feature/theirs) 반영" · "10행 Mine(feature/mine) 으로 정함". 충돌 네 칸 머리는 3-Way 콤보 대신 "병합 충돌 · REMOTE = 상대". 브랜치 3-Way 문구는 그대로 | 아래 | | 2026-09-14 | **7차 E-2** — 폴더 비교 화면 아무 데나 폴더를 하나 놓으면 늘 첫 칸에 들어가 두 번째 폴더가 첫 폴더를 덮어썼다. 놓는 차례대로 첫 칸 → 둘째 칸(양쪽이 차면 바로 비교), 경로 칸 위에 놓은 것도 차례에 친다. 탐색기에서 끌어다 놓아 확인 | 아래 | | 2026-09-14 | **7차 E-3** — 켤 때 사용자 이름 창을 Alt+F4 로 닫으면 이름 없이 본 창을 쓸 수 있었고 창도 끌려 움직였다. 저장 전에는 닫히지 않고 움직이지 않게(플랫폼 `UserNameDialog`, 확인 받고 `D:\UbisamPlatform\bin` 배포). MergeHub 코드 변경 없음 | 아래 | diff --git a/doc/진행상황.md b/doc/진행상황.md index 534b1a7..bff0fcc 100644 --- a/doc/진행상황.md +++ b/doc/진행상황.md @@ -157,7 +157,7 @@ | 상태 | 항목 | |---|---| -| 대기 | B-1 병합 커밋 뒤에도 `.git/MERGEHUB_CONFLICTS` 가 남는다 | +| **완료** | B-1 병합 커밋 뒤에도 `.git/MERGEHUB_CONFLICTS` 가 남는다 — 병합 커밋 · 작업 취소 직후와 새로고침마다 쓸모없어진 목록을 지움(터미널에서 끝낸 병합 포함). 병합 중에는 그대로 | ### C. 병합 말고 다른 충돌 길 diff --git a/src/MergeHub/Services/GitService.cs b/src/MergeHub/Services/GitService.cs index d5bca24..ff41215 100644 --- a/src/MergeHub/Services/GitService.cs +++ b/src/MergeHub/Services/GitService.cs @@ -550,6 +550,7 @@ namespace MergeHub.Services // libgit2 의 reset(HARD) 이 MERGE_HEAD / CHERRY_PICK_HEAD / REVERT_HEAD 까지 정리한다. Repo.Reset(ResetMode.Hard, Repo.Head.Tip); + ForgetStaleMergeConflicts(); // MERGE_HEAD 가 없어졌으니 처음 충돌 목록도 치운다(7차 B-1) Report("작업 취소 (reset --hard HEAD)"); } @@ -880,6 +881,28 @@ namespace MergeHub.Services return lines.Skip(1).Count(l => l.Trim().Length > 0); } + /// + /// 쓸모없어진 충돌 목록 파일을 지운다(7차 B-1). 지웠으면 true. + /// 병합 커밋 · 작업 취소 직후와 상태를 다시 읽을 때마다 부른다 - 터미널에서 병합을 끝낸 경우도 다음 새로고침에 치운다. + /// 못 지워도(잠김 등) 다른 일을 막지 않는다. 다음에 다시 시도한다. + /// + public bool ForgetStaleMergeConflicts() + { + var path = Path.Combine(Repo.Info.Path, MergeConflictListFile); + if (!File.Exists(path)) return false; + + try + { + var first = File.ReadAllLines(path).FirstOrDefault(); + if (!MergeConflictRecord.IsStale(ReadMergeHeadSha(), first)) return false; + + File.Delete(path); + return true; + } + catch (IOException) { return false; } + catch (UnauthorizedAccessException) { return false; } + } + public ThreeWayContent GetThreeWayContent(string path) { var normalized = NormalizePath(path); @@ -1019,6 +1042,7 @@ namespace MergeHub.Services var signature = BuildSignature(); var commit = Repo.Commit(message, signature, signature); + ForgetStaleMergeConflicts(); // 병합이 끝났으니 처음 충돌 목록도 치운다(7차 B-1) Report($"병합 커밋: {Shorten(commit.Sha)} {commit.MessageShort}"); return commit.Sha; } diff --git a/src/MergeHub/Services/GitWorkspace.cs b/src/MergeHub/Services/GitWorkspace.cs index f81c339..5e28b2b 100644 --- a/src/MergeHub/Services/GitWorkspace.cs +++ b/src/MergeHub/Services/GitWorkspace.cs @@ -335,6 +335,9 @@ namespace MergeHub.Services { if (!Git.IsOpen) return; + // 병합이 끝났는데 남은 처음 충돌 목록을 치운다 - MergeHub 밖(터미널)에서 끝낸 병합도 여기서 걸린다(7차 B-1). + Git.ForgetStaleMergeConflicts(); + var info = Git.GetRepositoryInfo(); var branches = Git.GetBranches(); var commits = Git.GetCommits(maxCount: Settings.CommitPageSize <= 0 ? 200 : Settings.CommitPageSize); diff --git a/src/MergeHub/Services/IGitService.cs b/src/MergeHub/Services/IGitService.cs index cfb3649..fa93730 100644 --- a/src/MergeHub/Services/IGitService.cs +++ b/src/MergeHub/Services/IGitService.cs @@ -82,6 +82,9 @@ namespace MergeHub.Services /// 이번 병합에서 처음 난 충돌 파일 수. MergeHub 가 병합할 때 적어 둔 것, 모르면 null(6차 C-1). int? GetMergeConflictTotal(); + /// 쓸모없어진 처음 충돌 목록(.git/MERGEHUB_CONFLICTS)을 지운다. 지웠으면 true(7차 B-1). + bool ForgetStaleMergeConflicts(); + /// 충돌 파일의 Base/Mine/Theirs 3분할 본문. ThreeWayContent GetThreeWayContent(string path); diff --git a/src/MergeHub/Services/MergeConflictRecord.cs b/src/MergeHub/Services/MergeConflictRecord.cs new file mode 100644 index 0000000..1515862 --- /dev/null +++ b/src/MergeHub/Services/MergeConflictRecord.cs @@ -0,0 +1,27 @@ +using System; + +namespace MergeHub.Services +{ + /// + /// 병합 때 처음 충돌 목록을 적어 두는 .git/MERGEHUB_CONFLICTS 를 치울지 가른다(7차 B-1). + /// + /// 이 파일은 "푼 것 M개 (M/전체)" 를 세려고 둔다(6차 C-1). 첫 줄이 그때의 MERGE_HEAD 라 다른 병합과 섞이지는 않지만, + /// 지우는 곳이 없어 병합 커밋 · 작업 취소 · 터미널에서 끝낸 병합 뒤에도 남의 .git 에 남았다. + /// 판단만 여기 두어 저장소 없이 검사한다. 지우는 것은 가 한다. + /// + public static class MergeConflictRecord + { + /// + /// 적어 둔 것이 이제 쓸모없는지. 병합 중이 아니면(MERGE_HEAD 없음) 쓸모없다. + /// 병합 중이어도 첫 줄이 지금 MERGE_HEAD 와 다르면 다른 병합의 것이라 쓸모없다. + /// + /// 지금 MERGE_HEAD. 병합 중이 아니면 null. + /// 적어 둔 파일의 첫 줄. 빈 파일이면 null. + public static bool IsStale(string? mergeHeadSha, string? recordFirstLine) + { + if (string.IsNullOrWhiteSpace(mergeHeadSha)) return true; + if (string.IsNullOrWhiteSpace(recordFirstLine)) return true; + return !string.Equals(recordFirstLine!.Trim(), mergeHeadSha!.Trim(), StringComparison.OrdinalIgnoreCase); + } + } +}