9차 A-2: HEAD 를 보고 있었으면 커밋 뒤 새 HEAD 를 따라간다

8차 E-1 에서 한 번 다른 브랜치 커밋이 기준이 된 것은 A-1 과 뿌리가 같았다
(새로고침에서 선택을 놓치면 목록 맨 위로 채움). A-1 대로 해시만 지키자
cherry-pick [커밋] 뒤 선택이 옛 HEAD 에 남아 방금 커밋한 줄이 "남은 차이 6줄" 로 보였다.

- CommitSelection.Choose(previousHeadSha): HEAD 를 보다가 HEAD 가 움직이면 새 HEAD
- GitWorkspace: 지난 새로고침의 HEAD 를 들고 있다
- 하네스 MergeSaveTest +5 (206건), 22벌 1,117건 통과, 빌드 경고 0 오류 0

확인: 8차 E-1 cherry-pick 순서([모든 브랜치] 켬)를 여섯 번 - 시작 · 파일 단위 = 고른 커밋,
마지막 Save 뒤 = HEAD, [커밋] 뒤 = 새 HEAD · 차이 없음. 다른 브랜치 커밋이 잡힌 회차 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
김승준 2026-09-17 11:31:30 +09:00
parent 9fffc40dbe
commit 5710e522bb
5 changed files with 58 additions and 3 deletions

View File

@ -68,3 +68,40 @@
이번에 바꾼 것이 아니라 전부터 그랬다. 고치지 않고 적어 둔다.
- A-2(한 번은 다른 브랜치 커밋이 기준)는 이번에 원인(목록 맨 위로 채움)이 드러나 함께 고쳐졌다. 지시서대로 A-2 에서 같은 순서를 다섯 번 넘게 되풀이해 확인한다.
- 자동 조작 쪽: 첫 확인 스크립트는 Git 탭에서 HEAD 를 골라 파일 비교 탭을 봤는데 위의 까닭으로 안 바뀌어, 파일 비교 탭 이력에서 고르게 고쳤다.
## A-2. 한 번은 기준이 HEAD 도 가져온 커밋도 아닌 다른 브랜치 커밋이었다 (2026-09-17, 완료)
### 원인 (A-1 에서 드러남)
- 8차 E-1 N1-덧의 `f310f49`(다른 브랜치의 `뒤이은 수정`)는 **목록 맨 위**였다. 새로고침이 이력 목록을 갈아 끼울 때 Git 탭 목록이 선택을 놓치면
고른 커밋이 비고, 비면 `Commits[0]` 으로 채웠다. [모든 브랜치] 를 켜면 맨 위는 가장 최근의 다른 브랜치 커밋이다.
- 놓치는지는 그때 화면 상태에 따라 달라 "같은 순서로 다시 재니 안 났다". 9차 A-1 의 고치기 전 확인에서 **다시 났다**(기준 `ace42e3 "뒤이은 수정"`).
- A-1 에서 해시로 되찾고 없으면 HEAD 로 채우게 고쳤다(`CommitSelection`).
### 고치며 새로 걸린 것 → 함께 고침
- A-1 대로 해시만 지키자 **cherry-pick [커밋] 뒤에도 선택이 옛 HEAD(`내 쪽 작업`)에 남아** 파일 비교가 방금 커밋한 줄을 **"남은 차이 6줄"** 이라고 했다
(되풀이 확인 첫 회차에서 남). 예전에는 선택을 놓치면 맨 위(새 커밋)로 가서 우연히 "차이 없음" 이었다.
- `CommitSelection.Choose(…, previousHeadSha)` — **HEAD 를 보고 있다가 HEAD 가 움직이면**(커밋 · 체크아웃) 새 HEAD 를 따라간다.
다른 커밋을 보고 있었으면 그대로 둔다. `GitWorkspace` 가 지난 새로고침의 HEAD(`_lastHeadSha`)를 들고 있다.
- 하네스 `MergeSaveTest` 5건 추가(206건): HEAD 를 보다 커밋하면 새 HEAD · 다른 커밋을 보고 있었으면 그대로 · HEAD 가 안 움직이면 그대로 · 처음 열기 · 새 HEAD 가 목록에 없음.
### 확인한 것 — 8차 E-1 순서를 여섯 번 (`d9-a2.ps1`)
회차마다 저장소를 새로 만들고, 파일 비교 탭을 미리 열지 않고, [모든 브랜치] 를 켜고 이력에서 `상대 쪽 작업` → [Cherry-pick] →
`Weld/Unit1.cs` 만 남기고 파일 단위 Accept Theirs → [3분할로 열기] · Take Theirs · [Save] → 충돌 탭 [커밋]. 새로고침이 일어나는 곳마다 Git 탭 이력의 선택 줄과 파일 비교 탭 Selected Commit 을 적었다.
| 지점 | 기대(규칙) | 6회 결과 |
|---|---|---|
| [Cherry-pick] 뒤(충돌 24) | 고른 커밋 `상대 쪽 작업` | 6/6 |
| 파일 단위 Accept 뒤(충돌 1) | 고른 커밋 | 6/6 |
| 마지막 [Save] 뒤(충돌 0) | HEAD `내 쪽 작업` · 제목 "가져오기 전 내 쪽" · 요약 "내 쪽 대비 가져온 변경 6줄" | 6/6 (Git 탭 · 파일 비교 탭 둘 다) |
| [커밋] 뒤 | 새 HEAD(cherry-pick 커밋) · "차이 없음" · Working Tree Clean | 6/6 (첫 회차는 고친 뒤 되풀이 스크립트의 1회) |
- 다른 브랜치 커밋(`뒤이은 수정`)이 기준으로 잡힌 회차 0.
- 하네스 22벌 1,117건 통과(+5) · 빌드 경고 0 · 오류 0.
### 남긴 것
- 자동 조작 쪽: 되풀이 스크립트가 처음에 한 회차만 돌았다. PowerShell 변수 이름은 대소문자를 가리지 않아 안쪽 반복 횟수 `$rounds` 가 회차 수 `$Rounds` 를 덮었다. 이름을 바꿔 다시 돌렸다.
- Git 탭에서 고른 커밋을 파일 비교 탭이 새로고침 전까지 안 따라오는 것(A-1 에 적음)은 그대로다.

