3-Way 를 "끝낼 수 있게" 만들기 (자동 병합 · 충돌만 이동 · 완료 판정)
지금까지는 안 겹치는 변경까지 사람이 하나씩 눌러야 해서, 정작 판단이 필요한 서너 줄이 그 속에 묻혔다. git merge 가 자동으로 해 주는 일을 여기서도 해 준다. - 자동 병합: 저쪽만 고친 줄(ThreeWayState.RemoteChanged)을 한 번에 가져온다. 충돌과 내가 고친 줄은 건드리지 않는다. 이미 가져온 줄은 더 이상 RemoteChanged 가 아니므로 여러 번 눌러도 안전하다. - 충돌만 이동: Alt+Shift+↑↓. "다음 차이" 와 달리 아직 안 정한 충돌만 딛는다. - 완료 판정: ACTIONS 에 "남은 충돌 N개"(빨강) → "충돌 정리 끝"(초록). 판단이 하나 필요했다 - 충돌 여부는 BASE·MINE·REMOTE 를 견주면 저절로 나오지만, "내 것으로 간다"고 정한 줄은 내용이 그대로라 여전히 충돌로 보인다. 그래서 MergeLine.NeedsDecision(충돌이면서 반영·수정 표시가 없는 줄)을 따로 두었다. 그 결정은 글을 바꾸지 않아 편집 뒤 재정렬하면 사라지므로, 얼어 있는 BASE 줄 번호 (없으면 MINE 줄 번호)로 붙들어 되살린다. 곁들여 고친 것: Take Mine / Take Theirs 버튼이 줄을 옮겨도 켜짐·꺼짐이 그대로였다. CommunityToolkit 커맨드는 CommandManager 를 구독하지 않는데 이 둘만 갱신 목록에서 빠져 있었다 - 키보드(Alt+←/→)로는 되고 버튼만 안 되던 이유다. 실행 확인(mergehub-demo, 3-Way = feature/cooling): 자동 병합으로 Theirs only 1→0, 다음 충돌+Take Theirs 를 반복해 남은 충돌 3→2→1→"충돌 정리 끝". 테스트 290 → 293건 전부 통과(단축키 3건 추가). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
9956567bf8
commit
85c5e9e085
26
doc/진행상황.md
26
doc/진행상황.md
@ -24,13 +24,24 @@
|
|||||||
| **완료** | blame (줄별 작성자) | `Blame` 탭 신설. 변경 목록에서 파일을 고르고 `blame` 을 누르면 경로가 넘어간다 | 확인: 31줄 전부 `커밋·날짜·작성자` 가 붙어 나옴 |
|
| **완료** | blame (줄별 작성자) | `Blame` 탭 신설. 변경 목록에서 파일을 고르고 `blame` 을 누르면 경로가 넘어간다 | 확인: 31줄 전부 `커밋·날짜·작성자` 가 붙어 나옴 |
|
||||||
| **완료** | 변경분 파일 추출 | `GitService.ExportChangedFiles` 신설. 저장소 안 경로 구조를 그대로 만들고, <b>바이트 그대로</b> 옮긴다(텍스트로 다시 쓰면 줄바꿈·인코딩이 바뀐다). 지워진 파일은 뽑을 것이 없어 건너뛴다 | 확인: `2개 파일 → ...Temp (건너뜀 0개)` |
|
| **완료** | 변경분 파일 추출 | `GitService.ExportChangedFiles` 신설. 저장소 안 경로 구조를 그대로 만들고, <b>바이트 그대로</b> 옮긴다(텍스트로 다시 쓰면 줄바꿈·인코딩이 바뀐다). 지워진 파일은 뽑을 것이 없어 건너뛴다 | 확인: `2개 파일 → ...Temp (건너뜀 0개)` |
|
||||||
|
|
||||||
### ③ 3-Way 병합 — "볼 수 있다"에서 "끝낼 수 있다"로
|
### ③ 3-Way 병합 — **완료** (2026-09-08)
|
||||||
|
|
||||||
| 상태 | 항목 | 내용 |
|
"볼 수 있다"에서 "끝낼 수 있다"로.
|
||||||
|---|---|---|
|
|
||||||
| 대기 | 자동 병합 | 한쪽만 고친 곳은 자동으로 결과에 넣는다. 사람은 충돌만 판단 |
|
| 상태 | 항목 | 내용 | 확인 |
|
||||||
| 대기 | 충돌만 이동 | `다음 차이` 말고 `다음 충돌`. 자동 병합과 짝이다 |
|
|---|---|---|---|
|
||||||
| 대기 | 병합 완료 판정 | 남은 충돌 0 이면 저장·커밋을 유도 |
|
| **완료** | 자동 병합 | 저쪽만 고친 줄(`ThreeWayState.RemoteChanged`)을 한 번에 가져온다. 충돌과 내가 고친 줄은 안 건드린다. `git merge` 가 자동으로 해 주는 일을 여기서도 해 주는 것 | `Theirs only 1 → 0`, 자동 병합 버튼이 스스로 꺼짐 |
|
||||||
|
| **완료** | 충돌만 이동 | `이전 충돌` / `다음 충돌` (`Alt+Shift+↑↓`). <b>아직 안 정한 충돌만</b> 딛는다 | 3개를 차례로 딛고 마지막에 버튼이 꺼짐 |
|
||||||
|
| **완료** | 병합 완료 판정 | ACTIONS 위쪽에 `남은 충돌 N개`(빨강) → `충돌 정리 끝`(초록) | 3개 → 2 → 1 → 끝 |
|
||||||
|
|
||||||
|
**여기서 판단이 하나 필요했다** — 충돌인지 아닌지는 BASE·MINE·REMOTE 를 견주면 저절로 나오지만,
|
||||||
|
"내 것으로 간다"고 정한 줄은 <b>내용이 그대로라 여전히 충돌로 보인다.</b> 그래서 `NeedsDecision`
|
||||||
|
(충돌이면서 아직 반영·수정 표시가 없는 줄)을 따로 두었다. 그 결정은 글을 바꾸지 않으므로 편집 뒤
|
||||||
|
다시 정렬하면 사라지는데, 얼어 있는 <b>BASE 줄 번호</b>(없으면 MINE 줄 번호)로 붙들어 되살린다.
|
||||||
|
|
||||||
|
**곁들여 고친 것**: `Take Mine` · `Take Theirs` 버튼이 <b>줄을 옮겨도 켜짐/꺼짐이 그대로였다.</b>
|
||||||
|
CommunityToolkit 커맨드는 CommandManager 를 구독하지 않는데 이 둘만 갱신 목록에서 빠져 있었다.
|
||||||
|
키보드(`Alt+←/→`)로는 되고 버튼만 안 되던 이유가 이것이다.
|
||||||
|
|
||||||
### ① 폴더 비교
|
### ① 폴더 비교
|
||||||
|
|
||||||
@ -69,4 +80,5 @@
|
|||||||
| 날짜 | 항목 | 커밋 |
|
| 날짜 | 항목 | 커밋 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| 2026-09-08 | 저장소 개설 (MergeHub · UbisamPlatform 각각) | `7719094` / `36ee902` |
|
| 2026-09-08 | 저장소 개설 (MergeHub · UbisamPlatform 각각) | `7719094` / `36ee902` |
|
||||||
| 2026-09-08 | **④ Git 조작 5가지** — amend · stash 목록 · 커밋 간 비교 · blame · 변경분 추출 | 아래 |
|
| 2026-09-08 | **④ Git 조작 5가지** — amend · stash 목록 · 커밋 간 비교 · blame · 변경분 추출 | `9956567` |
|
||||||
|
| 2026-09-08 | **③ 3-Way** — 자동 병합 · 충돌만 이동 · 병합 완료 판정 | 아래 |
|
||||||
|
|||||||
@ -130,6 +130,7 @@ namespace MergeHub.Models
|
|||||||
[NotifyPropertyChangedFor(nameof(Highlight))]
|
[NotifyPropertyChangedFor(nameof(Highlight))]
|
||||||
[NotifyPropertyChangedFor(nameof(ThreeWay))]
|
[NotifyPropertyChangedFor(nameof(ThreeWay))]
|
||||||
[NotifyPropertyChangedFor(nameof(IsConflict))]
|
[NotifyPropertyChangedFor(nameof(IsConflict))]
|
||||||
|
[NotifyPropertyChangedFor(nameof(NeedsDecision))]
|
||||||
[NotifyPropertyChangedFor(nameof(CanTakeRemote))]
|
[NotifyPropertyChangedFor(nameof(CanTakeRemote))]
|
||||||
[NotifyPropertyChangedFor(nameof(CanTakeLocal))]
|
[NotifyPropertyChangedFor(nameof(CanTakeLocal))]
|
||||||
private string _localText;
|
private string _localText;
|
||||||
@ -141,11 +142,13 @@ namespace MergeHub.Models
|
|||||||
/// <summary>BASE 내용을 가져와 반영한 줄인지. 되돌리면 풀린다.</summary>
|
/// <summary>BASE 내용을 가져와 반영한 줄인지. 되돌리면 풀린다.</summary>
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
[NotifyPropertyChangedFor(nameof(Highlight))]
|
[NotifyPropertyChangedFor(nameof(Highlight))]
|
||||||
|
[NotifyPropertyChangedFor(nameof(NeedsDecision))]
|
||||||
private bool _isApplied;
|
private bool _isApplied;
|
||||||
|
|
||||||
/// <summary>사용자가 직접 타이핑해 고친 줄인지. "머지 반영"과 구분해서 표시한다.</summary>
|
/// <summary>사용자가 직접 타이핑해 고친 줄인지. "머지 반영"과 구분해서 표시한다.</summary>
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
[NotifyPropertyChangedFor(nameof(Highlight))]
|
[NotifyPropertyChangedFor(nameof(Highlight))]
|
||||||
|
[NotifyPropertyChangedFor(nameof(NeedsDecision))]
|
||||||
private bool _isManuallyEdited;
|
private bool _isManuallyEdited;
|
||||||
|
|
||||||
/// <summary>반영하면서 줄 끝에 붙인 주석(툴팁으로 보여준다).</summary>
|
/// <summary>반영하면서 줄 끝에 붙인 주석(툴팁으로 보여준다).</summary>
|
||||||
@ -281,6 +284,7 @@ namespace MergeHub.Models
|
|||||||
[NotifyPropertyChangedFor(nameof(ThreeWay))]
|
[NotifyPropertyChangedFor(nameof(ThreeWay))]
|
||||||
[NotifyPropertyChangedFor(nameof(Highlight))]
|
[NotifyPropertyChangedFor(nameof(Highlight))]
|
||||||
[NotifyPropertyChangedFor(nameof(IsConflict))]
|
[NotifyPropertyChangedFor(nameof(IsConflict))]
|
||||||
|
[NotifyPropertyChangedFor(nameof(NeedsDecision))]
|
||||||
[NotifyPropertyChangedFor(nameof(CanTakeRemote))]
|
[NotifyPropertyChangedFor(nameof(CanTakeRemote))]
|
||||||
[NotifyPropertyChangedFor(nameof(CanTakeLocal))]
|
[NotifyPropertyChangedFor(nameof(CanTakeLocal))]
|
||||||
private string _remoteText = string.Empty;
|
private string _remoteText = string.Empty;
|
||||||
@ -363,6 +367,15 @@ namespace MergeHub.Models
|
|||||||
|
|
||||||
public bool IsConflict => ThreeWay == ThreeWayState.Conflict;
|
public bool IsConflict => ThreeWay == ThreeWayState.Conflict;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 아직 사람이 골라야 하는 충돌 줄인지.
|
||||||
|
///
|
||||||
|
/// 충돌인지 아닌지는 BASE·MINE·REMOTE 를 견주면 저절로 나오지만, <b>"내 것으로 간다"고
|
||||||
|
/// 정한 줄은 내용이 그대로라 여전히 충돌로 보인다.</b> 그래서 "정했다"는 표시
|
||||||
|
/// (반영했거나 직접 고쳤거나)를 함께 본다 - 이 수가 0 이 되면 이 파일의 병합은 끝난 것이다.
|
||||||
|
/// </summary>
|
||||||
|
public bool NeedsDecision => IsConflict && !IsApplied && !IsManuallyEdited && !IsMergeNote;
|
||||||
|
|
||||||
/// <summary>REMOTE 내용을 가져올 수 있는 줄인지. 이미 같으면 가져올 것이 없다.</summary>
|
/// <summary>REMOTE 내용을 가져올 수 있는 줄인지. 이미 같으면 가져올 것이 없다.</summary>
|
||||||
public bool CanTakeRemote =>
|
public bool CanTakeRemote =>
|
||||||
IsThreeWay && !IsMergeNote && ThreeWay is ThreeWayState.RemoteChanged or ThreeWayState.Conflict;
|
IsThreeWay && !IsMergeNote && ThreeWay is ThreeWayState.RemoteChanged or ThreeWayState.Conflict;
|
||||||
|
|||||||
@ -21,6 +21,9 @@ namespace MergeHub.Services
|
|||||||
GoToLine,
|
GoToLine,
|
||||||
NextBookmark,
|
NextBookmark,
|
||||||
ToggleBookmark,
|
ToggleBookmark,
|
||||||
|
NextConflict,
|
||||||
|
PrevConflict,
|
||||||
|
AutoMerge,
|
||||||
OpenFolderCompare,
|
OpenFolderCompare,
|
||||||
Find,
|
Find,
|
||||||
FindNext,
|
FindNext,
|
||||||
@ -109,6 +112,13 @@ namespace MergeHub.Services
|
|||||||
new ShortcutEntry(ShortcutAction.CopyRightToLeft, Key.Left, ModifierKeys.Alt, "병합·편집",
|
new ShortcutEntry(ShortcutAction.CopyRightToLeft, Key.Left, ModifierKeys.Alt, "병합·편집",
|
||||||
"오른쪽 → 왼쪽 반영 (2-Way: 이 줄 되돌리기 / 3-Way: Take Theirs)"),
|
"오른쪽 → 왼쪽 반영 (2-Way: 이 줄 되돌리기 / 3-Way: Take Theirs)"),
|
||||||
|
|
||||||
|
new ShortcutEntry(ShortcutAction.NextConflict, Key.Down, ModifierKeys.Alt | ModifierKeys.Shift, "병합·편집",
|
||||||
|
"다음 충돌 (3-Way)", "차이가 아니라 <아직 안 정한 충돌>만 딛는다. 자동 병합과 짝이다."),
|
||||||
|
new ShortcutEntry(ShortcutAction.PrevConflict, Key.Up, ModifierKeys.Alt | ModifierKeys.Shift, "병합·편집",
|
||||||
|
"이전 충돌 (3-Way)"),
|
||||||
|
new ShortcutEntry(ShortcutAction.AutoMerge, Key.M, ModifierKeys.Alt, "병합·편집",
|
||||||
|
"자동 병합 - 저쪽만 고친 줄을 한 번에 가져온다 (3-Way)"),
|
||||||
|
|
||||||
new ShortcutEntry(ShortcutAction.Undo, Key.Z, ModifierKeys.Control, "병합·편집", "실행 취소",
|
new ShortcutEntry(ShortcutAction.Undo, Key.Z, ModifierKeys.Control, "병합·편집", "실행 취소",
|
||||||
"머지 반영을 되돌린다. 되돌릴 머지 작업이 없으면 편집기의 글자 되돌리기가 그대로 동작한다."),
|
"머지 반영을 되돌린다. 되돌릴 머지 작업이 없으면 편집기의 글자 되돌리기가 그대로 동작한다."),
|
||||||
new ShortcutEntry(ShortcutAction.Redo, Key.Y, ModifierKeys.Control, "병합·편집", "다시 실행"),
|
new ShortcutEntry(ShortcutAction.Redo, Key.Y, ModifierKeys.Control, "병합·편집", "다시 실행"),
|
||||||
|
|||||||
@ -453,6 +453,11 @@ namespace MergeHub.ViewModels
|
|||||||
UndoCommand.NotifyCanExecuteChanged();
|
UndoCommand.NotifyCanExecuteChanged();
|
||||||
RedoCommand.NotifyCanExecuteChanged();
|
RedoCommand.NotifyCanExecuteChanged();
|
||||||
AcceptAllCommand.NotifyCanExecuteChanged();
|
AcceptAllCommand.NotifyCanExecuteChanged();
|
||||||
|
|
||||||
|
// 3-Way 의 두 버튼. 빠져 있어서 줄을 옮겨도 켜짐/꺼짐이 그대로였다 -
|
||||||
|
// CommunityToolkit 의 커맨드는 CommandManager 를 구독하지 않으므로 직접 알려야 한다.
|
||||||
|
AcceptLocalCommand.NotifyCanExecuteChanged();
|
||||||
|
AcceptRemoteCommand.NotifyCanExecuteChanged();
|
||||||
FirstDiffCommand.NotifyCanExecuteChanged();
|
FirstDiffCommand.NotifyCanExecuteChanged();
|
||||||
PrevDiffCommand.NotifyCanExecuteChanged();
|
PrevDiffCommand.NotifyCanExecuteChanged();
|
||||||
NextDiffCommand.NotifyCanExecuteChanged();
|
NextDiffCommand.NotifyCanExecuteChanged();
|
||||||
@ -462,6 +467,10 @@ namespace MergeHub.ViewModels
|
|||||||
ReloadCommand.NotifyCanExecuteChanged();
|
ReloadCommand.NotifyCanExecuteChanged();
|
||||||
SaveAndCommitCommand.NotifyCanExecuteChanged();
|
SaveAndCommitCommand.NotifyCanExecuteChanged();
|
||||||
SavePlainCommand.NotifyCanExecuteChanged();
|
SavePlainCommand.NotifyCanExecuteChanged();
|
||||||
|
|
||||||
|
AutoMergeCommand.NotifyCanExecuteChanged();
|
||||||
|
NextConflictCommand.NotifyCanExecuteChanged();
|
||||||
|
PrevConflictCommand.NotifyCanExecuteChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ================= 로드 =================
|
// ================= 로드 =================
|
||||||
@ -621,6 +630,8 @@ namespace MergeHub.ViewModels
|
|||||||
_baseTextSnapshot = baseText;
|
_baseTextSnapshot = baseText;
|
||||||
_remoteTextSnapshot = threeWay ? remoteText : string.Empty;
|
_remoteTextSnapshot = threeWay ? remoteText : string.Empty;
|
||||||
_mineTextSnapshot = threeWay ? localText : string.Empty;
|
_mineTextSnapshot = threeWay ? localText : string.Empty;
|
||||||
|
_resolvedBaseNumbers.Clear(); // 파일이 바뀌면 앞 파일의 결정은 뜻이 없다
|
||||||
|
_resolvedMineNumbers.Clear();
|
||||||
_originalLocalText = BuildLocalText();
|
_originalLocalText = BuildLocalText();
|
||||||
SyncEditorFromLines();
|
SyncEditorFromLines();
|
||||||
|
|
||||||
@ -994,7 +1005,10 @@ namespace MergeHub.ViewModels
|
|||||||
if (beforeHasLocal == afterHasLocal && string.Equals(before, after, StringComparison.Ordinal))
|
if (beforeHasLocal == afterHasLocal && string.Equals(before, after, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
// 이미 내 원본과 같다. 충돌 줄이면 "내 것으로 정했다"는 표시만 남긴다.
|
// 이미 내 원본과 같다. 충돌 줄이면 "내 것으로 정했다"는 표시만 남긴다.
|
||||||
|
// 내용이 안 바뀌므로 다시 정렬하면 이 표시가 사라진다 - BASE 줄 번호로 따로 붙들어 둔다.
|
||||||
line.IsApplied = true;
|
line.IsApplied = true;
|
||||||
|
if (line.BaseNumber is int resolvedBase) _resolvedBaseNumbers.Add(resolvedBase);
|
||||||
|
else if (line.OriginalLocalNumber is int resolvedMine) _resolvedMineNumbers.Add(resolvedMine);
|
||||||
AfterEdit();
|
AfterEdit();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1270,6 +1284,12 @@ namespace MergeHub.ViewModels
|
|||||||
if (row.LocalNumber is int number && touched.Contains(number))
|
if (row.LocalNumber is int number && touched.Contains(number))
|
||||||
row.IsManuallyEdited = true;
|
row.IsManuallyEdited = true;
|
||||||
|
|
||||||
|
// "내 것으로 간다"고 정했던 충돌은 내용이 그대로라 글만 봐서는 알 수 없다.
|
||||||
|
if (row.BaseNumber is int baseNumber
|
||||||
|
? _resolvedBaseNumbers.Contains(baseNumber)
|
||||||
|
: row.OriginalLocalNumber is int mineNumber && _resolvedMineNumbers.Contains(mineNumber))
|
||||||
|
row.IsApplied = true;
|
||||||
|
|
||||||
Lines.Add(row);
|
Lines.Add(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1460,6 +1480,8 @@ namespace MergeHub.ViewModels
|
|||||||
_baseTextSnapshot = baseText;
|
_baseTextSnapshot = baseText;
|
||||||
_remoteTextSnapshot = string.Empty; // 파일 두 개 비교는 늘 2-Way 다
|
_remoteTextSnapshot = string.Empty; // 파일 두 개 비교는 늘 2-Way 다
|
||||||
_mineTextSnapshot = string.Empty;
|
_mineTextSnapshot = string.Empty;
|
||||||
|
_resolvedBaseNumbers.Clear();
|
||||||
|
_resolvedMineNumbers.Clear();
|
||||||
|
|
||||||
RefreshLanguage();
|
RefreshLanguage();
|
||||||
var rows = BuildLines(baseText, localText);
|
var rows = BuildLines(baseText, localText);
|
||||||
@ -2001,6 +2023,85 @@ namespace MergeHub.ViewModels
|
|||||||
RefreshProblems();
|
RefreshProblems();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ================= 자동 병합 · 충돌만 보기 =================
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "내 것으로 간다"고 정한 충돌 줄의 BASE 줄 번호.
|
||||||
|
///
|
||||||
|
/// 그 결정은 <b>내용을 바꾸지 않으므로</b> 글만 봐서는 되살릴 수 없다. 편집 뒤 다시 정렬하면
|
||||||
|
/// 행이 새로 만들어지는데, 이걸 들고 있지 않으면 이미 정한 충돌이 되살아나
|
||||||
|
/// "남은 충돌"이 줄지 않는다. BASE 는 얼어 있으므로 그 줄 번호가 안전한 열쇠다.
|
||||||
|
/// </summary>
|
||||||
|
private readonly HashSet<int> _resolvedBaseNumbers = new HashSet<int>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// BASE 에 없는 충돌(양쪽이 각자 새 줄을 넣은 자리)용. 이런 줄은 BASE 줄 번호가 없으므로
|
||||||
|
/// 역시 얼어 있는 MINE 줄 번호로 붙든다.
|
||||||
|
/// </summary>
|
||||||
|
private readonly HashSet<int> _resolvedMineNumbers = new HashSet<int>();
|
||||||
|
|
||||||
|
private bool CanAutoMerge() => IsThreeWay && Lines.Any(l => l.ThreeWay == ThreeWayState.RemoteChanged);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 저쪽만 고친 줄을 한 번에 결과로 가져온다. 사람은 <b>충돌만</b> 판단하면 된다.
|
||||||
|
///
|
||||||
|
/// 안 겹치는 변경까지 하나씩 누르게 하면 100줄짜리 충돌 파일에서 정작 판단이 필요한
|
||||||
|
/// 서너 줄이 묻힌다 - `git merge` 가 자동으로 해 주는 일을 여기서도 해 준다.
|
||||||
|
/// 내가 고친 줄과 충돌 줄은 건드리지 않는다.
|
||||||
|
/// </summary>
|
||||||
|
[RelayCommand(CanExecute = nameof(CanAutoMerge))]
|
||||||
|
private void AutoMerge()
|
||||||
|
{
|
||||||
|
// 반영하면서 주석 줄이 끼어들어 Lines 가 바뀐다. 먼저 목록을 떠 놓고 돈다.
|
||||||
|
var targets = Lines.Where(l => l.ThreeWay == ThreeWayState.RemoteChanged && l.CanTakeRemote).ToList();
|
||||||
|
if (targets.Count == 0) return;
|
||||||
|
|
||||||
|
foreach (var line in targets) ApplyRemote(line, requery: false);
|
||||||
|
|
||||||
|
AfterEdit();
|
||||||
|
MessageUtil.ShowSuccessToast(UnresolvedConflictCount == 0
|
||||||
|
? $"{targets.Count}줄 자동 병합 · 남은 충돌 없음"
|
||||||
|
: $"{targets.Count}줄 자동 병합 · 남은 충돌 {UnresolvedConflictCount}개");
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CanGoConflict() => IsThreeWay && Lines.Any(l => l.NeedsDecision);
|
||||||
|
|
||||||
|
/// <summary>다음 충돌로. `다음 차이` 와 달리 <b>아직 안 정한 충돌만</b> 딛는다.</summary>
|
||||||
|
[RelayCommand(CanExecute = nameof(CanGoConflict))]
|
||||||
|
private void NextConflict()
|
||||||
|
{
|
||||||
|
for (var row = CaretRow + 1; row < Lines.Count; row++)
|
||||||
|
if (Lines[row].NeedsDecision) { GoToRow(row); return; }
|
||||||
|
|
||||||
|
// 끝까지 갔으면 처음으로 돈다 - 차이 이동과 같은 감각이다.
|
||||||
|
for (var row = 0; row < Lines.Count; row++)
|
||||||
|
if (Lines[row].NeedsDecision) { GoToRow(row); return; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand(CanExecute = nameof(CanGoConflict))]
|
||||||
|
private void PrevConflict()
|
||||||
|
{
|
||||||
|
for (var row = CaretRow - 1; row >= 0; row--)
|
||||||
|
if (Lines[row].NeedsDecision) { GoToRow(row); return; }
|
||||||
|
|
||||||
|
for (var row = Lines.Count - 1; row >= 0; row--)
|
||||||
|
if (Lines[row].NeedsDecision) { GoToRow(row); return; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>아직 사람이 골라야 하는 충돌 줄 수. 0 이면 이 파일의 병합은 끝난 것이다.</summary>
|
||||||
|
[ObservableProperty]
|
||||||
|
[NotifyPropertyChangedFor(nameof(MergeStatusText))]
|
||||||
|
[NotifyPropertyChangedFor(nameof(IsMergeComplete))]
|
||||||
|
private int _unresolvedConflictCount;
|
||||||
|
|
||||||
|
/// <summary>3-Way 에서 지금 어디까지 왔는지 한 줄로.</summary>
|
||||||
|
public string MergeStatusText =>
|
||||||
|
!IsThreeWay ? string.Empty :
|
||||||
|
UnresolvedConflictCount == 0 ? "충돌 정리 끝" :
|
||||||
|
$"남은 충돌 {UnresolvedConflictCount}개";
|
||||||
|
|
||||||
|
public bool IsMergeComplete => IsThreeWay && UnresolvedConflictCount == 0;
|
||||||
|
|
||||||
// ================= 범례에 실시간으로 붙는 개수 =================
|
// ================= 범례에 실시간으로 붙는 개수 =================
|
||||||
|
|
||||||
[ObservableProperty] private int _changedCount;
|
[ObservableProperty] private int _changedCount;
|
||||||
@ -2022,10 +2123,12 @@ namespace MergeHub.ViewModels
|
|||||||
private void UpdateLegendCounts()
|
private void UpdateLegendCounts()
|
||||||
{
|
{
|
||||||
int changed = 0, applied = 0, edited = 0, added = 0, removed = 0, moved = 0, ignored = 0, same = 0;
|
int changed = 0, applied = 0, edited = 0, added = 0, removed = 0, moved = 0, ignored = 0, same = 0;
|
||||||
int remote = 0, conflict = 0;
|
int remote = 0, conflict = 0, needsDecision = 0;
|
||||||
|
|
||||||
foreach (var line in Lines)
|
foreach (var line in Lines)
|
||||||
{
|
{
|
||||||
|
if (line.NeedsDecision) needsDecision++;
|
||||||
|
|
||||||
switch (line.Highlight)
|
switch (line.Highlight)
|
||||||
{
|
{
|
||||||
case LineHighlight.Applied: applied++; break;
|
case LineHighlight.Applied: applied++; break;
|
||||||
@ -2051,6 +2154,7 @@ namespace MergeHub.ViewModels
|
|||||||
SameCount = same;
|
SameCount = same;
|
||||||
RemoteChangeCount = remote;
|
RemoteChangeCount = remote;
|
||||||
ConflictCount = conflict;
|
ConflictCount = conflict;
|
||||||
|
UnresolvedConflictCount = needsDecision;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string BuildLocalText()
|
private string BuildLocalText()
|
||||||
|
|||||||
@ -1223,6 +1223,46 @@
|
|||||||
Background="{DynamicResource Ubisam.Brush.Accent.Fill}"
|
Background="{DynamicResource Ubisam.Brush.Accent.Fill}"
|
||||||
Foreground="{DynamicResource Ubisam.Brush.Accent.OnFill}"
|
Foreground="{DynamicResource Ubisam.Brush.Accent.OnFill}"
|
||||||
ToolTip="선택한 줄을 REMOTE(Theirs) 내용으로"/>
|
ToolTip="선택한 줄을 REMOTE(Theirs) 내용으로"/>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
3-Way 흐름. "안 겹치는 변경은 자동으로, 사람은 충돌만" 이 순서다.
|
||||||
|
남은 충돌이 0 이 되면 그 자리에서 다 됐다고 알려 준다.
|
||||||
|
-->
|
||||||
|
<Border Margin="2,10,2,0" Padding="6,4" CornerRadius="3"
|
||||||
|
Visibility="{Binding IsThreeWay, Converter={StaticResource BoolToVis}}"
|
||||||
|
Background="{DynamicResource Ubisam.Brush.Input.Background}"
|
||||||
|
BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}" BorderThickness="1">
|
||||||
|
<TextBlock Text="{Binding MergeStatusText}" FontWeight="Bold" FontSize="11.5"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
ToolTip="아직 사람이 골라야 하는 줄 수입니다">
|
||||||
|
<TextBlock.Style>
|
||||||
|
<Style TargetType="TextBlock">
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource Ubisam.Brush.Danger}"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding IsMergeComplete}" Value="True">
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource Ubisam.Brush.Success}"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</TextBlock.Style>
|
||||||
|
</TextBlock>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<Button Content="자동 병합" Command="{Binding AutoMergeCommand}"
|
||||||
|
MinHeight="30" Margin="2,4,2,0"
|
||||||
|
Visibility="{Binding IsThreeWay, Converter={StaticResource BoolToVis}}"
|
||||||
|
ToolTip="저쪽만 고친 줄을 한 번에 가져옵니다. 충돌과 내가 고친 줄은 건드리지 않습니다"/>
|
||||||
|
|
||||||
|
<UniformGrid Columns="2" Margin="0,4,0,0"
|
||||||
|
Visibility="{Binding IsThreeWay, Converter={StaticResource BoolToVis}}">
|
||||||
|
<Button Content="이전 충돌" Command="{Binding PrevConflictCommand}"
|
||||||
|
MinHeight="28" FontSize="11"
|
||||||
|
ToolTip="아직 안 정한 충돌만 딛습니다 (Alt+Shift+Up)"/>
|
||||||
|
<Button Content="다음 충돌" Command="{Binding NextConflictCommand}"
|
||||||
|
MinHeight="28" FontSize="11"
|
||||||
|
ToolTip="아직 안 정한 충돌만 딛습니다 (Alt+Shift+Down)"/>
|
||||||
|
</UniformGrid>
|
||||||
|
|
||||||
<Button Content="Revert" Command="{Binding RevertCommand}" MinHeight="30" Margin="2,4,2,0"
|
<Button Content="Revert" Command="{Binding RevertCommand}" MinHeight="30" Margin="2,4,2,0"
|
||||||
Style="{StaticResource FilledButton}"
|
Style="{StaticResource FilledButton}"
|
||||||
Background="{DynamicResource Ubisam.Brush.Warning}"
|
Background="{DynamicResource Ubisam.Brush.Warning}"
|
||||||
|
|||||||
@ -709,6 +709,10 @@ namespace MergeHub.Views
|
|||||||
GoToBox.SelectAll();
|
GoToBox.SelectAll();
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
case ShortcutAction.NextConflict: return Run(vm.NextConflictCommand);
|
||||||
|
case ShortcutAction.PrevConflict: return Run(vm.PrevConflictCommand);
|
||||||
|
case ShortcutAction.AutoMerge: return Run(vm.AutoMergeCommand);
|
||||||
|
|
||||||
case ShortcutAction.NextBookmark: return Run(vm.NextBookmarkCommand);
|
case ShortcutAction.NextBookmark: return Run(vm.NextBookmarkCommand);
|
||||||
|
|
||||||
case ShortcutAction.ToggleBookmark:
|
case ShortcutAction.ToggleBookmark:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user