6차 A-4: 한쪽이 지운 충돌은 [파일 남기기] · [파일 지우기] 로 푼다
상대가 지운 파일(UD)이나 내가 지운 파일(DU)을 열면 줄 단위 3분할로 끌고 가서 지운 쪽 칸이 통째로 비었다. 범례는 내 쪽에만 있는 줄 89개를 "Theirs only 89" 라고 거꾸로 말했고, 정할 것인 "남길까 지울까" 는 화면 어디에도 없었다. 파일 단위 해결은 이미 바르게 동작했다. - DeletionConflict: 어느 쪽이 지웠는지, 남기기와 지우기가 각각 어느 쪽을 고르는 것인지(UD 는 남기기=내 것, DU 는 남기기=상대 것) - LoadFileAsync: 지운 충돌이면 남은 파일을 BASE 와 두 칸으로 보인다. 칸 제목이 누가 지웠는지 말하고, BASE 제목은 (merge base) - 범례 위 띠: 누가 지웠고 누가 고쳤는지, [파일 남기기] [파일 지우기] - 남기기는 고친 것이 있으면 고친 대로 남기고, 지우기는 고친 것이 있으면 한 번 묻는다. 푼 뒤 남은 충돌 첫 파일로 간다 - 해결로 저장과 같은 뒷정리를 ReloadAfterResolveAsync 로 모았다 시험 저장소 생성기에 DU 경우를 넣었다(충돌 23개). 뿌리 폴더를 잡고 있는 셸 때문에 다시 만들기가 멈춰서 안만 비우게 고쳤다. 화면 확인 23건 + BASE 제목 고친 뒤 5건 통과. MergeSaveTest 16건 추가. 21벌 775건 통과(새로 빌드한 실행 파일). 경고 0, 오류 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
0d9719d833
commit
27798fa145
@ -207,3 +207,63 @@ A-1 검사를 넣고 하네스를 돌리다 **세 벌이 빌드가 안 된다**
|
||||
A-2 에서 "건너뛴 목록 불러오기가 목록을 비우지 않게" 막았으므로 이제 **비지 않고 옛 목록이 남는다.**
|
||||
새로고침 도중 게이트를 쥔 채로 목록 다시 세우기가 불리는 뿌리는 그대로라, B-1 에서 본다.
|
||||
이번 확인에 쓴 `Io/Unit12.cs` 는 두 목록에 다 있어 결과에는 영향이 없다.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## A-4. 상대가 지운 파일을 3분할이 다룰 줄 모른다 (2026-09-14, 완료)
|
||||
|
||||
### 고치기 전에 다시 났다
|
||||
|
||||
시험 저장소 생성기에 **J. 내가 지웠는데 상대가 고침(DU)** 을 넣었다(지시서가 요구). 이제 충돌은 23개다.
|
||||
|
||||
| 파일 | git | 화면 |
|
||||
|---|---|---|
|
||||
| `Recipe/ToBeDeleted.cs` (상대가 지움) | `UD` | 네 칸, 지운 쪽 칸이 빔, 범례 **`Theirs only 89`** (실은 내 쪽에만 있는 줄), 충돌 1개, 지웠다는 말 없음 |
|
||||
| `Recipe/ToBeDeletedByMe.cs` (내가 지움) | `DU` | 네 칸, `Theirs only 0`, 충돌 1개, 지웠다는 말 없음 |
|
||||
|
||||
### 무엇이 원인이었나
|
||||
|
||||
지운 쪽의 blob 이 없으면 `GetThreeWayContent` 는 그쪽 글을 빈 문자열로 준다. 화면은 그걸 "상대가 줄을 전부 지운 파일" 로 보고
|
||||
줄 단위 3분할을 만들었다. 사람이 정할 것은 **"이 파일을 남길까 지울까"** 하나인데, 그 물음이 화면 어디에도 없었다.
|
||||
파일 단위 해결(`ResolveConflict`)은 이미 "없는 쪽을 고르면 지운다" 로 바르게 동작했다 - 화면이 그 길로 안내하지 못했을 뿐이다.
|
||||
|
||||
### 고친 것
|
||||
|
||||
| 자리 | 무엇 |
|
||||
|---|---|
|
||||
| `Services/DeletionConflict.cs` (새 파일) | 어느 쪽이 지웠는지, **남기기 · 지우기가 각각 어느 쪽을 고르는 것인지**를 정한다. 지운 쪽에 따라 뒤바뀌므로(UD 는 남기기=내 것, DU 는 남기기=상대 것) 한 곳에 모았다 |
|
||||
| `LoadFileAsync` | 한쪽이 지운 충돌이면 줄 단위 3분할로 안 간다. **남아 있는 파일을 BASE 와 두 칸으로** 보여 준다 - 남은 쪽이 무엇을 고쳤는지는 보인다. 칸 제목이 `feature/mine · 내 쪽 (상대는 지움)` 처럼 누가 지웠는지 말한다 |
|
||||
| 범례 위 띠 | "상대(feature/theirs)가 이 파일을 지웠습니다. 내 쪽(feature/mine)은 고쳤습니다. 파일을 남길까요, 지울까요?" 와 [파일 남기기] · [파일 지우기] |
|
||||
| [파일 남기기] | 파일이 있는 쪽을 골라 스테이징. **고친 것이 있으면 고친 대로 남긴다**(해결로 저장과 같다) |
|
||||
| [파일 지우기] | 없는 쪽을 골라 지우고 스테이징. 고친 것이 있으면 한 번 묻는다 |
|
||||
| 뒷정리 | 해결로 저장 · 남기기 · 지우기가 같은 `ReloadAfterResolveAsync` 를 쓴다. 남은 충돌 첫 파일이 열린다 |
|
||||
|
||||
### 확인한 것 (새로 만든 저장소, 충돌 23개에서 시작)
|
||||
|
||||
| 확인 | 결과 |
|
||||
|---|---|
|
||||
| 보통 충돌(`Io/Unit12.cs`) | 띠 없음, 네 칸 그대로 |
|
||||
| 상대가 지운 파일을 연다 | 띠 "상대(feature/theirs)가 이 파일을 지웠습니다…", 단추 둘 켜짐, **두 칸**, 제목 `feature/mine · 내 쪽 (상대는 지움)`, 거꾸로 센 범례 없음, 콤보 그대로 |
|
||||
| 거기서 [파일 지우기] | "지움 · ToBeDeleted.cs · 남은 충돌 22건", git 충돌 22, 워킹트리에서 파일 없어짐, 스테이징 `D `, 다음 충돌 파일이 열림 |
|
||||
| 내가 지운 파일을 연다 | 띠 "내 쪽(feature/mine)에서 이 파일을 지웠습니다…", 제목 `feature/theirs · 상대 쪽 (나는 지움)`, 두 칸 |
|
||||
| 거기서 [파일 남기기] | "남김 · ToBeDeletedByMe.cs · 남은 충돌 21건", git 충돌 21, 상대가 고친 줄이 들어간 파일이 생김, CRLF 그대로(CR 70 = LF 70), 스테이징 `A ` |
|
||||
| Git 탭 충돌 목록 | 두 파일이 빠짐 |
|
||||
|
||||
화면 확인 **23건 통과**. `MergeSaveTest` 에 지운 충돌 검사 16건을 넣었다(32 → 48).
|
||||
검사 **21벌 775건 통과**(전부 새로 빌드한 실행 파일) · 빌드 경고 0 · 오류 0.
|
||||
|
||||
### 화면을 보다 하나 더 고쳤다 — BASE 칸 제목
|
||||
|
||||
두 칸으로 보여 줄 때 BASE 칸 제목이 `a751a6b · Recipe/ToBeDeleted.cs` 로 나왔다. 지금 커밋과 견주는 것처럼 읽히지만
|
||||
실제로 보이는 것은 **공통 조상**이다. 제목을 정하는 조건이 "3분할인가" 만 봤기 때문이다.
|
||||
충돌 파일이면 두 칸이어도 `(merge base) 경로` 로 적게 했다.
|
||||
|
||||
다시 확인 **5건 통과** — 보통 충돌은 네 칸, 두 지운 파일 모두 BASE 제목이 `(merge base) …`, 지우기 → 남은 충돌 22건, 남기기 → 21건.
|
||||
이 고침은 화면 코드(`HomeViewModel`)만 건드려 검사 하네스에는 걸리지 않는다. 앞의 775건은 마지막 서비스 코드 변경 뒤에 돌린 값이다.
|
||||
|
||||
### 시험 저장소 생성기가 반쯤 지운 채 멈췄다
|
||||
|
||||
다시 만들려는데 생성기가 `다른 프로세스가 사용 중` 으로 멈추고 저장소가 **빈 폴더**로 남았다. 앱을 끄는 명령과 생성기를
|
||||
동시에 돌렸고, 그 뒤에도 셸이 이 폴더를 작업 폴더로 잡고 있어 **뿌리 폴더를 지울 수 없었다.**
|
||||
생성기가 뿌리는 두고 **안만 비우게** 고쳤다. 제품 코드와는 상관없다.
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
|
||||
| 날짜 | 항목 | 커밋 |
|
||||
|---|---|---|
|
||||
| 2026-09-14 | **6차 A-4** — 한쪽이 지운 충돌(UD·DU)은 줄 단위 3분할 대신 남은 파일을 두 칸으로 보이고 [파일 남기기] · [파일 지우기] 로 푼다. 거꾸로 세던 범례(Theirs only 89)가 없어짐 | 아래 |
|
||||
| 2026-09-14 | **6차 A-3** — 병합 중 충돌 파일을 열면 콤보를 안 건드려도 네 칸이 뜬다. REMOTE 제목에 상대 브랜치 이름(MERGE_HEAD → 병합 메시지 → 짧은 해시) | 아래 |
|
||||
| 2026-09-14 | **6차 A-1** — 충돌 해결 저장이 내 쪽(ours) 파일의 줄끝·인코딩을 따른다(3,363줄 파일 +3,367/−3,363 → +8/−4). 검사 모음이 옛 실행 파일로 돌던 것을 찾아 세 벌을 고침 | 아래 |
|
||||
| 2026-09-14 | **6차 A-2** — 병합 중 [해결로 저장] 을 따로 둠(쓰고 스테이징만). Save & Commit 은 남은 충돌이 있으면 쓰기 전에 막는다. 건너뛴 목록 불러오기가 목록을 비우던 것도 막음 | 아래 |
|
||||
|
||||
@ -96,7 +96,7 @@
|
||||
| **완료** | A-1 3분할로 푼 파일을 저장하면 CRLF 가 LF 로 바뀐다 — 내 쪽(ours) 파일의 줄끝·인코딩을 따른다 |
|
||||
| **완료** | A-2 Save & Commit 이 "실패" 라는데 파일은 이미 써지고 스테이징됐다 — [해결로 저장] 을 따로 둠 |
|
||||
| **완료** | A-3 충돌 파일을 고르면 상대(Theirs) 칸이 안 나온다 — 콤보를 안 건드려도 네 칸, REMOTE 제목에 상대 브랜치 |
|
||||
| 대기 | A-4 상대가 지운 파일을 3분할이 다룰 줄 모른다 |
|
||||
| **완료** | A-4 상대가 지운 파일을 3분할이 다룰 줄 모른다 — 남은 파일을 두 칸으로, [파일 남기기] · [파일 지우기] 띠 |
|
||||
| 대기 | A-5 양쪽 새 파일에 Accept Both 를 누르면 빌드가 깨진다 |
|
||||
|
||||
### B. 헷갈리거나 막히는 것
|
||||
|
||||
75
src/MergeHub/Services/DeletionConflict.cs
Normal file
75
src/MergeHub/Services/DeletionConflict.cs
Normal file
@ -0,0 +1,75 @@
|
||||
using MergeHub.Models;
|
||||
|
||||
namespace MergeHub.Services
|
||||
{
|
||||
/// <summary>병합 충돌에서 파일을 지운 쪽.</summary>
|
||||
public enum DeletedSide
|
||||
{
|
||||
/// <summary>양쪽 다 파일이 있다. 보통 충돌이다.</summary>
|
||||
None,
|
||||
|
||||
/// <summary>내가 지웠고 상대는 고쳤다 (git 상태 DU).</summary>
|
||||
Ours,
|
||||
|
||||
/// <summary>내가 고쳤고 상대는 지웠다 (git 상태 UD).</summary>
|
||||
Theirs,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 한쪽이 파일을 지운 충돌을 어떻게 풀지 가른다(6차 A-4).
|
||||
///
|
||||
/// 이런 충돌에서 사람이 정할 것은 <b>"이 파일을 남길까 지울까"</b> 하나다. 예전에는 줄 단위 3분할로 끌고 가서
|
||||
/// 지운 쪽 칸이 통째로 비고, 범례는 내 쪽에만 있는 줄 89개를 "Theirs only 89" 라고 거꾸로 말했다.
|
||||
/// 충돌은 10번 줄 하나로만 잡혀 진짜 물음이 안 보였다.
|
||||
///
|
||||
/// 풀기는 파일 단위 해결(<c>ResolveConflict</c>)에 맡긴다. 이미 "없는 쪽을 고르면 지운다" 로 동작한다.
|
||||
/// 여기서는 <b>남기기 · 지우기가 각각 어느 쪽을 고르는 것인지</b>만 정한다 - 지운 쪽에 따라 뒤바뀌므로
|
||||
/// 화면 코드에서 매번 따지면 틀리기 쉽다.
|
||||
/// </summary>
|
||||
public readonly struct DeletionConflict
|
||||
{
|
||||
private DeletionConflict(DeletedSide deleted)
|
||||
{
|
||||
Deleted = deleted;
|
||||
}
|
||||
|
||||
public DeletedSide Deleted { get; }
|
||||
|
||||
public bool IsDeletion => Deleted != DeletedSide.None;
|
||||
|
||||
/// <summary>
|
||||
/// git 인덱스의 충돌 항목에서 만든다. 한쪽만 없을 때만 지운 충돌이다.
|
||||
/// 둘 다 없거나(양쪽이 지움 - 충돌이 아니다) 둘 다 있으면 보통 충돌로 본다.
|
||||
/// </summary>
|
||||
public static DeletionConflict From(bool hasOurs, bool hasTheirs)
|
||||
{
|
||||
if (hasOurs && !hasTheirs) return new DeletionConflict(DeletedSide.Theirs);
|
||||
if (!hasOurs && hasTheirs) return new DeletionConflict(DeletedSide.Ours);
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>남기려면 고를 쪽 - 파일이 <b>있는</b> 쪽.</summary>
|
||||
public ConflictResolution KeepWith =>
|
||||
Deleted == DeletedSide.Ours ? ConflictResolution.Theirs : ConflictResolution.Mine;
|
||||
|
||||
/// <summary>지우려면 고를 쪽 - 파일이 <b>없는</b> 쪽. 없는 쪽을 고르면 파일 단위 해결이 지운다.</summary>
|
||||
public ConflictResolution DeleteWith =>
|
||||
Deleted == DeletedSide.Ours ? ConflictResolution.Mine : ConflictResolution.Theirs;
|
||||
|
||||
/// <summary>화면 위 안내 한 줄. 누가 지웠고 누가 고쳤는지, 무엇을 골라야 하는지.</summary>
|
||||
public string Message(string mine, string theirs) => Deleted switch
|
||||
{
|
||||
DeletedSide.Theirs => $"상대({theirs})가 이 파일을 지웠습니다. 내 쪽({mine})은 고쳤습니다. 파일을 남길까요, 지울까요?",
|
||||
DeletedSide.Ours => $"내 쪽({mine})에서 이 파일을 지웠습니다. 상대({theirs})는 고쳤습니다. 파일을 남길까요, 지울까요?",
|
||||
_ => string.Empty,
|
||||
};
|
||||
|
||||
/// <summary>남아 있는 파일을 보여 주는 칸의 제목.</summary>
|
||||
public string SurvivingLabel(string mine, string theirs) => Deleted switch
|
||||
{
|
||||
DeletedSide.Theirs => $"{mine} · 내 쪽 (상대는 지움)",
|
||||
DeletedSide.Ours => $"{theirs} · 상대 쪽 (나는 지움)",
|
||||
_ => mine,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -722,11 +722,93 @@ namespace MergeHub.ViewModels
|
||||
: $"해결로 저장 · {Path.GetFileName(path)} · 남은 충돌 {remaining}건");
|
||||
});
|
||||
|
||||
// 새로고침 직후엔 게이트가 잠깐 막혀 목록 세우기가 건너뛰어진다. 기다렸다 다시 세운다.
|
||||
await ReloadAfterResolveAsync();
|
||||
}
|
||||
|
||||
// ================= 한쪽이 지운 충돌 (6차 A-4) =================
|
||||
|
||||
private DeletionConflict _deletion;
|
||||
|
||||
/// <summary>지금 열린 파일이 한쪽이 지운 충돌인지. 화면 위에 "남길까 지울까" 띠를 띄운다.</summary>
|
||||
[ObservableProperty] private bool _isDeletionConflict;
|
||||
|
||||
[ObservableProperty] private string _deletionConflictText = string.Empty;
|
||||
|
||||
private void SetDeletion(DeletionConflict deletion, string? theirsName)
|
||||
{
|
||||
_deletion = deletion;
|
||||
IsDeletionConflict = deletion.IsDeletion;
|
||||
DeletionConflictText = deletion.IsDeletion
|
||||
? deletion.Message(_workspace.CurrentBranch, theirsName ?? "상대")
|
||||
: string.Empty;
|
||||
KeepFileCommand.NotifyCanExecuteChanged();
|
||||
DeleteFileCommand.NotifyCanExecuteChanged();
|
||||
}
|
||||
|
||||
private bool CanDecideDeletion() =>
|
||||
IsGitMode && IsDeletionConflict && !string.IsNullOrEmpty(SelectedFile);
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanDecideDeletion))]
|
||||
private Task KeepFileAsync() => DecideDeletionAsync(keep: true);
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanDecideDeletion))]
|
||||
private Task DeleteFileAsync() => DecideDeletionAsync(keep: false);
|
||||
|
||||
/// <summary>
|
||||
/// 한쪽이 지운 충돌을 파일 단위로 푼다. 남기기는 파일이 있는 쪽, 지우기는 없는 쪽을 고르는 것이다
|
||||
/// (<see cref="DeletionConflict"/>). 푼 뒤에는 해결로 저장과 같이 남은 충돌의 첫 파일로 간다.
|
||||
/// </summary>
|
||||
private async Task DecideDeletionAsync(bool keep)
|
||||
{
|
||||
var path = SelectedFile;
|
||||
if (string.IsNullOrEmpty(path)) return;
|
||||
|
||||
// 남기되 고친 것이 있으면 고친 대로 남긴다 - 해결로 저장과 같은 일이다.
|
||||
if (keep && HasChanges)
|
||||
{
|
||||
await SaveResolvedAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
// 고친 것이 있는데 지우면 고친 것도 사라진다. 한 번 묻는다.
|
||||
if (!keep && HasChanges && !ConfirmDiscard($"이 파일을 지웁니다: {path}")) return;
|
||||
|
||||
var resolution = keep ? _deletion.KeepWith : _deletion.DeleteWith;
|
||||
var remaining = 0;
|
||||
|
||||
var ok = await _workspace.RunAsync(keep ? "파일 남기기" : "파일 지우기", () =>
|
||||
{
|
||||
_workspace.Git.ResolveConflict(path!, resolution);
|
||||
remaining = MergeSavePlan.RemainingAfterSave(_workspace.Git.GetConflicts().Select(c => c.Path), path);
|
||||
});
|
||||
|
||||
if (!ok) return;
|
||||
|
||||
_workspace.OnUi(() =>
|
||||
{
|
||||
_undo.Clear();
|
||||
_redo.Clear();
|
||||
HasChanges = false;
|
||||
RaiseCanExecuteChanged();
|
||||
|
||||
var what = keep ? "남김" : "지움";
|
||||
MessageUtil.ShowSuccessToast(remaining == 0
|
||||
? $"{what} · {Path.GetFileName(path)} · 남은 충돌 없음 - 이제 커밋할 수 있습니다"
|
||||
: $"{what} · {Path.GetFileName(path)} · 남은 충돌 {remaining}건");
|
||||
});
|
||||
|
||||
await ReloadAfterResolveAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 충돌 하나를 푼 뒤 저장소를 다시 읽고 파일 목록을 다시 세운다. 방금 푼 파일이 빠지므로 남은 충돌 첫 파일이 열린다.
|
||||
/// 새로고침 직후엔 게이트가 잠깐 막혀 목록 세우기가 건너뛰어지므로 풀릴 때까지 기다렸다 한다(6차 A-2).
|
||||
/// </summary>
|
||||
private async Task ReloadAfterResolveAsync()
|
||||
{
|
||||
await _workspace.RefreshAsync();
|
||||
|
||||
// 새로고침이 알아서 목록을 다시 세우지만, 그 신호가 게이트를 쥔 채로 오면 건너뛴다.
|
||||
// 새로고침 직후에도 다른 화면(Git 탭)이 게이트를 잠깐 쥐는 일이 있어, 곧바로 불러도 또 건너뛰었다.
|
||||
// 게이트가 풀릴 때까지 기다렸다가 세운다. 방금 푼 파일은 충돌 목록에서 빠지므로 남은 충돌의 첫 파일이 열린다.
|
||||
for (var attempt = 0; attempt < 5; attempt++)
|
||||
{
|
||||
await _workspace.WhenIdleAsync();
|
||||
@ -757,6 +839,8 @@ namespace MergeHub.ViewModels
|
||||
ReloadCommand.NotifyCanExecuteChanged();
|
||||
SaveAndCommitCommand.NotifyCanExecuteChanged();
|
||||
SaveResolvedCommand.NotifyCanExecuteChanged();
|
||||
KeepFileCommand.NotifyCanExecuteChanged();
|
||||
DeleteFileCommand.NotifyCanExecuteChanged();
|
||||
SavePlainCommand.NotifyCanExecuteChanged();
|
||||
ExportReportCommand.NotifyCanExecuteChanged();
|
||||
|
||||
@ -878,6 +962,7 @@ namespace MergeHub.ViewModels
|
||||
var branch = RemoteBranch;
|
||||
var baseOrigin = string.Empty;
|
||||
string? theirsName = null;
|
||||
var deletion = default(DeletionConflict);
|
||||
|
||||
// 다른 작업이 게이트를 쥐고 있으면 RunAsync 는 아무것도 하지 않고 false 를 준다.
|
||||
// 그대로 진행하면 빈 글로 화면을 갈아엎어 "차이 없음"이 뜬다(파일 비교 쪽에서 실제로 겪었다).
|
||||
@ -892,9 +977,23 @@ namespace MergeHub.ViewModels
|
||||
localText = three.OursText;
|
||||
remoteText = three.TheirsText;
|
||||
isConflict = true;
|
||||
threeWay = true;
|
||||
baseOrigin = "merge base";
|
||||
theirsName = _workspace.Git.GetMergeTheirsName();
|
||||
|
||||
// 한쪽이 파일을 지운 충돌(6차 A-4). 정할 것은 "남길까 지울까" 하나라 줄 단위 3분할로 끌고 가지 않는다.
|
||||
// 끌고 가면 지운 쪽 칸이 통째로 비고, 범례는 남은 쪽에만 있는 줄을 거꾸로 센다("Theirs only 89").
|
||||
// 대신 <b>남아 있는 파일을 BASE 와 두 칸으로</b> 보여 준다 - 남은 쪽이 무엇을 고쳤는지는 보인다.
|
||||
deletion = DeletionConflict.From(conflict.HasOurs, conflict.HasTheirs);
|
||||
if (deletion.IsDeletion)
|
||||
{
|
||||
threeWay = false;
|
||||
localText = deletion.Deleted == DeletedSide.Ours ? three.TheirsText : three.OursText;
|
||||
remoteText = string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
threeWay = true;
|
||||
}
|
||||
}
|
||||
else if (threeWay)
|
||||
{
|
||||
@ -941,7 +1040,8 @@ namespace MergeHub.ViewModels
|
||||
if (!IsGitMode) return;
|
||||
|
||||
// 행을 싣기 전에 틀부터 바꾼다. 세 칸 행을 두 칸 틀에 실으면 REMOTE 칸이 안 보인다.
|
||||
SetConflictThreeWay(isConflict);
|
||||
SetConflictThreeWay(isConflict && !deletion.IsDeletion);
|
||||
SetDeletion(deletion, theirsName);
|
||||
|
||||
Lines.Clear();
|
||||
foreach (var row in rows) Lines.Add(row);
|
||||
@ -952,7 +1052,9 @@ namespace MergeHub.ViewModels
|
||||
BaseHeaderLead = string.Empty;
|
||||
LocalHeaderLead = string.Empty;
|
||||
|
||||
BaseHeader = threeWay
|
||||
// 충돌 파일이면 두 칸으로 보여도(한쪽이 지운 충돌, 6차 A-4) BASE 는 선택 커밋이 아니라 공통 조상이다.
|
||||
// threeWay 만 보면 "a751a6b · 경로" 로 적혀 지금 커밋과 견주는 것처럼 읽혔다.
|
||||
BaseHeader = threeWay || isConflict
|
||||
? string.IsNullOrEmpty(baseText)
|
||||
? $"({baseOrigin}) 이 파일 없음 - 양쪽에서 새로 만든 파일입니다"
|
||||
: $"({baseOrigin}) {path}"
|
||||
@ -962,6 +1064,9 @@ namespace MergeHub.ViewModels
|
||||
? $"REMOTE · {theirsName ?? "THEIRS"}"
|
||||
: $"REMOTE · {branch}";
|
||||
|
||||
if (deletion.IsDeletion)
|
||||
LocalHeader = deletion.SurvivingLabel(_workspace.CurrentBranch, theirsName ?? "상대");
|
||||
|
||||
_baseTextSnapshot = baseText;
|
||||
_remoteTextSnapshot = threeWay ? remoteText : string.Empty;
|
||||
_mineTextSnapshot = threeWay ? localText : string.Empty;
|
||||
@ -1011,6 +1116,7 @@ namespace MergeHub.ViewModels
|
||||
HasChanges = false;
|
||||
_loadedAsConflict = false;
|
||||
SetConflictThreeWay(false);
|
||||
SetDeletion(default, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -552,7 +552,31 @@
|
||||
|
||||
<!-- ===== 색 구분 안내(범례) =====
|
||||
칩 색은 LineHighlightBrushes 한 곳에서 오므로 코드 패널과 항상 같은 색이다. -->
|
||||
<Border Grid.Row="1" Padding="10,3" Margin="0,0,0,5"
|
||||
<StackPanel Grid.Row="1">
|
||||
|
||||
<!-- ===== 한쪽이 지운 충돌 (6차 A-4) =====
|
||||
이런 충돌에서 정할 것은 "이 파일을 남길까 지울까" 하나다. 줄 단위로 끌고 가면 그 물음이 안 보였다.
|
||||
범례 바로 위, 코드보다 먼저 눈에 들어오는 자리에 둔다. -->
|
||||
<Border Padding="10,6" Margin="0,0,0,5" BorderThickness="1"
|
||||
Visibility="{Binding IsDeletionConflict, Converter={StaticResource BoolToVis}}"
|
||||
Background="{DynamicResource Ubisam.Brush.Input.Background}"
|
||||
BorderBrush="{DynamicResource Ubisam.Brush.Danger}">
|
||||
<DockPanel LastChildFill="True">
|
||||
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<Button Content="파일 남기기" MinWidth="110" Margin="8,0,0,0"
|
||||
Command="{Binding KeepFileCommand}"
|
||||
Style="{StaticResource FilledButton}" FontWeight="Bold"
|
||||
ToolTip="파일이 있는 쪽을 골라 남기고 스테이징합니다. 고친 것이 있으면 고친 대로 남깁니다"/>
|
||||
<Button Content="파일 지우기" MinWidth="110" Margin="8,0,0,0"
|
||||
Command="{Binding DeleteFileCommand}"
|
||||
ToolTip="지운 쪽을 따라 파일을 지우고 스테이징합니다"/>
|
||||
</StackPanel>
|
||||
<TextBlock Text="{Binding DeletionConflictText}" TextWrapping="Wrap" VerticalAlignment="Center"
|
||||
FontWeight="Bold" Foreground="{DynamicResource Ubisam.Brush.Danger}"/>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
|
||||
<Border Padding="10,3" Margin="0,0,0,5"
|
||||
Background="{DynamicResource Ubisam.Brush.Input.Background}"
|
||||
BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}" BorderThickness="1">
|
||||
<WrapPanel Orientation="Horizontal">
|
||||
@ -687,6 +711,7 @@
|
||||
|
||||
</WrapPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<!--
|
||||
이진 파일을 열었을 때. 코드 패널 위를 덮는다.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user