View File

@ -8,6 +8,7 @@
| 날짜 | 항목 | 커밋 |
|---|---|---|
| 2026-09-17 | **9차 A-2** — 8차 E-1 에서 한 번 다른 브랜치 커밋이 기준으로 잡힌 것은 A-1 과 뿌리가 같았다(선택을 놓치면 목록 맨 위). 해시만 지키자 cherry-pick [커밋] 뒤 선택이 옛 HEAD 에 남아 방금 커밋한 줄이 "남은 차이 6줄" 로 보여, **HEAD 를 보고 있었으면 커밋 · 체크아웃 뒤 새 HEAD 를 따라가게** 함께 고침. 같은 순서 여섯 번 모두 규칙대로. 하네스 +5 | 아래 |
| 2026-09-17 | **9차 A-1** — cherry-pick 마지막 [Save] 뒤 두 칸의 기준이 고른 커밋 · 다른 브랜치 커밋 · HEAD 중 하나로 그때그때 달랐는데 요약은 늘 "내 쪽 대비" 라고 했다. 새로고침이 이력 목록을 갈아 끼우며 선택을 놓치면 **목록 맨 위**로 채웠기 때문([모든 브랜치] 에서 맨 위는 HEAD 가 아님). 사용자 결정대로 작업 중 충돌이 막 0 이 되면 기준을 HEAD 로 옮기고, 그 밖에는 해시로 되찾고 없으면 HEAD. 요약은 기준이 HEAD 일 때만 작업 글. 하네스 +18 | 아래 |
| 2026-09-17 | **9차 지시서** — 8차 전부 끝. E-1 에서 새로 걸린 N1(A-1 cherry-pick 마지막 저장 뒤 요약은 "내 쪽 대비" 인데 두 칸의 기준은 이력에서 고른 커밋 — 먼저 정함 · A-2 한 번은 다른 브랜치 커밋이 기준 — 새로고침 뒤 선택 복원, 원인부터)과 전에 남긴 것(B-1 MERGED 주석 줄 "무시" 색 · B-2 폴더 트리 1초) · C-1 웹 문서(F-1 · 숫자) · D-1 cherry-pick · revert 를 마지막 파일까지 네 칸으로. 코드 변경 없음 | 아래 |
| 2026-09-17 | **8차 E-1** — 다섯 길(병합 · pull · rebase · cherry-pick · revert)을 새 저장소에서 **파일 비교 탭을 미리 열지 않고** MergeHub 만으로 끝까지(제품 코드 변경 없음). 다섯 길 모두 커밋(rebase 끝)까지 · 줄끝 · BOM 바뀐 파일 0 · 충돌 표식 0 · class 이름 검사 0. 7차 A-1 · A-2 · B-1, 8차 A-1 · B-1 · B-2 다시 안 남, F-1 병합 · pull 본문 26개. 새로 걸린 것: N1 cherry-pick 요약 "내 쪽 대비 가져온 변경" 이 실제 기준(이력에서 고른 커밋)과 다름 · 한 번 다른 브랜치 커밋이 기준으로 잡힘(재현 안 됨) → 9차. 8차 전부 끝 | 아래 |

