diff --git a/doc/history/2026-09-14-6차.md b/doc/history/2026-09-14-6차.md index e38a7f4..384179f 100644 --- a/doc/history/2026-09-14-6차.md +++ b/doc/history/2026-09-14-6차.md @@ -654,3 +654,40 @@ MERGED 는 git 이 충돌 표시를 넣어 둔 파일이라, 두 칸으로는 - 자동 풀기는 Git 탭 [Merge] 에만 걸었다. pull · rebase · cherry-pick 이 낸 충돌에는 아직 걸지 않았다. - MERGED 칸에서 반영하며 새로 넣은 주석 줄은, 다시 정렬하면(타이핑 멈춤 · 규칙 변경) "무시" 색이 된다. 차이 수에는 원래도 세지 않던 줄이다. + +## B-6. 충돌 탭에서 하나씩 누를 때마다 오래 기다린다 (2026-09-14, 완료) + +### 고치기 전에 다시 났다 + +새로 만든 저장소를 병합(충돌 24)하고, 같은 줄 충돌 15개(`Vision/Unit10.cs` ~ `Motion/Unit24.cs`)를 충돌 탭에서 **하나씩 골라 Accept Mine**. + +| 본 것 | 값 | +|---|---| +| 15개 합계 | **22.0초** | +| 한 개 평균 | **1.47초** (자동 조작의 고르기 0.3초 포함) | +| 여러 개 고르기 | 안 됨 - 목록이 한 개만 고른다 | + +### 무엇이 원인이었나 + +- 목록이 `SelectionMode` 기본값(한 개)이었다. +- Accept 한 번이 작업 하나(`RunAndRefreshAsync`)라, 끝날 때마다 저장소 전체를 다시 읽었다. + +### 고친 것 + +| 파일 | 무엇 | +|---|---| +| `Views/GitView.xaml` | 충돌 목록 `SelectionMode="Extended"`(Ctrl · Shift · Ctrl+A). 단추 옆에 "N개 고름 - Accept 는 고른 파일 모두에 한 번에 적용됩니다". 안내 글에 여러 개 고르기 | +| `Views/GitView.xaml.cs` | `SelectionChanged` 에서 고른 목록을 넘긴다(SelectedItems 는 바인딩이 안 된다) | +| `ViewModels/GitViewModel.cs` | `SetSelectedConflicts` · `ConflictTargets`. Accept Mine/Theirs/Both 가 고른 파일 모두를 **작업 하나**로 풀고 다시 읽기는 끝에 한 번. 한 파일이 실패해도 나머지는 풀고, 실패는 로그 · 알림. 로그 "충돌 N개를 Mine 로 한 번에 풀었습니다 (… ms)". Accept Both 확인 창은 "N개 파일 (첫 파일 외 N-1개)" | + +### 확인한 것 (같은 저장소 · 같은 15개) + +| 확인 | 결과 | +|---|---| +| 15개를 골라 Accept Mine 한 번 | "15개 고름" 표시. git 반영 **0.8초**, 목록이 9개로 다시 설 때까지 **1.2초** (고치기 전 22.0초) | +| 로그 | "충돌 15개를 Mine 로 한 번에 풀었습니다 (224ms · 저장소 다시 읽기는 끝에 한 번)" | +| 결과 | 충돌 24 → 9. 15개 모두 내 쪽(HEAD) 글과 같음 | + +(자동 조작은 목록이 화면에 보이는 항목만 만들어 두는 탓에 처음에는 10개만 골랐다. 목록 항목을 모두 불러오게 고쳐 15개로 다시 쟀다.) + +빌드 경고 0 · 오류 0. diff --git a/doc/history/changelog.md b/doc/history/changelog.md index ee8381e..1ae29ea 100644 --- a/doc/history/changelog.md +++ b/doc/history/changelog.md @@ -8,6 +8,7 @@ | 날짜 | 항목 | 커밋 | |---|---|---| +| 2026-09-14 | **6차 B-6** — 충돌 탭에서 여러 파일을 골라(Ctrl · Shift) Accept 를 한 번에. 작업 하나로 풀고 저장소 다시 읽기는 끝에 한 번. 같은 줄 충돌 15개 22.0초 → 1.2초 | 아래 | | 2026-09-14 | **6차 B-5** — 머지 주석을 비교에서 무시(주석 줄은 거르고 줄 끝 주석은 떼고 견줌). 주석만 다른 git 충돌은 양쪽 주석을 남겨 자동으로 풂. 정렬이 주석 줄과 코드 줄을 짝지으면 떼어 놓음. 폴더 비교도. 설정 [머지 주석] 에서 끔 | 아래 | | 2026-09-14 | **6차 B-4 확인 중 발견 두 건** — "마지막 차이" 로 멀리 건너뛰면 표시층 보정이 화면 밖 행으로 재져 네 칸이 9줄 위로 밀리던 것을 고침. `git mergetool` 이 넘긴 세 벌을 두 칸이 아닌 네 칸으로 열고 결과를 MERGED 파일에 쓴다 | 아래 | | 2026-09-14 | **6차 B-4** — 3분할 MERGED 칸 줄 번호 자리가 20px 라 2949 가 "294" 로 잘리던 것을 고침. 네 칸 여백을 54 → 66px 로 맞춰 넓혀 다섯 자리까지 보인다 | 아래 | diff --git a/doc/진행상황.md b/doc/진행상황.md index 8992d68..ce10bae 100644 --- a/doc/진행상황.md +++ b/doc/진행상황.md @@ -109,7 +109,7 @@ | **완료** | B-4 MERGED 칸 줄 번호가 네 자리에서 잘린다 — 네 칸 여백을 66px 로, MERGED 번호 자리 20 → 36px(다섯 자리) | | **완료** | B-4 확인 중 발견 두 건 — 멀리 건너뛰면 네 칸이 위로 밀려 아래가 빔 · `git mergetool` 세 벌이 두 칸으로 열림 | | **완료** | B-5 머지 주석은 소스에 넣고, 병합 뒤에는 그 줄을 무시한다 — 비교(두 칸 · 네 칸 · 폴더)에서 가리고, 주석만 다른 git 충돌은 양쪽 주석을 남겨 자동으로 푼다. 설정에서 끔 | -| 대기 | B-6 충돌 탭에서 하나씩 누를 때마다 오래 기다린다 | +| **완료** | B-6 충돌 탭에서 하나씩 누를 때마다 오래 기다린다 — 여러 개 골라 Accept 한 번, 다시 읽기는 끝에 한 번. 15개 22.0초 → 1.2초 | | 대기 | B-7 상태바가 병합 중에 믿을 수 없는 값을 보인다 | | 대기 | B-8 병합 커밋 뒤 파일 비교 탭이 어정쩡하게 남는다 | diff --git a/src/MergeHub/ViewModels/GitViewModel.cs b/src/MergeHub/ViewModels/GitViewModel.cs index 90c6fb7..438e3d0 100644 --- a/src/MergeHub/ViewModels/GitViewModel.cs +++ b/src/MergeHub/ViewModels/GitViewModel.cs @@ -276,7 +276,29 @@ namespace MergeHub.ViewModels }); }); - private bool HasConflict() => SelectedConflict is not null; + /// + /// 충돌 목록에서 고른 파일들(6차 B-6). Ctrl · Shift 로 여러 개를 고를 수 있다. + /// ListBox.SelectedItems 는 바인딩이 안 돼 화면이 고를 때마다 넘겨준다. + /// + private IReadOnlyList _selectedConflicts = Array.Empty(); + + public void SetSelectedConflicts(IReadOnlyList conflicts) + { + _selectedConflicts = conflicts ?? Array.Empty(); + OnPropertyChanged(nameof(ConflictSelectionText)); + RaiseCanExecuteChanged(); + } + + /// Accept 가 적용될 파일들. 여러 개를 골랐으면 그것들, 아니면 고른 하나. + private IReadOnlyList ConflictTargets => + _selectedConflicts.Count > 0 ? _selectedConflicts + : SelectedConflict is { } one ? new[] { one } : Array.Empty(); + + /// 여러 개를 골랐을 때 단추 옆에 띄우는 한 줄. + public string ConflictSelectionText => + ConflictTargets.Count > 1 ? $"{ConflictTargets.Count}개 고름 - Accept 는 고른 파일 모두에 한 번에 적용됩니다" : string.Empty; + + private bool HasConflict() => ConflictTargets.Count > 0; [RelayCommand(CanExecute = nameof(HasConflict))] private Task AcceptMineAsync() => ResolveAsync(ConflictResolution.Mine); @@ -289,7 +311,10 @@ namespace MergeHub.ViewModels { // 파일 단위 "둘 다" 는 파일 두 벌을 통째로 이어 붙인다. 양쪽이 같은 이름으로 새로 만든 파일이면 // 같은 형식이 두 번 나와 빌드가 안 된다(5차 C-1 에서 그랬다). 누르기 전에 무엇이 일어나는지 알린다(6차 A-5). - var path = SelectedConflict?.Path ?? string.Empty; + var targets = ConflictTargets; + var path = targets.Count <= 1 + ? targets.FirstOrDefault()?.Path ?? string.Empty + : $"{targets.Count}개 파일 ({targets[0].Path} 외 {targets.Count - 1}개)"; var go = MessageUtil.ShowYesNo( "파일 두 벌을 이어 붙입니다", $"{path}\n\n내 것 다음에 상대 것을 한 파일로 이어 붙입니다. 내 파일의 인코딩과 줄끝으로 씁니다.\n\n" + @@ -317,9 +342,50 @@ namespace MergeHub.ViewModels _workspace.RequestOpenConflict(conflict.Path); } - private Task ResolveAsync(ConflictResolution resolution) => - _workspace.RunAndRefreshAsync($"충돌 해결({resolution})", - () => _workspace.Git.ResolveConflict(SelectedConflict!.Path, resolution)); + /// + /// 고른 충돌 파일들을 한 작업으로 푼다(6차 B-6). + /// + /// 예전에는 파일 하나마다 작업 하나였고, 작업이 끝날 때마다 저장소 전체를 다시 읽었다(한 개 약 1.5초). + /// 이제는 고른 파일을 모두 풀고 다시 읽기는 끝에 한 번이다. + /// 한 파일이 실패해도 나머지는 계속 풀고, 실패한 것은 로그와 알림으로 알린다. + /// + private Task ResolveAsync(ConflictResolution resolution) + { + var targets = ConflictTargets.ToList(); + if (targets.Count == 0) return Task.CompletedTask; + + var label = targets.Count == 1 ? $"충돌 해결({resolution})" : $"충돌 해결({resolution}) {targets.Count}개"; + + return _workspace.RunAndRefreshAsync(label, () => + { + var failed = new List(); + var watch = Stopwatch.StartNew(); + + foreach (var conflict in targets) + { + try + { + _workspace.Git.ResolveConflict(conflict.Path, resolution); + } + catch (Exception ex) when (ex is not OperationCanceledException) + { + failed.Add($"{conflict.Path}: {ex.Message}"); + } + } + + watch.Stop(); + + _workspace.OnUi(() => + { + if (targets.Count > 1) + _workspace.AppendLog($"충돌 {targets.Count - failed.Count}개를 {resolution} 로 한 번에 풀었습니다 " + + $"({watch.ElapsedMilliseconds}ms · 저장소 다시 읽기는 끝에 한 번)"); + + foreach (var line in failed) _workspace.AppendLog("충돌 해결 실패: " + line); + if (failed.Count > 0) MessageUtil.ShowWarningToast($"{failed.Count}개는 풀지 못했습니다 - 로그를 보세요"); + }); + }); + } // ================= 이력 조작 ================= diff --git a/src/MergeHub/Views/GitView.xaml b/src/MergeHub/Views/GitView.xaml index 998d6ed..431659a 100644 --- a/src/MergeHub/Views/GitView.xaml +++ b/src/MergeHub/Views/GitView.xaml @@ -240,13 +240,15 @@ + Text="파일 전체를 한쪽으로 채택하려면 아래 버튼을 쓰고, 줄마다 고르려면 목록을 두 번 누르거나 [3분할로 열기] 를 누르세요. 파일 비교 탭에서 세 칸으로 열립니다. Ctrl · Shift 로 여러 파일을 고르면 Accept 가 한 번에 적용됩니다(Ctrl+A 전부)."/> + @@ -291,6 +293,8 @@ Background="{DynamicResource Ubisam.Brush.Warning}" Foreground="{DynamicResource Ubisam.Brush.Accent.OnFill}"/>