6차 B-6: 충돌 탭에서 여러 파일을 골라 Accept 를 한 번에, 다시 읽기는 끝에 한 번
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
8dd9fa98a6
commit
eb43385e14
@ -654,3 +654,40 @@ MERGED 는 git 이 충돌 표시를 넣어 둔 파일이라, 두 칸으로는
|
|||||||
|
|
||||||
- 자동 풀기는 Git 탭 [Merge] 에만 걸었다. pull · rebase · cherry-pick 이 낸 충돌에는 아직 걸지 않았다.
|
- 자동 풀기는 Git 탭 [Merge] 에만 걸었다. pull · rebase · cherry-pick 이 낸 충돌에는 아직 걸지 않았다.
|
||||||
- MERGED 칸에서 반영하며 새로 넣은 주석 줄은, 다시 정렬하면(타이핑 멈춤 · 규칙 변경) "무시" 색이 된다. 차이 수에는 원래도 세지 않던 줄이다.
|
- 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.
|
||||||
|
|||||||
@ -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-5** — 머지 주석을 비교에서 무시(주석 줄은 거르고 줄 끝 주석은 떼고 견줌). 주석만 다른 git 충돌은 양쪽 주석을 남겨 자동으로 풂. 정렬이 주석 줄과 코드 줄을 짝지으면 떼어 놓음. 폴더 비교도. 설정 [머지 주석] 에서 끔 | 아래 |
|
||||||
| 2026-09-14 | **6차 B-4 확인 중 발견 두 건** — "마지막 차이" 로 멀리 건너뛰면 표시층 보정이 화면 밖 행으로 재져 네 칸이 9줄 위로 밀리던 것을 고침. `git mergetool` 이 넘긴 세 벌을 두 칸이 아닌 네 칸으로 열고 결과를 MERGED 파일에 쓴다 | 아래 |
|
| 2026-09-14 | **6차 B-4 확인 중 발견 두 건** — "마지막 차이" 로 멀리 건너뛰면 표시층 보정이 화면 밖 행으로 재져 네 칸이 9줄 위로 밀리던 것을 고침. `git mergetool` 이 넘긴 세 벌을 두 칸이 아닌 네 칸으로 열고 결과를 MERGED 파일에 쓴다 | 아래 |
|
||||||
| 2026-09-14 | **6차 B-4** — 3분할 MERGED 칸 줄 번호 자리가 20px 라 2949 가 "294" 로 잘리던 것을 고침. 네 칸 여백을 54 → 66px 로 맞춰 넓혀 다섯 자리까지 보인다 | 아래 |
|
| 2026-09-14 | **6차 B-4** — 3분할 MERGED 칸 줄 번호 자리가 20px 라 2949 가 "294" 로 잘리던 것을 고침. 네 칸 여백을 54 → 66px 로 맞춰 넓혀 다섯 자리까지 보인다 | 아래 |
|
||||||
|
|||||||
@ -109,7 +109,7 @@
|
|||||||
| **완료** | B-4 MERGED 칸 줄 번호가 네 자리에서 잘린다 — 네 칸 여백을 66px 로, MERGED 번호 자리 20 → 36px(다섯 자리) |
|
| **완료** | B-4 MERGED 칸 줄 번호가 네 자리에서 잘린다 — 네 칸 여백을 66px 로, MERGED 번호 자리 20 → 36px(다섯 자리) |
|
||||||
| **완료** | B-4 확인 중 발견 두 건 — 멀리 건너뛰면 네 칸이 위로 밀려 아래가 빔 · `git mergetool` 세 벌이 두 칸으로 열림 |
|
| **완료** | B-4 확인 중 발견 두 건 — 멀리 건너뛰면 네 칸이 위로 밀려 아래가 빔 · `git mergetool` 세 벌이 두 칸으로 열림 |
|
||||||
| **완료** | B-5 머지 주석은 소스에 넣고, 병합 뒤에는 그 줄을 무시한다 — 비교(두 칸 · 네 칸 · 폴더)에서 가리고, 주석만 다른 git 충돌은 양쪽 주석을 남겨 자동으로 푼다. 설정에서 끔 |
|
| **완료** | B-5 머지 주석은 소스에 넣고, 병합 뒤에는 그 줄을 무시한다 — 비교(두 칸 · 네 칸 · 폴더)에서 가리고, 주석만 다른 git 충돌은 양쪽 주석을 남겨 자동으로 푼다. 설정에서 끔 |
|
||||||
| 대기 | B-6 충돌 탭에서 하나씩 누를 때마다 오래 기다린다 |
|
| **완료** | B-6 충돌 탭에서 하나씩 누를 때마다 오래 기다린다 — 여러 개 골라 Accept 한 번, 다시 읽기는 끝에 한 번. 15개 22.0초 → 1.2초 |
|
||||||
| 대기 | B-7 상태바가 병합 중에 믿을 수 없는 값을 보인다 |
|
| 대기 | B-7 상태바가 병합 중에 믿을 수 없는 값을 보인다 |
|
||||||
| 대기 | B-8 병합 커밋 뒤 파일 비교 탭이 어정쩡하게 남는다 |
|
| 대기 | B-8 병합 커밋 뒤 파일 비교 탭이 어정쩡하게 남는다 |
|
||||||
|
|
||||||
|
|||||||
@ -276,7 +276,29 @@ namespace MergeHub.ViewModels
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
private bool HasConflict() => SelectedConflict is not null;
|
/// <summary>
|
||||||
|
/// 충돌 목록에서 고른 파일들(6차 B-6). Ctrl · Shift 로 여러 개를 고를 수 있다.
|
||||||
|
/// ListBox.SelectedItems 는 바인딩이 안 돼 화면이 고를 때마다 넘겨준다.
|
||||||
|
/// </summary>
|
||||||
|
private IReadOnlyList<ConflictInfo> _selectedConflicts = Array.Empty<ConflictInfo>();
|
||||||
|
|
||||||
|
public void SetSelectedConflicts(IReadOnlyList<ConflictInfo> conflicts)
|
||||||
|
{
|
||||||
|
_selectedConflicts = conflicts ?? Array.Empty<ConflictInfo>();
|
||||||
|
OnPropertyChanged(nameof(ConflictSelectionText));
|
||||||
|
RaiseCanExecuteChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Accept 가 적용될 파일들. 여러 개를 골랐으면 그것들, 아니면 고른 하나.</summary>
|
||||||
|
private IReadOnlyList<ConflictInfo> ConflictTargets =>
|
||||||
|
_selectedConflicts.Count > 0 ? _selectedConflicts
|
||||||
|
: SelectedConflict is { } one ? new[] { one } : Array.Empty<ConflictInfo>();
|
||||||
|
|
||||||
|
/// <summary>여러 개를 골랐을 때 단추 옆에 띄우는 한 줄.</summary>
|
||||||
|
public string ConflictSelectionText =>
|
||||||
|
ConflictTargets.Count > 1 ? $"{ConflictTargets.Count}개 고름 - Accept 는 고른 파일 모두에 한 번에 적용됩니다" : string.Empty;
|
||||||
|
|
||||||
|
private bool HasConflict() => ConflictTargets.Count > 0;
|
||||||
|
|
||||||
[RelayCommand(CanExecute = nameof(HasConflict))]
|
[RelayCommand(CanExecute = nameof(HasConflict))]
|
||||||
private Task AcceptMineAsync() => ResolveAsync(ConflictResolution.Mine);
|
private Task AcceptMineAsync() => ResolveAsync(ConflictResolution.Mine);
|
||||||
@ -289,7 +311,10 @@ namespace MergeHub.ViewModels
|
|||||||
{
|
{
|
||||||
// 파일 단위 "둘 다" 는 파일 두 벌을 통째로 이어 붙인다. 양쪽이 같은 이름으로 새로 만든 파일이면
|
// 파일 단위 "둘 다" 는 파일 두 벌을 통째로 이어 붙인다. 양쪽이 같은 이름으로 새로 만든 파일이면
|
||||||
// 같은 형식이 두 번 나와 빌드가 안 된다(5차 C-1 에서 그랬다). 누르기 전에 무엇이 일어나는지 알린다(6차 A-5).
|
// 같은 형식이 두 번 나와 빌드가 안 된다(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(
|
var go = MessageUtil.ShowYesNo(
|
||||||
"파일 두 벌을 이어 붙입니다",
|
"파일 두 벌을 이어 붙입니다",
|
||||||
$"{path}\n\n내 것 다음에 상대 것을 한 파일로 이어 붙입니다. 내 파일의 인코딩과 줄끝으로 씁니다.\n\n" +
|
$"{path}\n\n내 것 다음에 상대 것을 한 파일로 이어 붙입니다. 내 파일의 인코딩과 줄끝으로 씁니다.\n\n" +
|
||||||
@ -317,9 +342,50 @@ namespace MergeHub.ViewModels
|
|||||||
_workspace.RequestOpenConflict(conflict.Path);
|
_workspace.RequestOpenConflict(conflict.Path);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task ResolveAsync(ConflictResolution resolution) =>
|
/// <summary>
|
||||||
_workspace.RunAndRefreshAsync($"충돌 해결({resolution})",
|
/// 고른 충돌 파일들을 한 작업으로 푼다(6차 B-6).
|
||||||
() => _workspace.Git.ResolveConflict(SelectedConflict!.Path, resolution));
|
///
|
||||||
|
/// 예전에는 파일 하나마다 작업 하나였고, 작업이 끝날 때마다 저장소 전체를 다시 읽었다(한 개 약 1.5초).
|
||||||
|
/// 이제는 고른 파일을 모두 풀고 <b>다시 읽기는 끝에 한 번</b>이다.
|
||||||
|
/// 한 파일이 실패해도 나머지는 계속 풀고, 실패한 것은 로그와 알림으로 알린다.
|
||||||
|
/// </summary>
|
||||||
|
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<string>();
|
||||||
|
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}개는 풀지 못했습니다 - 로그를 보세요");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// ================= 이력 조작 =================
|
// ================= 이력 조작 =================
|
||||||
|
|
||||||
|
|||||||
@ -240,13 +240,15 @@
|
|||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<TextBlock Grid.Row="0" Style="{StaticResource Muted}" Margin="0,0,0,6" TextWrapping="Wrap"
|
<TextBlock Grid.Row="0" Style="{StaticResource Muted}" Margin="0,0,0,6" TextWrapping="Wrap"
|
||||||
Text="파일 전체를 한쪽으로 채택하려면 아래 버튼을 쓰고, 줄마다 고르려면 목록을 두 번 누르거나 [3분할로 열기] 를 누르세요. 파일 비교 탭에서 세 칸으로 열립니다."/>
|
Text="파일 전체를 한쪽으로 채택하려면 아래 버튼을 쓰고, 줄마다 고르려면 목록을 두 번 누르거나 [3분할로 열기] 를 누르세요. 파일 비교 탭에서 세 칸으로 열립니다. Ctrl · Shift 로 여러 파일을 고르면 Accept 가 한 번에 적용됩니다(Ctrl+A 전부)."/>
|
||||||
|
|
||||||
<!-- 두 번 누르거나 Enter 로 파일 비교 탭의 3분할에서 연다(6차 B-1).
|
<!-- 두 번 누르거나 Enter 로 파일 비교 탭의 3분할에서 연다(6차 B-1).
|
||||||
두 번 누르기는 MouseBinding 이 아니라 목록의 MouseDoubleClick 으로 받는다 -
|
두 번 누르기는 MouseBinding 이 아니라 목록의 MouseDoubleClick 으로 받는다 -
|
||||||
항목이 마우스 누름을 먼저 처리해 버려 MouseBinding 은 항목 위에서 안 온다. -->
|
항목이 마우스 누름을 먼저 처리해 버려 MouseBinding 은 항목 위에서 안 온다. -->
|
||||||
|
<!-- 여러 개를 골라 한 번에 Accept 한다(6차 B-6). SelectedItems 는 바인딩이 안 돼 SelectionChanged 로 넘긴다. -->
|
||||||
<ListBox Grid.Row="1" ItemsSource="{Binding Workspace.Conflicts}"
|
<ListBox Grid.Row="1" ItemsSource="{Binding Workspace.Conflicts}"
|
||||||
SelectedItem="{Binding SelectedConflict}" HorizontalContentAlignment="Stretch"
|
SelectedItem="{Binding SelectedConflict}" HorizontalContentAlignment="Stretch"
|
||||||
|
SelectionMode="Extended" SelectionChanged="Conflicts_SelectionChanged"
|
||||||
MouseDoubleClick="Conflicts_MouseDoubleClick"
|
MouseDoubleClick="Conflicts_MouseDoubleClick"
|
||||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
|
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
|
||||||
<ListBox.InputBindings>
|
<ListBox.InputBindings>
|
||||||
@ -291,6 +293,8 @@
|
|||||||
Background="{DynamicResource Ubisam.Brush.Warning}"
|
Background="{DynamicResource Ubisam.Brush.Warning}"
|
||||||
Foreground="{DynamicResource Ubisam.Brush.Accent.OnFill}"/>
|
Foreground="{DynamicResource Ubisam.Brush.Accent.OnFill}"/>
|
||||||
<Button Content="병합 커밋" Command="{Binding CommitMergeCommand}" MinWidth="100"/>
|
<Button Content="병합 커밋" Command="{Binding CommitMergeCommand}" MinWidth="100"/>
|
||||||
|
<TextBlock Text="{Binding ConflictSelectionText}" Style="{StaticResource Muted}"
|
||||||
|
VerticalAlignment="Center" Margin="8,0,0,0"/>
|
||||||
</WrapPanel>
|
</WrapPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using System.Windows;
|
using System.Linq;
|
||||||
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using MergeHub.ViewModels;
|
using MergeHub.ViewModels;
|
||||||
|
|
||||||
@ -44,6 +45,13 @@ namespace MergeHub.Views
|
|||||||
if (DataContext is MergeHub.ViewModels.GitViewModel vm && vm.OpenConflictInCompareCommand.CanExecute(null))
|
if (DataContext is MergeHub.ViewModels.GitViewModel vm && vm.OpenConflictInCompareCommand.CanExecute(null))
|
||||||
vm.OpenConflictInCompareCommand.Execute(null);
|
vm.OpenConflictInCompareCommand.Execute(null);
|
||||||
}
|
}
|
||||||
|
/// <summary>고른 충돌 파일들을 넘긴다(6차 B-6). ListBox.SelectedItems 는 바인딩할 수 없다.</summary>
|
||||||
|
private void Conflicts_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is not ListBox list || ViewModel is not { } vm) return;
|
||||||
|
vm.SetSelectedConflicts(list.SelectedItems.OfType<MergeHub.Models.ConflictInfo>().ToList());
|
||||||
|
}
|
||||||
|
|
||||||
private void Password_Changed(object sender, RoutedEventArgs e)
|
private void Password_Changed(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (sender is PasswordBox box && ViewModel is { } vm) vm.Workspace.Password = box.Password;
|
if (sender is PasswordBox box && ViewModel is { } vm) vm.Workspace.Password = box.Password;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user