7차 B-1: 병합이 끝나면 .git/MERGEHUB_CONFLICTS 를 치운다
병합 때 적는 처음 충돌 목록이 병합 커밋 · 작업 취소 · 터미널에서 끝낸 병합 뒤에도 남았다. MERGE_HEAD 가 없거나 다른 병합의 것이면 병합 커밋 · 작업 취소 직후와 상태를 다시 읽을 때마다 지운다. 병합 중에는 그대로라 "푼 것 M개 (M/전체)" 는 이어진다. 세 경우 화면에서 확인, MergeSaveTest 88건 통과. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
1db788f1a0
commit
bb6bd655a5
@ -86,6 +86,43 @@ MergeSaveTest 70건 통과. 빌드 경고 0 · 오류 0. 설정 파일은 바이
|
|||||||
MergeSaveTest 82건 통과. 빌드 경고 0 · 오류 0. 설정 파일은 바이트 그대로 되돌림.
|
MergeSaveTest 82건 통과. 빌드 경고 0 · 오류 0. 설정 파일은 바이트 그대로 되돌림.
|
||||||
콤보로 브랜치를 고르는 3-Way 는 병합이 아닐 때만 쓰는 길이라 화면으로는 따로 안 봤다 — 문구가 그대로인 것은 하네스로 확인했다.
|
콤보로 브랜치를 고르는 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, 추가 요청, 완료)
|
## E-2. 폴더 비교 탭에 폴더를 하나씩 끌어다 놓으면 첫 칸 → 둘째 칸 차례로 (2026-09-14, 추가 요청, 완료)
|
||||||
|
|
||||||
사용자: "폴더비교 탭 첫번째 드래그 첫번째 폴더 경로에 들어가고 두번째는 드래그앤드롭은 두번째로 들어가게해줘 경로텍스트 박스에 안넣어도 그냥 화면에다 넣어둬 기능 동작하도록".
|
사용자: "폴더비교 탭 첫번째 드래그 첫번째 폴더 경로에 들어가고 두번째는 드래그앤드롭은 두번째로 들어가게해줘 경로텍스트 박스에 안넣어도 그냥 화면에다 넣어둬 기능 동작하도록".
|
||||||
|
|||||||
@ -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차 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-2** — 폴더 비교 화면 아무 데나 폴더를 하나 놓으면 늘 첫 칸에 들어가 두 번째 폴더가 첫 폴더를 덮어썼다. 놓는 차례대로 첫 칸 → 둘째 칸(양쪽이 차면 바로 비교), 경로 칸 위에 놓은 것도 차례에 친다. 탐색기에서 끌어다 놓아 확인 | 아래 |
|
||||||
| 2026-09-14 | **7차 E-3** — 켤 때 사용자 이름 창을 Alt+F4 로 닫으면 이름 없이 본 창을 쓸 수 있었고 창도 끌려 움직였다. 저장 전에는 닫히지 않고 움직이지 않게(플랫폼 `UserNameDialog`, 확인 받고 `D:\UbisamPlatform\bin` 배포). MergeHub 코드 변경 없음 | 아래 |
|
| 2026-09-14 | **7차 E-3** — 켤 때 사용자 이름 창을 Alt+F4 로 닫으면 이름 없이 본 창을 쓸 수 있었고 창도 끌려 움직였다. 저장 전에는 닫히지 않고 움직이지 않게(플랫폼 `UserNameDialog`, 확인 받고 `D:\UbisamPlatform\bin` 배포). MergeHub 코드 변경 없음 | 아래 |
|
||||||
|
|||||||
@ -157,7 +157,7 @@
|
|||||||
|
|
||||||
| 상태 | 항목 |
|
| 상태 | 항목 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| 대기 | B-1 병합 커밋 뒤에도 `.git/MERGEHUB_CONFLICTS` 가 남는다 |
|
| **완료** | B-1 병합 커밋 뒤에도 `.git/MERGEHUB_CONFLICTS` 가 남는다 — 병합 커밋 · 작업 취소 직후와 새로고침마다 쓸모없어진 목록을 지움(터미널에서 끝낸 병합 포함). 병합 중에는 그대로 |
|
||||||
|
|
||||||
### C. 병합 말고 다른 충돌 길
|
### C. 병합 말고 다른 충돌 길
|
||||||
|
|
||||||
|
|||||||
@ -550,6 +550,7 @@ namespace MergeHub.Services
|
|||||||
|
|
||||||
// libgit2 의 reset(HARD) 이 MERGE_HEAD / CHERRY_PICK_HEAD / REVERT_HEAD 까지 정리한다.
|
// libgit2 의 reset(HARD) 이 MERGE_HEAD / CHERRY_PICK_HEAD / REVERT_HEAD 까지 정리한다.
|
||||||
Repo.Reset(ResetMode.Hard, Repo.Head.Tip);
|
Repo.Reset(ResetMode.Hard, Repo.Head.Tip);
|
||||||
|
ForgetStaleMergeConflicts(); // MERGE_HEAD 가 없어졌으니 처음 충돌 목록도 치운다(7차 B-1)
|
||||||
Report("작업 취소 (reset --hard HEAD)");
|
Report("작업 취소 (reset --hard HEAD)");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -880,6 +881,28 @@ namespace MergeHub.Services
|
|||||||
return lines.Skip(1).Count(l => l.Trim().Length > 0);
|
return lines.Skip(1).Count(l => l.Trim().Length > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 쓸모없어진 충돌 목록 파일을 지운다(7차 B-1). 지웠으면 true.
|
||||||
|
/// 병합 커밋 · 작업 취소 직후와 상태를 다시 읽을 때마다 부른다 - 터미널에서 병합을 끝낸 경우도 다음 새로고침에 치운다.
|
||||||
|
/// 못 지워도(잠김 등) 다른 일을 막지 않는다. 다음에 다시 시도한다.
|
||||||
|
/// </summary>
|
||||||
|
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)
|
public ThreeWayContent GetThreeWayContent(string path)
|
||||||
{
|
{
|
||||||
var normalized = NormalizePath(path);
|
var normalized = NormalizePath(path);
|
||||||
@ -1019,6 +1042,7 @@ namespace MergeHub.Services
|
|||||||
|
|
||||||
var signature = BuildSignature();
|
var signature = BuildSignature();
|
||||||
var commit = Repo.Commit(message, signature, signature);
|
var commit = Repo.Commit(message, signature, signature);
|
||||||
|
ForgetStaleMergeConflicts(); // 병합이 끝났으니 처음 충돌 목록도 치운다(7차 B-1)
|
||||||
Report($"병합 커밋: {Shorten(commit.Sha)} {commit.MessageShort}");
|
Report($"병합 커밋: {Shorten(commit.Sha)} {commit.MessageShort}");
|
||||||
return commit.Sha;
|
return commit.Sha;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -335,6 +335,9 @@ namespace MergeHub.Services
|
|||||||
{
|
{
|
||||||
if (!Git.IsOpen) return;
|
if (!Git.IsOpen) return;
|
||||||
|
|
||||||
|
// 병합이 끝났는데 남은 처음 충돌 목록을 치운다 - MergeHub 밖(터미널)에서 끝낸 병합도 여기서 걸린다(7차 B-1).
|
||||||
|
Git.ForgetStaleMergeConflicts();
|
||||||
|
|
||||||
var info = Git.GetRepositoryInfo();
|
var info = Git.GetRepositoryInfo();
|
||||||
var branches = Git.GetBranches();
|
var branches = Git.GetBranches();
|
||||||
var commits = Git.GetCommits(maxCount: Settings.CommitPageSize <= 0 ? 200 : Settings.CommitPageSize);
|
var commits = Git.GetCommits(maxCount: Settings.CommitPageSize <= 0 ? 200 : Settings.CommitPageSize);
|
||||||
|
|||||||
@ -82,6 +82,9 @@ namespace MergeHub.Services
|
|||||||
/// <summary>이번 병합에서 처음 난 충돌 파일 수. MergeHub 가 병합할 때 적어 둔 것, 모르면 null(6차 C-1).</summary>
|
/// <summary>이번 병합에서 처음 난 충돌 파일 수. MergeHub 가 병합할 때 적어 둔 것, 모르면 null(6차 C-1).</summary>
|
||||||
int? GetMergeConflictTotal();
|
int? GetMergeConflictTotal();
|
||||||
|
|
||||||
|
/// <summary>쓸모없어진 처음 충돌 목록(.git/MERGEHUB_CONFLICTS)을 지운다. 지웠으면 true(7차 B-1).</summary>
|
||||||
|
bool ForgetStaleMergeConflicts();
|
||||||
|
|
||||||
/// <summary>충돌 파일의 Base/Mine/Theirs 3분할 본문.</summary>
|
/// <summary>충돌 파일의 Base/Mine/Theirs 3분할 본문.</summary>
|
||||||
ThreeWayContent GetThreeWayContent(string path);
|
ThreeWayContent GetThreeWayContent(string path);
|
||||||
|
|
||||||
|
|||||||
27
src/MergeHub/Services/MergeConflictRecord.cs
Normal file
27
src/MergeHub/Services/MergeConflictRecord.cs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace MergeHub.Services
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 병합 때 처음 충돌 목록을 적어 두는 <c>.git/MERGEHUB_CONFLICTS</c> 를 치울지 가른다(7차 B-1).
|
||||||
|
///
|
||||||
|
/// 이 파일은 "푼 것 M개 (M/전체)" 를 세려고 둔다(6차 C-1). 첫 줄이 그때의 MERGE_HEAD 라 다른 병합과 섞이지는 않지만,
|
||||||
|
/// 지우는 곳이 없어 병합 커밋 · 작업 취소 · 터미널에서 끝낸 병합 뒤에도 남의 <c>.git</c> 에 남았다.
|
||||||
|
/// 판단만 여기 두어 저장소 없이 검사한다. 지우는 것은 <see cref="GitService"/> 가 한다.
|
||||||
|
/// </summary>
|
||||||
|
public static class MergeConflictRecord
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 적어 둔 것이 이제 쓸모없는지. 병합 중이 아니면(MERGE_HEAD 없음) 쓸모없다.
|
||||||
|
/// 병합 중이어도 첫 줄이 지금 MERGE_HEAD 와 다르면 다른 병합의 것이라 쓸모없다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mergeHeadSha">지금 MERGE_HEAD. 병합 중이 아니면 null.</param>
|
||||||
|
/// <param name="recordFirstLine">적어 둔 파일의 첫 줄. 빈 파일이면 null.</param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user