6차 A-5: Accept Both 는 글로 이어 붙이고 누르기 전에 묻는다
양쪽이 같은 이름으로 새로 만든 파일에 Accept Both 를 누르면 묻지 않고 두 blob 을 바이트째 이어 붙였다. 결과 파일 한가운데에 BOM 이 하나 더 들어갔고 같은 class 가 두 번 나와 빌드가 안 됐다. - BothJoin: 두 벌을 각자 제 인코딩으로 글로 풀어 이어 붙인 뒤 내 쪽 형식(인코딩, BOM, 줄끝)으로 다시 쓴다. 규칙은 A-1 의 TextFile.Encode - ResolveConflict(Both): 바이트 이어 붙이기 대신 BothJoin - Git 탭 Accept Both: 먼저 묻는다(무엇이 일어나는지, 줄 단위로 둘 다 넣으려면 3분할에서). 단추 도움말 Both 는 없애지 않았다. 이어 붙이는 게 맞는 파일도 있다. 화면 확인 14건 통과(묻기, 아니오면 그대로, 예면 BOM 1개와 CRLF 한 가지, 보통 충돌도 같음). EncTest 10건 추가. 21벌 785건 통과 (새로 빌드한 실행 파일). 경고 0, 오류 0. A 묶음 끝. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
27798fa145
commit
04b8ef8a60
@ -267,3 +267,53 @@ A-2 에서 "건너뛴 목록 불러오기가 목록을 비우지 않게" 막았
|
|||||||
다시 만들려는데 생성기가 `다른 프로세스가 사용 중` 으로 멈추고 저장소가 **빈 폴더**로 남았다. 앱을 끄는 명령과 생성기를
|
다시 만들려는데 생성기가 `다른 프로세스가 사용 중` 으로 멈추고 저장소가 **빈 폴더**로 남았다. 앱을 끄는 명령과 생성기를
|
||||||
동시에 돌렸고, 그 뒤에도 셸이 이 폴더를 작업 폴더로 잡고 있어 **뿌리 폴더를 지울 수 없었다.**
|
동시에 돌렸고, 그 뒤에도 셸이 이 폴더를 작업 폴더로 잡고 있어 **뿌리 폴더를 지울 수 없었다.**
|
||||||
생성기가 뿌리는 두고 **안만 비우게** 고쳤다. 제품 코드와는 상관없다.
|
생성기가 뿌리는 두고 **안만 비우게** 고쳤다. 제품 코드와는 상관없다.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## A-5. 양쪽 새 파일에 Accept Both 를 누르면 빌드가 깨진다 (2026-09-14, 완료)
|
||||||
|
|
||||||
|
### 고치기 전에 다시 났다
|
||||||
|
|
||||||
|
새로 만든 저장소에서 Git 탭 충돌 목록의 `Ui/Views/NewPanel.cs`(양쪽이 같은 이름으로 새로 만듦, `AA`)에 Accept Both 를 눌렀다.
|
||||||
|
|
||||||
|
| 본 것 | 값 |
|
||||||
|
|---|---|
|
||||||
|
| 누르기 전에 묻는가 | **안 묻고 바로 이어 붙였다** |
|
||||||
|
| 결과 파일의 BOM | **2개** — 맨 앞 하나, 가운데 하나 |
|
||||||
|
| `class NewPanel` | **2번** |
|
||||||
|
|
||||||
|
### 무엇이 원인이었나
|
||||||
|
|
||||||
|
`GitService.ResolveConflict` 의 Both 갈래가 두 blob 을 **바이트째** 이어 붙였다. 양쪽이 BOM 으로 시작하면 뒤쪽 BOM 이
|
||||||
|
결과 한가운데에 그대로 남는다. 줄끝이 다르면 한 파일 안에 CRLF 와 LF 가 섞인다.
|
||||||
|
그리고 파일 단위 "둘 다" 는 같은 이름의 형식이 두 번 나오는 결과를 거의 늘 만드는데, 누르기 전에 그걸 알릴 자리가 없었다.
|
||||||
|
|
||||||
|
### 고친 것
|
||||||
|
|
||||||
|
| 자리 | 무엇 |
|
||||||
|
|---|---|
|
||||||
|
| `Services/BothJoin.cs` (새 파일) | 두 벌을 각자 제 인코딩으로 **글로 풀어** 이어 붙인 뒤, **내 쪽 파일의 형식**(인코딩 · BOM · 줄끝)으로 다시 쓴다. 규칙은 A-1 의 `TextFile.Encode` 를 그대로 쓴다. 한쪽이 비면 다른 쪽을 손대지 않고 돌려준다 |
|
||||||
|
| `GitService.ResolveConflict` (Both) | 바이트 이어 붙이기 대신 `BothJoin.Join` |
|
||||||
|
| Git 탭 Accept Both | 누르면 먼저 묻는다 — "내 것 다음에 상대 것을 한 파일로 이어 붙입니다… 같은 형식이 두 번 나와 빌드가 안 될 수 있습니다. 줄마다 골라 둘 다 넣으려면 파일 비교 탭의 3분할에서 하세요." 단추에 같은 뜻의 도움말 |
|
||||||
|
|
||||||
|
**Both 를 없애지는 않았다.** 설정 파일이나 목록 파일처럼 이어 붙이는 게 맞는 경우가 있다. 대신 누르기 전에 결과를 알게 했다.
|
||||||
|
|
||||||
|
### 확인한 것 (새로 만든 저장소, 충돌 23개에서 시작)
|
||||||
|
|
||||||
|
| 확인 | 결과 |
|
||||||
|
|---|---|
|
||||||
|
| 양쪽 새 파일에 Accept Both | 먼저 묻는다(단추: 예 · 아니오) |
|
||||||
|
| [아니오] | git 은 여전히 `AA`, 파일 바이트 그대로, 충돌 23 |
|
||||||
|
| 다시 눌러 [예] | BOM **1개(맨 앞)**, CR 4 = LF 4, 내 것 다음에 상대 것, 가운데 U+FEFF 없음, git `M`, 충돌 22 |
|
||||||
|
| 양쪽이 고친 보통 충돌(`Io/Unit20.cs`)에 Accept Both | BOM 1개, CR 362 = LF 362, 양쪽 줄이 다 들어감, 충돌 21 |
|
||||||
|
|
||||||
|
화면 확인 **14건 통과**. `EncTest` 에 이어 붙이기 검사 10건을 넣었다(38 → 48). CP949 인 내 파일과 UTF-8 BOM 인 상대 파일을 이어도
|
||||||
|
CP949 로 쓰이고 양쪽 한글이 다 읽히는지까지 본다.
|
||||||
|
검사 **21벌 785건 통과**(전부 새로 빌드한 실행 파일) · 빌드 경고 0 · 오류 0.
|
||||||
|
|
||||||
|
### A 묶음을 마치며
|
||||||
|
|
||||||
|
A 의 다섯 가지(줄끝 · 반쯤 된 실패 · 상대 칸 · 지운 파일 · Both)가 모두 끝났다. 끝까지 풀어 커밋까지 가는 확인은 D-1 에서
|
||||||
|
처음부터 다시 한다. A 를 하다 B-1 으로 넘긴 것 - **새로고침 도중 게이트를 쥔 채 목록 다시 세우기가 불려 건너뛰는 뿌리** -
|
||||||
|
는 아직 그대로다(지금은 목록이 비지 않고 옛 목록이 남는다).
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
| 날짜 | 항목 | 커밋 |
|
| 날짜 | 항목 | 커밋 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
| 2026-09-14 | **6차 A-5** — 파일 단위 Accept Both 가 두 벌을 글로 풀어 이어 붙이고 내 쪽 형식으로 쓴다(가운데 BOM 사라짐). 누르기 전에 무엇이 일어나는지 묻는다. A 묶음 끝 | 아래 |
|
||||||
| 2026-09-14 | **6차 A-4** — 한쪽이 지운 충돌(UD·DU)은 줄 단위 3분할 대신 남은 파일을 두 칸으로 보이고 [파일 남기기] · [파일 지우기] 로 푼다. 거꾸로 세던 범례(Theirs only 89)가 없어짐 | 아래 |
|
| 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-3** — 병합 중 충돌 파일을 열면 콤보를 안 건드려도 네 칸이 뜬다. REMOTE 제목에 상대 브랜치 이름(MERGE_HEAD → 병합 메시지 → 짧은 해시) | 아래 |
|
||||||
| 2026-09-14 | **6차 A-1** — 충돌 해결 저장이 내 쪽(ours) 파일의 줄끝·인코딩을 따른다(3,363줄 파일 +3,367/−3,363 → +8/−4). 검사 모음이 옛 실행 파일로 돌던 것을 찾아 세 벌을 고침 | 아래 |
|
| 2026-09-14 | **6차 A-1** — 충돌 해결 저장이 내 쪽(ours) 파일의 줄끝·인코딩을 따른다(3,363줄 파일 +3,367/−3,363 → +8/−4). 검사 모음이 옛 실행 파일로 돌던 것을 찾아 세 벌을 고침 | 아래 |
|
||||||
|
|||||||
@ -97,7 +97,7 @@
|
|||||||
| **완료** | A-2 Save & Commit 이 "실패" 라는데 파일은 이미 써지고 스테이징됐다 — [해결로 저장] 을 따로 둠 |
|
| **완료** | A-2 Save & Commit 이 "실패" 라는데 파일은 이미 써지고 스테이징됐다 — [해결로 저장] 을 따로 둠 |
|
||||||
| **완료** | A-3 충돌 파일을 고르면 상대(Theirs) 칸이 안 나온다 — 콤보를 안 건드려도 네 칸, REMOTE 제목에 상대 브랜치 |
|
| **완료** | A-3 충돌 파일을 고르면 상대(Theirs) 칸이 안 나온다 — 콤보를 안 건드려도 네 칸, REMOTE 제목에 상대 브랜치 |
|
||||||
| **완료** | A-4 상대가 지운 파일을 3분할이 다룰 줄 모른다 — 남은 파일을 두 칸으로, [파일 남기기] · [파일 지우기] 띠 |
|
| **완료** | A-4 상대가 지운 파일을 3분할이 다룰 줄 모른다 — 남은 파일을 두 칸으로, [파일 남기기] · [파일 지우기] 띠 |
|
||||||
| 대기 | A-5 양쪽 새 파일에 Accept Both 를 누르면 빌드가 깨진다 |
|
| **완료** | A-5 양쪽 새 파일에 Accept Both 를 누르면 빌드가 깨진다 — 누르기 전에 묻고, 글로 이어 붙여 BOM 한 번 · 줄끝 한 가지 |
|
||||||
|
|
||||||
### B. 헷갈리거나 막히는 것
|
### B. 헷갈리거나 막히는 것
|
||||||
|
|
||||||
|
|||||||
37
src/MergeHub/Services/BothJoin.cs
Normal file
37
src/MergeHub/Services/BothJoin.cs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace MergeHub.Services
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 충돌 파일 두 벌을 "둘 다" 로 이어 붙인다(파일 단위 Accept Both).
|
||||||
|
///
|
||||||
|
/// 예전에는 바이트째 이어 붙였다. 그러면 양쪽이 다 BOM 으로 시작할 때 <b>결과 파일 한가운데에 BOM 이 하나 더</b>
|
||||||
|
/// 들어가고(컴파일러는 그 자리를 이상한 글자로 본다), 양쪽 줄끝이 다르면 한 파일 안에 CRLF 와 LF 가 섞였다(6차 A-5).
|
||||||
|
///
|
||||||
|
/// 이제는 두 벌을 각자 제 인코딩으로 <b>글로 풀어</b> 이어 붙인 뒤, <b>내 쪽 파일의 형식</b>(인코딩 · BOM · 줄끝)으로
|
||||||
|
/// 다시 쓴다. 규칙은 저장과 같은 <see cref="TextFile.Encode"/> 를 쓴다 - 형식 판단이 두 군데로 갈리면 또 어긋난다.
|
||||||
|
///
|
||||||
|
/// 한쪽이 비어 있으면 다른 쪽을 <b>손대지 않고</b> 그대로 돌려준다(이어 붙일 것이 없다).
|
||||||
|
/// </summary>
|
||||||
|
public static class BothJoin
|
||||||
|
{
|
||||||
|
public static byte[] Join(byte[]? ours, byte[]? theirs)
|
||||||
|
{
|
||||||
|
ours ??= Array.Empty<byte>();
|
||||||
|
theirs ??= Array.Empty<byte>();
|
||||||
|
|
||||||
|
if (ours.Length == 0) return theirs;
|
||||||
|
if (theirs.Length == 0) return ours;
|
||||||
|
|
||||||
|
var text = Decode(ours).Replace("\r\n", "\n");
|
||||||
|
if (!text.EndsWith("\n", StringComparison.Ordinal)) text += "\n";
|
||||||
|
text += Decode(theirs).Replace("\r\n", "\n");
|
||||||
|
|
||||||
|
return TextFile.Encode(text, encodingName: null, newLineName: null, reference: ours);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>제 인코딩으로 푼다. 글자로 딸려 온 BOM(U+FEFF)은 뗀다 - 다시 쓸 때 맨 앞에 한 번만 붙는다.</summary>
|
||||||
|
private static string Decode(byte[] bytes) =>
|
||||||
|
TextFile.DetectEncoding(bytes).GetString(bytes).TrimStart('\uFEFF');
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -880,20 +880,15 @@ namespace MergeHub.Services
|
|||||||
|
|
||||||
if (resolution == ConflictResolution.Both)
|
if (resolution == ConflictResolution.Both)
|
||||||
{
|
{
|
||||||
// 인코딩 변환 없이 바이트 단위로 이어붙인다 - CP949 등 원본 인코딩이 그대로 보존된다.
|
// 양쪽 파일을 읽는다. 한쪽이 없으면 빈 것으로 본다.
|
||||||
var oursBlob = LookupBlob(conflict.Ours);
|
var oursBlob = LookupBlob(conflict.Ours);
|
||||||
var theirsBlob = LookupBlob(conflict.Theirs);
|
var theirsBlob = LookupBlob(conflict.Theirs);
|
||||||
var ours = oursBlob is null ? Array.Empty<byte>() : ReadBlobBytes(oursBlob, normalized);
|
var ours = oursBlob is null ? Array.Empty<byte>() : ReadBlobBytes(oursBlob, normalized);
|
||||||
var theirs = theirsBlob is null ? Array.Empty<byte>() : ReadBlobBytes(theirsBlob, normalized);
|
var theirs = theirsBlob is null ? Array.Empty<byte>() : ReadBlobBytes(theirsBlob, normalized);
|
||||||
|
|
||||||
using var output = File.Create(fullPath);
|
// 바이트째 이어 붙이면 결과 한가운데에 BOM 이 하나 더 들어가고 줄끝이 섞였다(6차 A-5).
|
||||||
output.Write(ours, 0, ours.Length);
|
// 두 벌을 글로 풀어 이어 붙인 뒤 내 쪽 형식(인코딩 · BOM · 줄끝)으로 다시 쓴다.
|
||||||
if (ours.Length > 0 && ours[ours.Length - 1] != Lf)
|
File.WriteAllBytes(fullPath, BothJoin.Join(ours, theirs));
|
||||||
{
|
|
||||||
var newLine = ContainsCrLf(ours) ? new[] { Cr, Lf } : new[] { Lf };
|
|
||||||
output.Write(newLine, 0, newLine.Length);
|
|
||||||
}
|
|
||||||
output.Write(theirs, 0, theirs.Length);
|
|
||||||
}
|
}
|
||||||
else if (chosen is null)
|
else if (chosen is null)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -272,7 +272,18 @@ namespace MergeHub.ViewModels
|
|||||||
private Task AcceptTheirsAsync() => ResolveAsync(ConflictResolution.Theirs);
|
private Task AcceptTheirsAsync() => ResolveAsync(ConflictResolution.Theirs);
|
||||||
|
|
||||||
[RelayCommand(CanExecute = nameof(HasConflict))]
|
[RelayCommand(CanExecute = nameof(HasConflict))]
|
||||||
private Task AcceptBothAsync() => ResolveAsync(ConflictResolution.Both);
|
private Task AcceptBothAsync()
|
||||||
|
{
|
||||||
|
// 파일 단위 "둘 다" 는 파일 두 벌을 통째로 이어 붙인다. 양쪽이 같은 이름으로 새로 만든 파일이면
|
||||||
|
// 같은 형식이 두 번 나와 빌드가 안 된다(5차 C-1 에서 그랬다). 누르기 전에 무엇이 일어나는지 알린다(6차 A-5).
|
||||||
|
var path = SelectedConflict?.Path ?? string.Empty;
|
||||||
|
var go = MessageUtil.ShowYesNo(
|
||||||
|
"파일 두 벌을 이어 붙입니다",
|
||||||
|
$"{path}\n\n내 것 다음에 상대 것을 한 파일로 이어 붙입니다. 내 파일의 인코딩과 줄끝으로 씁니다.\n\n" +
|
||||||
|
"양쪽이 같은 이름으로 새로 만든 파일이면 같은 형식이 두 번 나와 빌드가 안 될 수 있습니다.\n" +
|
||||||
|
"줄마다 골라 둘 다 넣으려면 파일 비교 탭의 3분할에서 하세요.\n\n이어 붙일까요?");
|
||||||
|
return go ? ResolveAsync(ConflictResolution.Both) : Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
private Task ResolveAsync(ConflictResolution resolution) =>
|
private Task ResolveAsync(ConflictResolution resolution) =>
|
||||||
_workspace.RunAndRefreshAsync($"충돌 해결({resolution})",
|
_workspace.RunAndRefreshAsync($"충돌 해결({resolution})",
|
||||||
|
|||||||
@ -278,6 +278,7 @@
|
|||||||
Background="{DynamicResource Ubisam.Brush.Accent.Fill}"
|
Background="{DynamicResource Ubisam.Brush.Accent.Fill}"
|
||||||
Foreground="{DynamicResource Ubisam.Brush.Accent.OnFill}"/>
|
Foreground="{DynamicResource Ubisam.Brush.Accent.OnFill}"/>
|
||||||
<Button Content="Accept Both" Command="{Binding AcceptBothCommand}" MinWidth="100" Style="{StaticResource FilledButton}"
|
<Button Content="Accept Both" Command="{Binding AcceptBothCommand}" MinWidth="100" Style="{StaticResource FilledButton}"
|
||||||
|
ToolTip="내 파일 다음에 상대 파일을 통째로 이어 붙입니다. 누르면 먼저 묻습니다"
|
||||||
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"/>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user