View File

@ -251,7 +251,7 @@
| 상태 | 항목 |
|---|---|
| **완료** | A-1 cherry-pick 마지막 저장 뒤 "내 쪽 대비" 라고 하지만 실제 기준은 이력에서 고른 커밋이다 — 사용자가 "HEAD 로 옮긴다" 를 고름(2026-09-17). 새로고침이 고른 커밋을 놓치면 목록 맨 위(모든 브랜치에서는 다른 브랜치 커밋)로 채우던 것을 해시로 되찾고 없으면 HEAD, 작업 중 충돌이 막 0 이 되면 HEAD 로. 요약은 기준이 HEAD 일 때만 작업 글. cherry-pick · revert 마지막 Save 뒤 제목 · 요약 · 기준이 같음(6줄 = git 대조) |
| 대기 | A-2 한 번은 기준이 다른 브랜치 커밋이었다 — 새로고침 뒤 이력 선택 복원 (원인부터) |
| **완료** | A-2 한 번은 기준이 다른 브랜치 커밋이었다 — 원인은 A-1 과 같음(선택을 놓치면 목록 맨 위로 채움). 해시만 지키자 cherry-pick [커밋] 뒤 옛 HEAD 에 남아 "남은 차이 6줄" 이 떠서, HEAD 를 보고 있었으면 새 HEAD 를 따라가게 함께 고침. 8차 E-1 순서 여섯 번 모두 시작 · 파일 단위 = 고른 커밋, Save 뒤 = HEAD, 커밋 뒤 = 새 HEAD |
### B. 전에 남겨 둔 작은 것

View File

