From aa3f17939003b72b73cbd5007e949c741a140662 Mon Sep 17 00:00:00 2001 From: ksj09406 Date: Fri, 18 Sep 2026 16:58:59 +0900 Subject: [PATCH] =?UTF-8?q?11=EC=B0=A8=20C-1(=EC=9D=BC=EB=B6=80):=20[?= =?UTF-8?q?=EC=BB=A4=EB=B0=8B=20=EC=88=98=EC=A0=95]=20=EC=9D=84=20?= =?UTF-8?q?=EB=8B=A4=EC=8B=9C=20=EB=82=B4=20=EB=B3=B4=EA=B3=A0=20=EB=84=B7?= =?UTF-8?q?=EC=9D=84=20=EB=B0=94=EB=A1=9C=EC=9E=A1=EC=9D=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 화면에서 여덟 가지를 먼저 재현하고, 방향이 정해진 1 · 3 · 4 · 7 만 고쳤다. - [마지막 메시지] 는 이력 맨 위가 아니라 HEAD 커밋 메시지를 읽는다 (CommitSelection.MessageOfHead) - 고쳐 써도 원래 작성자 · 작성 시각을 지킨다 (git 과 같게, 커밋한 사람만 지금) - 병합 · rebase · cherry-pick · revert 중에는 단추를 끈다 (CanAmend) · 꺼진 까닭을 단추 설명으로 - 알림에 '커밋 수정 완료 (옛 bcd2c43 → 새 fd7810c) · 파일 1개 포함' - GitWorkspace 가 HeadFullSha 를 들고 있는다 - 하네스 MergeSaveTest +15 (210 → 225), 22벌 1,200건 통과, 빌드 경고 0 오류 0 남은 결정: 2(스테이징 파일이 함께 들어가는 것) · 6(이미 올린 커밋). 8(여러 줄 메시지)은 범위 밖으로 적어 둠. Co-Authored-By: Claude Opus 5 --- doc/history/2026-09-18-11차.md | 46 +++++++++++++++++++ doc/history/changelog.md | 1 + doc/진행상황.md | 2 +- src/MergeHub/Services/CommitSelection.cs | 51 +++++++++++++++++++++ src/MergeHub/Services/GitService.cs | 10 ++++- src/MergeHub/Services/GitWorkspace.cs | 4 ++ src/MergeHub/ViewModels/GitViewModel.cs | 56 ++++++++++++++++++++---- src/MergeHub/Views/GitView.xaml | 3 +- 8 files changed, 160 insertions(+), 13 deletions(-) diff --git a/doc/history/2026-09-18-11차.md b/doc/history/2026-09-18-11차.md index e6ba3ea..f79c6f8 100644 --- a/doc/history/2026-09-18-11차.md +++ b/doc/history/2026-09-18-11차.md @@ -127,6 +127,52 @@ A-1 에서 값으로 확인한 것 — 지운 파일은 "삭제되어 건너뜀 - A-4(ZIP)는 아직 정하지 않았다. - 매뉴얼 · 웹 문서의 변경분 추출 설명(새 폴더 · 안내 파일)은 E-2 에서 함께 고친다. +## C-1. 있는 [커밋 수정] 을 다시 내 보고 바로잡음 (2026-09-18, 일부 완료 — 2 · 6 은 결정 대기) + +### 고치기 전에 여덟 가지를 화면에서 다시 냈다 + +시험 저장소: `C1Repo` 위에 `amend` 브랜치를 만들고 **작성자 박지훈인 커밋**을 HEAD 로, 다른 브랜치(`feature/revert`)에는 **더 새 커밋**을, 그리고 bare 저장소를 원격으로 걸어 upstream 을 두었다(`e11-c1-setup.sh`). + +| # | 고치기 전에 본 것 | +|---|---| +| 1 | [모든 브랜치] 켠 채 [마지막 메시지] → 칸에 `딴 브랜치 최신 작업`(HEAD 아님). 그대로 [커밋 수정] 하니 **HEAD 제목이 그 메시지로 바뀌었다** | +| 2 | 앱에서 [전체 스테이징] 한 파일이 **말없이 커밋에 들어갔다**(남은 변경 없음). 알림은 "커밋 수정" 뿐 | +| 3 | 작성자 `박지훈` · 작성 `2026-09-15 10:00` → **`시험` · `2026-09-18 15:48`** 로 바뀜 | +| 4 | 충돌 24개가 남은 채로도 단추가 **켜져 있고**, 누르면 `[커밋 수정] 실패: cannot create a tree from a not fully merged index` | +| 5 | 병합 커밋을 고쳐도 **부모 둘 · 본문(충돌 해결 26개 · 29줄) 그대로** | +| 6 | 원격에 이미 올라간 커밋도 **막지 않고** 고쳐졌고, 그 뒤 [Push] 가 `cannot push non-fastforwardable reference` 로 거부됨 | +| 7 | 알림 · 로그에 **새 해시만**(`커밋: 61653f5 …`). 고치기 전 해시는 어디에도 없음 | +| 8 | 커밋 메시지 칸이 한 줄짜리라 **여러 줄 메시지를 아예 칠 수 없다**. `#` 로 시작하는 글자는 그대로 들어간다 | + +### 고친 것 (1 · 3 · 4 · 7) + +| 자리 | 한 일 | +|---|---| +| `Services/CommitSelection.cs` | `MessageOfHead` — [마지막 메시지] 는 **HEAD 커밋**의 메시지를 읽는다(목록 순서와 무관). HEAD 가 목록에 없으면 아무것도 주지 않는다. `CanAmend` — **작업 중이면 끈다**. `AmendHint` — 꺼진 까닭을 단추 설명으로. `AmendDone` — "커밋 수정 완료 (옛 abc1234 → 새 def5678) · 파일 N개 포함" | +| `Services/GitService.cs` | 고쳐 쓸 때 **원래 작성자 · 작성 시각을 그대로** 쓴다(git 과 같게). 커밋한 사람 · 시각만 지금 | +| `Services/GitWorkspace.cs` | `HeadFullSha` 를 들고 있는다 — 이력 목록의 Sha 와 맞춰 보려면 짧은 해시로는 모자란다 | +| `ViewModels/GitViewModel.cs` | 위 판단을 붙이고, 고치기 전 해시 · 함께 들어간 파일 수를 알림에. 작업이 시작 · 끝날 때 단추 설명도 다시 그린다 | +| `Views/GitView.xaml` | [커밋 수정] 설명을 상황에 따라 바뀌는 값으로 | +| 하네스 `MergeSaveTest` | **+15건(210 → 225)** — HEAD 메시지 읽기(맨 위가 딴 브랜치여도 · 대소문자 · HEAD 가 목록에 없음 · 빈 목록) · 켜는 조건 넷 · 꺼진 까닭 · 알림 글 셋 | + +### 고친 뒤 같은 순서로 다시 + +| # | 결과 | +|---|---| +| 1 | 칸에 **`남이 만든 커밋`**(HEAD 것) | +| 3 | 고친 뒤에도 작성자 `박지훈` · 작성 `2026-09-15 10:00` **그대로**, 커밋한 사람 · 시각만 지금 | +| 4 | 충돌 24개가 남은 동안 단추 **꺼짐** — 누를 수 없고, 누른 뒤 실패 알림도 없다 | +| 7 | 알림 **`커밋 수정 완료 (옛 bcd2c43 → 새 fd7810c) · 파일 1개 포함`** | + +- 하네스 22벌 **1,200건** 통과(1,185 → +15) · 빌드 경고 0 · 오류 0. + +### 남은 것 (결정 대기) + +- **2. 스테이징한 파일이 함께 들어가는 것** — 지금은 들어가되 알림에 "파일 N개 포함" 으로 드러내기만 한다. 묻기 / 메시지만 고치기(git `--only`) / 지금대로 중 정해야 한다. +- **6. 이미 올린 커밋** — 지금도 막지 않는다(그 뒤 Push 가 거부된다). 묻기 / 막기 / 매뉴얼에만 중 정해야 한다. +- **8. 여러 줄 커밋 메시지** — 칸이 한 줄이라 본문을 칠 수 없다. 이번 범위 밖으로 적어 둔다(병합 커밋 본문은 앱이 저절로 붙인다). +- C-2(마지막이 아닌 커밋의 메시지 수정)는 C-1 을 쓰면서 더 필요한지 보고 정한다. + ## F-12. C# 코드 스타일 맞춤의 기본값을 AURORA 로 (2026-09-18, 완료) 사용자: "기본값은 원본형태를 따르고 C#인 경우에 AURORA 스타일로 하자" → 물어서 정한 것은 **제품 기본값 AURORA · C# 파일만 · 저장 동작은 지금 그대로**(켜져 있으면 저장도 그 스타일로 쓴다). diff --git a/doc/history/changelog.md b/doc/history/changelog.md index 6519a38..ef69174 100644 --- a/doc/history/changelog.md +++ b/doc/history/changelog.md @@ -8,6 +8,7 @@ | 날짜 | 항목 | 커밋 | |---|---|---| +| 2026-09-18 | **11차 C-1 (일부)** — [커밋 수정]의 여덟 가지를 화면에서 재현한 뒤 넷을 고침: [마지막 메시지]가 이력 맨 위(딴 브랜치일 수 있음) 대신 **HEAD 커밋** 메시지를 읽고, 고쳐 써도 **원래 작성자 · 작성 시각을 지키며**(git 과 같게), 병합 · rebase · cherry-pick · revert **진행 중에는 단추를 끄고**(예전엔 눌러서 실패), 알림에 **"옛 해시 → 새 해시 · 파일 N개 포함"**. 하네스 MergeSaveTest +15(225건). 스테이징 파일 처리(2)와 이미 올린 커밋(6)은 사용자 결정 대기 | 아래 | | 2026-09-18 | **11차 F-1 에 덧 (추가 요청)** — 지시서의 F-1(칸 이름 · 어느 파일인지)에 "머지 주석에도 어디서 가져왔는지(REMOTE/BASE · 브랜치/해시)를 남기고, 그에 맞춰 무시 조건도 고친다" 를 적음. 고칠 곳(`MergeNote.NoteBody` · `LooksLikeNote` · `TailStart` · `CompareRules` · `FolderComparer` · `NoteConflict` · `NoteRows`)과 옛 주석도 그대로 무시되어야 한다는 기준까지. 코드 변경 없음 | 아래 | | 2026-09-18 | **11차 A-3** — 변경분을 고른 폴더에 바로 쏟지 않고 **그 아래 새 폴더**(`변경분_범위_날짜`, 같은 이름이면 `_2`)를 만들어 뽑는다(사용자 결정). 예전에는 묻지 않고 덮어써 지난번에 뽑은 것과 사람이 넣어 둔 파일이 섞였다. 또 뽑기 전에 읽기 전용 · 열려 있는 파일을 **먼저 보고** 하나라도 막히면 아무것도 쓰지 않는다 — 반쯤 뽑힌 폴더가 남지 않는다. 하네스 ReportTest +12(57건) | 아래 | | 2026-09-18 | **11차 A-2** — 변경분을 뽑으면 **폴더 옆에 안내 파일**(`…_변경분안내.txt`)을 함께 만든다(사용자 결정: 폴더 밖 · 지울 파일은 목록으로만). A-1 에서 지운 파일 · 이름 바뀐 옛 경로 · 뽑은 범위가 결과에 안 남는 것을 확인했기 때문. 안내에 범위(커밋 제목 · 날짜) · 뽑은 파일(추가 · 수정 · 이름 바뀜은 옛 이름 병기) · **장비에서 지워야 할 파일**을 적는다. 추출이 무엇을 했는지 목록으로 돌려주도록 `ExportChangedFiles` 를 고치고, 안내를 못 써도 추출은 성공으로 둔다. 하네스 ReportTest +14(45건) | 아래 | diff --git a/doc/진행상황.md b/doc/진행상황.md index 851d2eb..8a68cea 100644 --- a/doc/진행상황.md +++ b/doc/진행상황.md @@ -350,7 +350,7 @@ | 상태 | 항목 | |---|---| -| 대기 | C-1 있는 [커밋 수정] 을 다시 내 보고 바로잡기 — [마지막 메시지] 가 목록 맨 위(다른 브랜치일 수 있음) · 스테이징 파일이 말없이 들어감 · 원래 작성자 · 시각이 바뀜 · 작업 중에도 켜짐 · 병합 커밋 · 올린 커밋 · 옛 해시 · `#` 줄 | +| **일부** | C-1 있는 [커밋 수정] 바로잡기 — 여덟 가지를 화면에서 재현한 뒤 **1 · 3 · 4 · 7 고침**([마지막 메시지]가 HEAD 것 · 작성자와 작성 시각 보존 · 작업 중 단추 끔 · 알림에 "옛 → 새 해시 · 파일 N개 포함"). 하네스 MergeSaveTest +15(225건). **남은 결정**: 2(스테이징 파일 함께 들어감 — 묻기/메시지만/지금대로) · 6(이미 올린 커밋 — 묻기/막기/매뉴얼). 옛 항목: — [마지막 메시지] 가 목록 맨 위(다른 브랜치일 수 있음) · 스테이징 파일이 말없이 들어감 · 원래 작성자 · 시각이 바뀜 · 작업 중에도 켜짐 · 병합 커밋 · 올린 커밋 · 옛 해시 · `#` 줄 | | 대기 | C-2 마지막 커밋이 아닌 커밋의 메시지 수정 (기획안 "커밋 우클릭 → 이름 변경") — 할지 먼저 정함 | ### D. 시험 도구 정리 diff --git a/src/MergeHub/Services/CommitSelection.cs b/src/MergeHub/Services/CommitSelection.cs index af0da32..c27f515 100644 --- a/src/MergeHub/Services/CommitSelection.cs +++ b/src/MergeHub/Services/CommitSelection.cs @@ -61,6 +61,57 @@ namespace MergeHub.Services /// 작업이 진행 중이고 남은 충돌이 없는가. public static bool IsResolvedOperation(bool inProgress, int conflictCount) => inProgress && conflictCount == 0; + /// + /// [마지막 메시지] 가 가져올 메시지(11차 C-1). + /// + /// 예전에는 이력 목록의 맨 위(Commits[0])를 가져왔다. [모든 브랜치] 를 켜면 맨 위가 + /// 다른 브랜치의 더 새 커밋이라, 그 메시지를 가져와 그대로 [커밋 수정] 하면 + /// HEAD 제목이 남의 커밋 메시지로 바뀌었다(값으로 확인). 고치는 대상은 늘 HEAD 이므로 HEAD 것을 읽는다. + /// HEAD 가 목록에 없으면(조회 개수 밖으로 밀렸을 때) 아무것도 주지 않는다 - 엉뚱한 것을 주느니 빈 칸이 낫다. + /// + public static string? MessageOfHead(IReadOnlyList<(string Sha, string Message)> commits, string? headSha) + { + if (commits is null || commits.Count == 0 || string.IsNullOrEmpty(headSha)) return null; + + foreach (var commit in commits) + if (SameSha(commit.Sha, headSha)) return commit.Message; + + return null; + } + + /// + /// [커밋 수정] 을 켤 수 있는가(11차 C-1). + /// + /// 예전에는 저장소가 열려 있고 메시지 칸이 비어 있지 않기만 하면 켜졌다. 병합 · rebase · cherry-pick · + /// revert 가 도는 중에도 켜져 있어서 누르면 cannot create a tree from a not fully merged index 로 + /// 실패했다(값으로 확인). git 도 병합 중 --amend 를 막는다. 작업 중에는 아예 끈다. + /// + public static bool CanAmend(bool repositoryOpen, bool hasMessage, bool operationInProgress) => + repositoryOpen && hasMessage && !operationInProgress; + + /// 작업 중이라 [커밋 수정] 이 꺼졌을 때 단추 설명에 쓸 말. + public static string AmendHint(bool operationInProgress) => + operationInProgress + ? "병합 · rebase · cherry-pick · revert 를 끝낸 뒤에 쓸 수 있습니다" + : "마지막 커밋을 고쳐 씁니다 (git commit --amend). 푸시하기 전에만 쓰세요"; + + /// + /// 커밋을 고친 뒤 알릴 글(11차 C-1). + /// + /// 예전에는 "커밋 수정 완료 (새해시)" 라 고치기 전 해시가 어디에도 남지 않았다. 되돌리려면 + /// reflog 를 뒤져야 했다. 함께 들어간 파일 수도 적는다 - 스테이징해 둔 것이 조용히 들어가는 것을 보이게. + /// + public static string AmendDone(string? oldSha, string? newSha, int fileCount) + { + var from = Short(oldSha); + var to = Short(newSha); + var files = fileCount > 0 ? $" · 파일 {fileCount}개 포함" : string.Empty; + return $"커밋 수정 완료 (옛 {from} → 새 {to}){files}"; + } + + private static string Short(string? sha) => + string.IsNullOrEmpty(sha) ? "-" : (sha!.Length > 7 ? sha.Substring(0, 7) : sha); + private static bool SameSha(string? a, string? b) => !string.IsNullOrEmpty(a) && string.Equals(a, b, StringComparison.OrdinalIgnoreCase); diff --git a/src/MergeHub/Services/GitService.cs b/src/MergeHub/Services/GitService.cs index 17c9f3f..55dcbc9 100644 --- a/src/MergeHub/Services/GitService.cs +++ b/src/MergeHub/Services/GitService.cs @@ -483,8 +483,14 @@ namespace MergeHub.Services if (string.IsNullOrWhiteSpace(message)) throw new ArgumentException("커밋 메시지가 비어 있습니다.", nameof(message)); - var signature = BuildSignature(); - var commit = Repo.Commit(message, signature, signature, new CommitOptions { AmendPreviousCommit = amend }); + var now = BuildSignature(); + + // 고쳐 쓸 때는 원래 작성자와 작성 시각을 그대로 둔다(11차 C-1). git 도 그렇게 한다 + // (`--reset-author` 를 줄 때만 바뀐다). 예전에는 남이 만든 커밋(cherry-pick 으로 가져온 것)의 + // 메시지만 고쳐도 작성자가 지금 사람으로 바뀌어, 누가 쓴 코드인지가 사라졌다. + var author = amend && Repo.Head.Tip is { } tip ? tip.Author : now; + + var commit = Repo.Commit(message, author, now, new CommitOptions { AmendPreviousCommit = amend }); Report($"커밋: {commit.Sha[..7]} {commit.MessageShort}"); return commit.Sha; } diff --git a/src/MergeHub/Services/GitWorkspace.cs b/src/MergeHub/Services/GitWorkspace.cs index 25a7f84..7fa0b59 100644 --- a/src/MergeHub/Services/GitWorkspace.cs +++ b/src/MergeHub/Services/GitWorkspace.cs @@ -155,6 +155,9 @@ namespace MergeHub.Services [ObservableProperty] private string _repositoryName = "-"; [ObservableProperty] private string _currentBranch = "-"; [ObservableProperty] private string _headSha = "-"; + + /// HEAD 의 전체 해시. 이력 목록()의 Sha 와 맞춰 볼 때 쓴다(11차 C-1). + public string? HeadFullSha { get; private set; } [ObservableProperty] private string _workingTreeSummary = "저장소 없음"; [ObservableProperty] private bool _isWorkingTreeClean = true; [ObservableProperty] private bool _isRepositoryOpen; @@ -410,6 +413,7 @@ namespace MergeHub.Services RepositoryName = GetRepositoryName(info.WorkingDirectory); CurrentBranch = info.CurrentBranchName ?? "(detached)"; HeadSha = string.IsNullOrEmpty(info.HeadSha) ? "-" : info.HeadSha!.Substring(0, 7); + HeadFullSha = info.HeadSha; IsRepositoryOpen = true; IsRemoteConnected = remotes.Count > 0; diff --git a/src/MergeHub/ViewModels/GitViewModel.cs b/src/MergeHub/ViewModels/GitViewModel.cs index dac82f1..6b138d1 100644 --- a/src/MergeHub/ViewModels/GitViewModel.cs +++ b/src/MergeHub/ViewModels/GitViewModel.cs @@ -646,30 +646,67 @@ namespace MergeHub.ViewModels // ================= 커밋 수정 (amend) ================= - private bool CanAmend() => IsOpen() && !string.IsNullOrWhiteSpace(CommitMessage); + /// + /// [커밋 수정] 을 켤 조건. 작업 중에는 끈다(11차 C-1) - 병합 · rebase · cherry-pick · revert 가 + /// 도는 중에 누르면 "cannot create a tree from a not fully merged index" 로 실패했다. + /// + private bool CanAmend() => + CommitSelection.CanAmend(IsOpen(), !string.IsNullOrWhiteSpace(CommitMessage), AmendBlockedByOperation); + + /// 병합 · rebase · cherry-pick · revert 가 도는 중인가. + private bool AmendBlockedByOperation => + _workspace.IsMergeInProgress || _workspace.IsRebaseInProgress || _workspace.IsOperationInProgress; + + /// 단추 설명. 작업 중이면 왜 꺼졌는지 말한다. + public string AmendHint => CommitSelection.AmendHint(AmendBlockedByOperation); /// /// 마지막 커밋을 고쳐 쓴다(`git commit --amend`). /// /// 오타 난 메시지나 빠뜨린 파일을 새 커밋을 쌓지 않고 바로잡을 때 쓴다. /// 이미 올린 커밋을 고치면 남의 이력과 갈라지므로, 푸시 전에만 쓰는 것이 원칙이다. + /// + /// 고치기 전 해시를 알림에 남긴다(11차 C-1) - 예전에는 새 해시만 알려 되돌릴 실마리가 없었다. /// [RelayCommand(CanExecute = nameof(CanAmend))] - private Task AmendAsync() => _workspace.RunAndRefreshAsync("커밋 수정", () => + private Task AmendAsync() { - var sha = _workspace.Git.Commit(CommitMessage, amend: true); - _workspace.OnUi(() => + var oldSha = _workspace.HeadFullSha; + + return _workspace.RunAndRefreshAsync("커밋 수정", () => { - CommitMessage = string.Empty; - MessageUtil.ShowSuccessToast($"커밋 수정 완료 ({sha.Substring(0, 7)})"); + var staged = _workspace.Git.GetStatus().StagedCount; + var sha = _workspace.Git.Commit(CommitMessage, amend: true); + _workspace.OnUi(() => + { + CommitMessage = string.Empty; + MessageUtil.ShowSuccessToast(CommitSelection.AmendDone(oldSha, sha, staged)); + }); }); - }); + } private bool CanLoadLastMessage() => IsOpen() && _workspace.Commits.Count > 0; - /// 고칠 메시지를 처음부터 다시 치지 않도록 마지막 커밋의 메시지를 가져온다. + /// + /// 고칠 메시지를 처음부터 다시 치지 않도록 HEAD 커밋의 메시지를 가져온다. + /// + /// 예전에는 이력 목록의 맨 위(Commits[0])를 가져왔다. [모든 브랜치] 를 켜면 맨 위가 다른 브랜치의 + /// 더 새 커밋이라, 그대로 [커밋 수정] 하면 HEAD 제목이 남의 커밋 메시지로 바뀌었다(11차 C-1). + /// [RelayCommand(CanExecute = nameof(CanLoadLastMessage))] - private void LoadLastMessage() => CommitMessage = _workspace.Commits[0].Message.TrimEnd(); + private void LoadLastMessage() + { + var pairs = _workspace.Commits.Select(c => (c.Sha, c.Message)).ToList(); + var message = CommitSelection.MessageOfHead(pairs, _workspace.HeadFullSha); + + if (message is null) + { + MessageUtil.ShowWarningToast("HEAD 커밋이 이력 목록에 없어 메시지를 가져오지 못했습니다."); + return; + } + + CommitMessage = message.TrimEnd(); + } // ================= stash 목록 ================= @@ -965,6 +1002,7 @@ namespace MergeHub.ViewModels RegisterMergeToolCommand.NotifyCanExecuteChanged(); AmendCommand.NotifyCanExecuteChanged(); + OnPropertyChanged(nameof(AmendHint)); // 작업이 시작 · 끝나면 단추 설명도 바뀐다(11차 C-1) LoadLastMessageCommand.NotifyCanExecuteChanged(); StashApplySelectedCommand.NotifyCanExecuteChanged(); StashDropSelectedCommand.NotifyCanExecuteChanged(); diff --git a/src/MergeHub/Views/GitView.xaml b/src/MergeHub/Views/GitView.xaml index 01b0249..fcd17d3 100644 --- a/src/MergeHub/Views/GitView.xaml +++ b/src/MergeHub/Views/GitView.xaml @@ -391,8 +391,9 @@