@ -14,6 +14,7 @@ namespace MergeHub.Services
/// 정한 것(2026-09-17 사용자) - 작업(병합 · rebase · cherry-pick · revert) 중 충돌이 0 이 되는 순간 기준을 HEAD 로 옮긴다.
/// 병합 · rebase 와 같아지고 "내 쪽 대비 가져온 변경 N줄" 이 사실이 된다(커밋하면 무엇이 바뀌는지).
/// 그 밖에는 사람이 고른 커밋을 <b>해시로</b> 되찾고, 없으면 맨 위가 아니라 HEAD 로 채운다.
/// 다만 HEAD 를 보고 있다가 HEAD 가 움직이면(커밋 · 체크아웃) 새 HEAD 를 따라간다(9차 A-2).
/// </summary>
public static class CommitSelection
{
@ -21,13 +22,21 @@ namespace MergeHub.Services
/// <param name="previousSha">새로고침 전에 고른 커밋. 없으면 null.</param>
/// <param name="headSha">지금 HEAD. 목록에 없을 수 있다(이력 쪽수 밖).</param>
/// <param name="enteredResolvedOperation">이번 새로고침에서 "작업 중 · 충돌 0" 이 막 된 것인지(<see cref="EntersResolvedOperation"/>).</param>
public static int Choose(IReadOnlyList<string> shas, string? previousSha, string? headSha, bool enteredResolvedOperation)
/// <param name="previousHeadSha">
/// 지난 새로고침 때의 HEAD. <b>HEAD 를 보고 있다가 HEAD 가 움직이면</b>(커밋 · 체크아웃) 새 HEAD 를 따라간다(9차 A-2).
/// 해시만 지키면 cherry-pick [커밋] 뒤에도 옛 HEAD 에 남아, 파일 비교가 방금 커밋한 줄을 "남은 차이 6줄" 이라고 했다.
/// </param>
public static int Choose(IReadOnlyList<string> shas, string? previousSha, string? headSha, bool enteredResolvedOperation,
string? previousHeadSha = null)
{
if (shas is null || shas.Count == 0) return -1;
var head = IndexOf(shas, headSha);
if (enteredResolvedOperation && head >= 0) return head;
var wasLookingAtHead = SameSha(previousSha, previousHeadSha);
if (wasLookingAtHead && head >= 0 && !SameSha(previousHeadSha, headSha)) return head;
var kept = IndexOf(shas, previousSha);
if (kept >= 0) return kept;
@ -44,6 +53,9 @@ namespace MergeHub.Services
/// <summary>작업이 진행 중이고 남은 충돌이 없는가.</summary>
public static bool IsResolvedOperation(bool inProgress, int conflictCount) => inProgress && conflictCount == 0;
private static bool SameSha(string? a, string? b) =>
!string.IsNullOrEmpty(a) && string.Equals(a, b, StringComparison.OrdinalIgnoreCase);
private static int IndexOf(IReadOnlyList<string> shas, string? sha)
{
if (string.IsNullOrEmpty(sha)) return -1;

View File

@ -224,6 +224,9 @@ namespace MergeHub.Services
/// <summary>지난 새로고침 때 "작업 중 · 충돌 0" 이었는지. 그 상태가 막 된 순간에만 선택을 HEAD 로 옮긴다(9차 A-1).</summary>
private bool _wasResolvedOperation;
/// <summary>지난 새로고침 때의 HEAD(전체 해시). HEAD 를 보고 있다가 HEAD 가 움직이면 새 HEAD 를 따라가려고 둔다(9차 A-2).</summary>
private string? _lastHeadSha;
[ObservableProperty] private BranchInfo? _selectedBranch;
[ObservableProperty] private string _log = string.Empty;
@ -427,7 +430,9 @@ namespace MergeHub.Services
// 두 칸의 기준이 다른 브랜치 커밋이 되기도 했다. 해시로 되찾고, 없으면 HEAD. 작업 중 충돌이 막 0 이 되면 HEAD 로(9차 A-1).
var entered = CommitSelection.EntersResolvedOperation(_wasResolvedOperation, operation.IsInProgress, conflicts.Count);
_wasResolvedOperation = resolvedOperation;
var pick = CommitSelection.Choose(commits.Select(c => c.Sha).ToList(), previousSha, info.HeadSha, entered);
// HEAD 를 보고 있었으면 커밋 · 체크아웃 뒤 새 HEAD 를 따라간다 - 옛 HEAD 에 남으면 방금 커밋한 줄이 "남은 차이" 로 보였다(9차 A-2).
var pick = CommitSelection.Choose(commits.Select(c => c.Sha).ToList(), previousSha, info.HeadSha, entered, _lastHeadSha);
_lastHeadSha = info.HeadSha;
SelectedCommit = pick < 0 ? null : Commits[pick];
});
}