11차 F-8: 저장한 뒤에도 되돌리기 · 푼 파일을 다시 충돌로
- 저장할 것 있음을 되돌리기 목록이 아니라 "마지막 저장과 다른가" 로(SaveBaseline). 저장 · 저장 후 커밋은 목록을 둠 - 줄을 다시 세울 때(커밋 뒤 새 HEAD · 규칙 바꾸기 · 친 뒤 재정렬) 이력을 글 단위로 옮겨 지킴, 친 것도 한 걸음 - 전체 되돌리기는 파일을 연 시점, 저장했으면 화면만 - Git 탭 "이번 병합에서 푼 파일" + [다시 충돌로] (이 단추만 git checkout -m, 해결 기록에서 뺌) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
3d0022a598
commit
a9cbb50884
@ -165,6 +165,7 @@ A-1 에서 값으로 확인한 것 — 지운 파일은 "삭제되어 건너뜀
|
|||||||
- F-4: 매뉴얼 파일 비교 화면 — [ACTIONS 감추기] · F9, 아래 탭 줄 [탭 접기] · 띠 · F11(플랫폼), 단축키 표에 F9 · F11. 숫자 23벌 1,414건 · 플랫폼 31건.
|
- F-4: 매뉴얼 파일 비교 화면 — [ACTIONS 감추기] · F9, 아래 탭 줄 [탭 접기] · 띠 · F11(플랫폼), 단축키 표에 F9 · F11. 숫자 23벌 1,414건 · 플랫폼 31건.
|
||||||
- F-5: 매뉴얼 설정 표 "보기 > 코드 글자 크기", 파일 비교 화면 Ctrl+휠 · Ctrl++/- · Ctrl+0 · 상태 줄 `글자 N`, 단축키 표. 숫자 24벌 1,538건.
|
- F-5: 매뉴얼 설정 표 "보기 > 코드 글자 크기", 파일 비교 화면 Ctrl+휠 · Ctrl++/- · Ctrl+0 · 상태 줄 `글자 N`, 단축키 표. 숫자 24벌 1,538건.
|
||||||
- F-6: 줄 간격을 좁혀 1920 × 1080 두 칸 40 → 50줄(매뉴얼의 화면 설명 · 그림이 있으면 새로). 숫자 24벌 1,572건.
|
- F-6: 줄 간격을 좁혀 1920 × 1080 두 칸 40 → 50줄(매뉴얼의 화면 설명 · 그림이 있으면 새로). 숫자 24벌 1,572건.
|
||||||
|
- F-8: 매뉴얼 되돌리기 설명 — 저장 · 저장 후 커밋 뒤에도 Undo/Redo(커밋은 그대로, 화면만), 편집기에 친 것 · 규칙 바꾸기도 이어짐, 요약 `· 저장됨`, [전체 되돌리기] 는 파일을 연 시점(저장했으면 화면만). Git 탭 충돌 화면 "이번 병합에서 푼 파일" + [다시 충돌로](git.exe 필요 — 알아 둘 한계에도). 숫자 24벌 1,593건.
|
||||||
|
|
||||||
## B-1. 배포 묶음을 다시 만들고 꼭 필요한 파일을 가림 (2026-09-22, 완료)
|
## B-1. 배포 묶음을 다시 만들고 꼭 필요한 파일을 가림 (2026-09-22, 완료)
|
||||||
|
|
||||||
@ -800,6 +801,48 @@ libgit2 의 다듬기(`PrettifyMessages`)를 켜면 **손대지 않은 뒤 커
|
|||||||
- ~~플랫폼 배포~~ — 사용자 "배포해줘" 뒤 `publish-platform.ps1`(경고 0 · 오류 0, Core · Shell · README 셋 바뀜). MergeHub Debug 다시 빌드 → 두 파일 해시가 배포본과 같음, 배포본으로 같은 재현 → 창 360~2560 · 종료 2560 · Accept 2526 · 넘침 0.
|
- ~~플랫폼 배포~~ — 사용자 "배포해줘" 뒤 `publish-platform.ps1`(경고 0 · 오류 0, Core · Shell · README 셋 바뀜). MergeHub Debug 다시 빌드 → 두 파일 해시가 배포본과 같음, 배포본으로 같은 재현 → 창 360~2560 · 종료 2560 · Accept 2526 · 넘침 0.
|
||||||
- 배율이 **서로 다른** 모니터 사이를 옮길 때는 이 PC(두 모니터 모두 150%)에서 재현할 수 없다. 대형 모니터에서 같은 증상이 또 나면 그 모니터의 해상도 · 배율 · 창 모드였는지를 알려 받는다(창 자리 파일 `ShellWindowPlacement.json` 한 벌이면 그대로 재현할 수 있다).
|
- 배율이 **서로 다른** 모니터 사이를 옮길 때는 이 PC(두 모니터 모두 150%)에서 재현할 수 없다. 대형 모니터에서 같은 증상이 또 나면 그 모니터의 해상도 · 배율 · 창 모드였는지를 알려 받는다(창 자리 파일 `ShellWindowPlacement.json` 한 벌이면 그대로 재현할 수 있다).
|
||||||
|
|
||||||
|
## F-8. 저장한 뒤에도 되돌리기 · 푼 파일을 다시 충돌로 (2026-09-22, 완료)
|
||||||
|
|
||||||
|
사용자 결정: "되돌리면 다시 충돌로", "커밋은 두고 화면만 되돌림", [전체 되돌리기] 는 "파일을 연 시점", 병합 중 [Save] 는 지금 흐름 그대로 두고 **Git 탭 충돌 목록에 [다시 충돌로]**("1번으로 — UNDO, REDO 기능이 중요해"). [다시 충돌로] 는 LibGit2Sharp 에 길이 없어 **이 단추만 git.exe 를 부름**(사용자 결정).
|
||||||
|
|
||||||
|
### 고치기 전 (재현 `f8-probe.ps1` · `f8-git.ps1`)
|
||||||
|
|
||||||
|
| 경우 | 고치기 전 |
|
||||||
|
|---|---|
|
||||||
|
| 두 파일 비교: 반영 → [저장] | Undo · Redo **꺼짐** — 저장하는 네 길(저장 · 저장 후 커밋 · 해결로 저장 · 파일 바꿀 때 "저장하고 계속")이 모두 목록을 비웠다 |
|
||||||
|
| Git: 반영 → [Save & Commit] | Undo 꺼짐 — 저장 때 비우고, 이어지는 새로고침이 **새 HEAD 기준으로 줄을 다시 세우며** 또 비움(9차 A-2 로 BASE 가 새 HEAD 를 따라감) |
|
||||||
|
| 편집기에 글자를 치거나 비교 규칙(Aa · 공백 · 필터)을 바꿈 | 재정렬이 목록을 통째로 비워 그 전 Accept 들을 Undo 할 수 없었다 |
|
||||||
|
| "저장할 것 있음" | 되돌리기 목록이 비었는지로 판단 — 저장 뒤 목록을 두면 저장 단추가 켜진 채로 남으므로 비울 수밖에 없었다 |
|
||||||
|
|
||||||
|
### 고친 것
|
||||||
|
|
||||||
|
- `SaveBaseline`(새) — 편집 칸의 두 기준 글: **파일을 연 시점**(전체 되돌리기가 가는 곳 · 직접 고친 줄을 가리는 기준)과 **마지막 저장**. "저장할 것 있음" = 지금 글 ≠ 마지막 저장(또는 스타일 맞춤 대기). 저장 뒤 Undo 하면 "있음", Redo 로 저장 지점에 오면 "없음".
|
||||||
|
- 저장 · 저장 후 커밋 · "저장하고 계속" 은 목록을 **두고** 저장 지점만 옮긴다(`MarkSaved`). 병합 중 [Save](해결로 저장)는 다음 충돌로 넘어가므로 예전처럼 비움.
|
||||||
|
- **글 단위 이력** — 줄 단위 목록은 줄 객체를 가리켜 줄을 다시 세우면 못 쓴다. 다시 세우기 **전에** 각 지점의 전문을 적어 옮긴다(`FlattenHistory` — 한 묶음이 한 걸음, 덩어리 · 메서드 반영은 11차 F-2 대로 한 번). 쓰는 곳: 커밋 뒤 같은 파일 · 같은 글을 다시 읽을 때, 비교 규칙을 바꿀 때, 편집기에서 친 뒤 재정렬할 때. 글 단위 걸음은 줄을 다시 세워 되돌린다. 걸음마다 파일 전문 한 벌이라 한쪽에 300걸음까지 둔다.
|
||||||
|
- **편집기에서 친 것도 한 걸음** — 치는 동안 화면을 바로 보이려고 줄 객체를 먼저 고치는데(되돌리기 목록 밖) 그 줄의 원래 글을 적어 두었다가 재정렬 때 한 걸음으로 올린다.
|
||||||
|
- 커밋 뒤 새로고침은 한 번에 **두 번 읽고 그 사이 화면을 잠깐 비운다**(고른 커밋 바뀜 · 목록 다시 세우기). 한 번 쓰고 버리는 표시로 했더니 두 번째가 비웠다 → 조건만 본다(같은 파일 · 두 칸 · 충돌 아님 · 읽은 글이 화면 글과 같음 — 파일의 CRLF · 끝 줄바꿈은 같은 글로 봄). 화면을 비울 때는 이력 · 두 기준을 맡겨 두고, 같은 파일 · 같은 글을 다시 실으면 되찾는다.
|
||||||
|
- [전체 되돌리기] — 저장한 뒤에도 켜짐(연 시점과 다르면), 파일을 연 시점으로. 이미 저장했으면 확인 창이 "저장한 파일 · 커밋은 그대로, 화면만 돌아감 — 넣으려면 다시 저장, 되돌리기 목록은 비워짐" 이라고 알린다.
|
||||||
|
- 요약 줄 — 저장한 지점이면 `· 저장됨` 을 붙인다(`남은 차이 1줄 · 편집 1건 · 되돌리기 0건 · 저장됨`).
|
||||||
|
- **Git 탭 충돌 목록 아래 "이번 병합에서 푼 파일 N개" + [다시 충돌로]** — 병합 중이고 푼 파일이 있을 때만 보인다. 목록 = 처음 충돌 목록(`.git/MERGEHUB_CONFLICTS`) ∩ git 의 해결 기록(REUC) − 지금 충돌(`MergeConflictRecord.Resolved` — REUC 만 보면 저절로 병합된 파일도 들어온다, 6차 C-1). 누르면 먼저 묻고(저장해 둔 해결 내용이 충돌 표시 글로 바뀜), `git checkout -m -- 파일` 로 세 벌 · 충돌 표시를 되살린 뒤 저장소를 다시 열고(열어 둔 객체는 인덱스를 다시 안 읽어 충돌이 0 으로 보였다 — `UnresolveProbe` 로 확인), 해결 기록(8차 F-1)에서 그 파일을 뺀다(`MergeResolutionLog.Forget`). git.exe 가 없으면 "Git 이 설치되어 PATH 에 있어야" 로 알린다.
|
||||||
|
|
||||||
|
### 확인
|
||||||
|
|
||||||
|
| 경우 | 결과 |
|
||||||
|
|---|---|
|
||||||
|
| 두 파일: 반영 → 저장 → Undo → Redo (`f8-probe.ps1`) | 저장 뒤 Undo 켜짐 · 저장 꺼짐 · `저장됨` → Undo: 저장 켜짐 · 파일은 그대로 → Redo: 저장 꺼짐 · `저장됨`. 전체 되돌리기 켜짐 |
|
||||||
|
| Git: 반영 → Save & Commit → Undo → Redo (`f8-git.ps1`) | 커밋됨(워킹 변경 0) · 요약 `차이 없음 · 편집 1건 · 되돌리기 0건 · 저장됨` · Undo 켜짐 → Undo: 새 HEAD 대비 `남은 차이 1줄` · 커밋 · 파일 그대로 → Redo: `차이 없음 · 저장됨` |
|
||||||
|
| 반영 → 편집기에 침 → 규칙(Aa) 바꿈 → Undo ×3 → Redo ×3 (`f8-typing.ps1`) | 친 것이 한 걸음(편집 2건), 규칙을 바꿔도 이력 그대로, Undo 가 친 것 → Accept 차례로, Redo 가 거꾸로 |
|
||||||
|
| 병합: Accept Theirs 로 푼 파일 · 3분할 [Save] 로 푼 파일을 [다시 충돌로] (`f8-reopen.ps1`, C1Repo) | 푼 파일 목록에 나옴 → 확인 창 → git `UU` · 충돌 표시 줄 되살아남 · 충돌 23 → 24 · 충돌 목록에 다시 · 푼 파일 목록에서 빠짐 · 해결 기록에서 빠짐. 병합 직후 머지 주석만 달라 저절로 푼 2개도 목록에 나옴(MergeHub 가 푼 것이라 맞음) |
|
||||||
|
| 병합 한 길 회귀(`e11-e1-merge.ps1`) | 충돌 24 · 풀기 26 · 누른 Take 18 = 로그 18 · 병합 커밋 부모 둘 · 줄끝/BOM/인코딩 다른 파일 0 · 충돌 표식 0 · 305초 |
|
||||||
|
|
||||||
|
- 하네스 MergeSaveTest **+21건(296 → 317)** — 두 기준 글(연 직후 · 고침 · 저장 · 저장 뒤 Undo/Redo · 전체 되돌리기 대상 · 스타일 대기 · 다시 열기 · null), 줄바꿈만 다른 글, 맡겨 둔 기준 되찾기, 푼 파일 목록(지금 충돌 · 저절로 병합 · 해결 기록 없는 파일 · 겹친 이름), 해결 기록에서 한 파일 빼기. 먼저 넣고 시작. Debug 빌드 경고 0 · 오류 0.
|
||||||
|
- 시험 도구: `d7lib` 의 머지 주석 줄 패턴이 F-1 뒤 주석 모양(날짜 뒤 출처)을 못 알아봐 "기준 대조 → 다름" 이 F-2 부터 나고 있었다 — 패턴을 고침(앱 문제 아님).
|
||||||
|
|
||||||
|
### 남은 것
|
||||||
|
|
||||||
|
- 세 칸(3-Way) 화면에서 규칙을 바꾸거나 치면 이력은 지키지만, 커밋 뒤 다시 읽기처럼 **파일에서 새로 읽는** 경우는 두 칸일 때만 지킨다(충돌 · 3-Way 는 저장이 곧 다음 충돌로 넘어감).
|
||||||
|
- [다시 충돌로] 는 MergeHub 로 시작한 병합만(처음 충돌 목록이 있어야). rebase · cherry-pick · revert 중에는 보이지 않는다.
|
||||||
|
|
||||||
## F-12. C# 코드 스타일 맞춤의 기본값을 AURORA 로 (2026-09-18, 완료)
|
## F-12. C# 코드 스타일 맞춤의 기본값을 AURORA 로 (2026-09-18, 완료)
|
||||||
|
|
||||||
사용자: "기본값은 원본형태를 따르고 C#인 경우에 AURORA 스타일로 하자" → 물어서 정한 것은 **제품 기본값 AURORA · C# 파일만 · 저장 동작은 지금 그대로**(켜져 있으면 저장도 그 스타일로 쓴다).
|
사용자: "기본값은 원본형태를 따르고 C#인 경우에 AURORA 스타일로 하자" → 물어서 정한 것은 **제품 기본값 AURORA · C# 파일만 · 저장 동작은 지금 그대로**(켜져 있으면 저장도 그 스타일로 쓴다).
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
| 날짜 | 항목 | 커밋 |
|
| 날짜 | 항목 | 커밋 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
| 2026-09-22 | **11차 F-8** — 저장한 뒤에도 Undo/Redo: 저장하는 길마다 되돌리기 목록을 비우던 것을 두고, "저장할 것 있음" 을 목록이 아니라 "지금 글이 마지막 저장과 다른가" 로(`SaveBaseline`). 커밋 뒤 새 HEAD 로 다시 읽기 · 비교 규칙 바꾸기 · 편집기에 친 뒤 재정렬처럼 줄을 다시 세울 때 이력을 글 단위로 옮겨 지킴(친 것도 한 걸음, 300걸음까지). 전체 되돌리기는 파일을 연 시점. 병합 중 푼 파일은 Git 탭 [다시 충돌로] 로 되돌림 — LibGit2Sharp 에 길이 없어 이 단추만 `git checkout -m`(사용자 결정), 해결 기록에서도 뺌 | 아래 |
|
||||||
| 2026-09-22 | **11차 F-7 (추가 요청)** — "대형 모니터에서 오른쪽이 잘려 종료 · ACTIONS 가 안 보임": 창이 모니터 밖으로 나간 것. 켤 때 창 모드로 기억한 자리를 모니터에 조금만 걸쳐도 그대로 놓았다 — 해상도 · 모니터 배치가 바뀐 뒤 오른쪽으로 넘침(재현 840px). 플랫폼이 가장 많이 겹치는 모니터 안으로 밀게 고침(플랫폼 changelog, 사용자 확인 뒤 배포 09-22). 가장 좁은 창 × 화면 배율 80~133% 에서 안쪽 화면은 원래 넘치지 않음 | 아래 |
|
| 2026-09-22 | **11차 F-7 (추가 요청)** — "대형 모니터에서 오른쪽이 잘려 종료 · ACTIONS 가 안 보임": 창이 모니터 밖으로 나간 것. 켤 때 창 모드로 기억한 자리를 모니터에 조금만 걸쳐도 그대로 놓았다 — 해상도 · 모니터 배치가 바뀐 뒤 오른쪽으로 넘침(재현 840px). 플랫폼이 가장 많이 겹치는 모니터 안으로 밀게 고침(플랫폼 changelog, 사용자 확인 뒤 배포 09-22). 가장 좁은 창 × 화면 배율 80~133% 에서 안쪽 화면은 원래 넘치지 않음 | 아래 |
|
||||||
| 2026-09-22 | **11차 F-6 (추가 요청)** — 코드 칸 줄 간격을 좁힘: 줄 높이 글자의 1.67배 → 1.33배(12 → 16, 사용자 결정 "선택 사항은 만들지 마" — 켜고 끄는 단추 · 설정 없음). 1920 × 1080 두 칸 40 → 50줄, 네 칸 위 17 → 22 · 아래 18 → 23줄. 북마크 표식 높이도 줄에 맞춤. 방법별 줄 수(탭 줄 접기 +2 · 글자 10 +7 · 칸 제목 한 줄 · 범례 감추기 +1 안팎)는 기록에 | 아래 |
|
| 2026-09-22 | **11차 F-6 (추가 요청)** — 코드 칸 줄 간격을 좁힘: 줄 높이 글자의 1.67배 → 1.33배(12 → 16, 사용자 결정 "선택 사항은 만들지 마" — 켜고 끄는 단추 · 설정 없음). 1920 × 1080 두 칸 40 → 50줄, 네 칸 위 17 → 22 · 아래 18 → 23줄. 북마크 표식 높이도 줄에 맞춤. 방법별 줄 수(탭 줄 접기 +2 · 글자 10 +7 · 칸 제목 한 줄 · 범례 감추기 +1 안팎)는 기록에 | 아래 |
|
||||||
| 2026-09-22 | **11차 F-5 뒤 (사용자 "응 고쳐줘")** — 켜자마자(또는 탭을 눌러 돌아오면) 키보드 포커스가 없거나 탭 줄에 있어, 코드 칸을 한 번 누르기 전까지 **모든 단축키**(Ctrl+S · F9 · Ctrl++ …)가 듣지 않았다. 파일 비교 화면이 뜰 때 · 돌아올 때 · 창이 활성이 될 때 포커스가 비었거나 화면 밖이면 편집기로 준다(화면 안 · 다른 창의 포커스는 그대로). E-1 병합 길 회귀 없음 | 아래 |
|
| 2026-09-22 | **11차 F-5 뒤 (사용자 "응 고쳐줘")** — 켜자마자(또는 탭을 눌러 돌아오면) 키보드 포커스가 없거나 탭 줄에 있어, 코드 칸을 한 번 누르기 전까지 **모든 단축키**(Ctrl+S · F9 · Ctrl++ …)가 듣지 않았다. 파일 비교 화면이 뜰 때 · 돌아올 때 · 창이 활성이 될 때 포커스가 비었거나 화면 밖이면 편집기로 준다(화면 안 · 다른 창의 포커스는 그대로). E-1 병합 길 회귀 없음 | 아래 |
|
||||||
|
|||||||
@ -377,7 +377,7 @@
|
|||||||
| **완료** | F-5 코드 글자 크기(8~24, 기본 12) — 한 값을 모든 모니터에(모니터 배율은 플랫폼), 코드 칸만, Ctrl+휠 · Ctrl++/- · Ctrl+0 + 설정 "보기 > 코드 글자 크기"(사용자 결정). 줄 높이 · 번호 칸 · 여백 단추가 같은 비율로(`CodeFontMetrics`, 12 에서 지금 값 그대로), 줄 번호 기준선을 코드와 맞춤. 8 · 12 · 24 에서 네 칸 행 높이 같음 · 단추 행 안. FontTest 110 · 1,538건. Ctrl+휠 · 키 · 껐다 켜도 남음 확인. 켜자마자는 포커스가 없어 단축키가 모두 안 듣던 것(원래 있던 것)도 고침 — 포커스가 비었거나 화면 밖이면 편집기로 |
|
| **완료** | F-5 코드 글자 크기(8~24, 기본 12) — 한 값을 모든 모니터에(모니터 배율은 플랫폼), 코드 칸만, Ctrl+휠 · Ctrl++/- · Ctrl+0 + 설정 "보기 > 코드 글자 크기"(사용자 결정). 줄 높이 · 번호 칸 · 여백 단추가 같은 비율로(`CodeFontMetrics`, 12 에서 지금 값 그대로), 줄 번호 기준선을 코드와 맞춤. 8 · 12 · 24 에서 네 칸 행 높이 같음 · 단추 행 안. FontTest 110 · 1,538건. Ctrl+휠 · 키 · 껐다 켜도 남음 확인. 켜자마자는 포커스가 없어 단축키가 모두 안 듣던 것(원래 있던 것)도 고침 — 포커스가 비었거나 화면 밖이면 편집기로 |
|
||||||
| **완료** | F-6 글자를 되도록 많이 보이게 — 방법마다 1920×1080 줄 수를 재 드린 뒤 사용자 결정 "줄 간격 줄여주고 선택 사항은 만들지 마": 줄 높이 글자의 1.67배 → 1.33배(12 → 16). 두 칸 40 → 50줄(탭 줄 접으면 53), 네 칸 17 · 18 → 22 · 23줄. 한글 잘림 · 겹침 없음, E-1 회귀 없음. FontTest 144 · 1,572건 |
|
| **완료** | F-6 글자를 되도록 많이 보이게 — 방법마다 1920×1080 줄 수를 재 드린 뒤 사용자 결정 "줄 간격 줄여주고 선택 사항은 만들지 마": 줄 높이 글자의 1.67배 → 1.33배(12 → 16). 두 칸 40 → 50줄(탭 줄 접으면 53), 네 칸 17 · 18 → 22 · 23줄. 한글 잘림 · 겹침 없음, E-1 회귀 없음. FontTest 144 · 1,572건 |
|
||||||
| **완료** | F-7 대형 모니터에서 오른쪽(종료 · ACTIONS)이 잘림 — 창이 모니터 밖으로 나간 것. 켤 때 창 모드로 기억한 자리를 모니터에 조금만 걸쳐도 그대로 놓던 것을 가장 많이 겹치는 모니터 안으로 밀게(플랫폼 `FitToMonitors`). 재현 840px 넘침 → 0. 좁은 창 × 화면 배율 80~133% 는 원래 넘친 것 없음. PlacementTest +8. 플랫폼 배포함(09-22) |
|
| **완료** | F-7 대형 모니터에서 오른쪽(종료 · ACTIONS)이 잘림 — 창이 모니터 밖으로 나간 것. 켤 때 창 모드로 기억한 자리를 모니터에 조금만 걸쳐도 그대로 놓던 것을 가장 많이 겹치는 모니터 안으로 밀게(플랫폼 `FitToMonitors`). 재현 840px 넘침 → 0. 좁은 창 × 화면 배율 80~133% 는 원래 넘친 것 없음. PlacementTest +8. 플랫폼 배포함(09-22) |
|
||||||
| 대기 | F-8 저장한 뒤에도 되돌리기 (지금은 저장 네 길이 스택을 비움) |
|
| **완료** | F-8 저장한 뒤에도 되돌리기 — 저장 · 저장 후 커밋 뒤 Undo/Redo 유지(저장할 것 있음은 "마지막 저장과 다른가" 로), 줄을 다시 세울 때(커밋 뒤 새 HEAD · 규칙 바꾸기 · 친 뒤 재정렬) 이력을 글 단위로 옮겨 지킴, 친 것도 한 걸음. 전체 되돌리기는 파일을 연 시점. Git 탭 "푼 파일" + [다시 충돌로](이 단추만 git.exe, 사용자 결정). MergeSaveTest +21 |
|
||||||
| 대기 | F-9 백업본으로 저장 · 원본을 건드릴지 고르게 |
|
| 대기 | F-9 백업본으로 저장 · 원본을 건드릴지 고르게 |
|
||||||
| 대기 | F-10 덩어리가 어디로 갔는지 UI 로 잇기 (지금은 "자리 옮김" 색만) |
|
| 대기 | F-10 덩어리가 어디로 갔는지 UI 로 잇기 (지금은 "자리 옮김" 색만) |
|
||||||
| 대기 | F-11 Git 계정 · URL · 암호 저장 확인 (지금은 주소 · 계정만 저장, 토큰은 메모리) |
|
| 대기 | F-11 Git 계정 · URL · 암호 저장 확인 (지금은 주소 · 계정만 저장, 토큰은 메모리) |
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@ -1002,6 +1003,106 @@ namespace MergeHub.Services
|
|||||||
return lines.Skip(1).Count(l => l.Trim().Length > 0);
|
return lines.Skip(1).Count(l => l.Trim().Length > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 이번 병합에서 처음 충돌 났다가 지금은 푼 파일(11차 F-8). [다시 충돌로] 로 되돌릴 수 있는 것만 준다 -
|
||||||
|
/// git 이 해결 전 세 벌(REUC)을 기억하는 파일. 병합 중이 아니거나 MergeHub 밖에서 시작한 병합이면 빈 목록.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<string> GetResolvedMergeConflicts()
|
||||||
|
{
|
||||||
|
var path = Path.Combine(Repo.Info.Path, MergeConflictListFile);
|
||||||
|
var head = ReadMergeHeadSha();
|
||||||
|
if (string.IsNullOrEmpty(head) || !File.Exists(path)) return Array.Empty<string>();
|
||||||
|
|
||||||
|
var lines = File.ReadAllLines(path);
|
||||||
|
if (lines.Length == 0 || !string.Equals(lines[0].Trim(), head, StringComparison.OrdinalIgnoreCase)) return Array.Empty<string>();
|
||||||
|
|
||||||
|
return MergeConflictRecord.Resolved(
|
||||||
|
lines.Skip(1),
|
||||||
|
GetConflicts().Select(c => c.Path),
|
||||||
|
Repo.Index.Conflicts.ResolvedConflicts.Select(r => r.Path));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 푼 파일을 다시 충돌로 되돌린다(11차 F-8, 사용자 결정 "되돌리면 다시 충돌로").
|
||||||
|
///
|
||||||
|
/// LibGit2Sharp 에는 인덱스에 충돌 세 벌을 다시 넣는 길이 없다. 이 한 곳만 git.exe 를 부른다(사용자 결정) -
|
||||||
|
/// <c>git checkout -m -- 파일</c> 은 해결 기록(REUC)으로 세 벌을 되살리고 워킹트리에 충돌 표시를 다시 쓴다.
|
||||||
|
/// 워킹트리에 저장해 둔 해결 내용은 충돌 표시 글로 바뀐다(부르는 쪽이 먼저 묻는다).
|
||||||
|
/// 끝나면 저장소를 다시 연다 - 열어 둔 객체는 인덱스를 다시 읽지 않아 충돌 목록이 비어 보였다(시험으로 확인).
|
||||||
|
/// </summary>
|
||||||
|
public void ReopenConflict(string path)
|
||||||
|
{
|
||||||
|
var normalized = NormalizePath(path);
|
||||||
|
var head = ReadMergeHeadSha();
|
||||||
|
if (string.IsNullOrEmpty(head)) throw new InvalidOperationException("병합 중이 아니라 다시 충돌로 되돌릴 수 없습니다.");
|
||||||
|
|
||||||
|
var workdir = Repo.Info.WorkingDirectory ?? throw new InvalidOperationException("워킹트리가 없는 저장소입니다.");
|
||||||
|
var gitDir = Repo.Info.Path;
|
||||||
|
|
||||||
|
RunGitExe(workdir, "-c", "core.quotepath=false", "checkout", "-m", "--", normalized);
|
||||||
|
|
||||||
|
_repo?.Dispose();
|
||||||
|
_repo = new Repository(gitDir);
|
||||||
|
|
||||||
|
// 해결 기록(8차 F-1)에서 뺀다 - 다시 풀면 그때 적는다.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var file = Path.Combine(gitDir, MergeResolutionLog.FileName);
|
||||||
|
if (File.Exists(file))
|
||||||
|
File.WriteAllText(file, MergeResolutionLog.Forget(File.ReadAllText(file), head!, normalized), new UTF8Encoding(false));
|
||||||
|
}
|
||||||
|
catch (IOException) { }
|
||||||
|
catch (UnauthorizedAccessException) { }
|
||||||
|
|
||||||
|
Report($"다시 충돌로: {normalized}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>git.exe 를 한 번 부른다(11차 F-8 [다시 충돌로] 만 쓴다). 실패하면 git 이 한 말을 담아 던진다.</summary>
|
||||||
|
private static void RunGitExe(string workdir, params string[] args)
|
||||||
|
{
|
||||||
|
var info = new ProcessStartInfo("git", string.Join(" ", args.Select(QuoteArg)))
|
||||||
|
{
|
||||||
|
WorkingDirectory = workdir,
|
||||||
|
UseShellExecute = false,
|
||||||
|
CreateNoWindow = true,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
RedirectStandardError = true,
|
||||||
|
StandardOutputEncoding = Encoding.UTF8,
|
||||||
|
StandardErrorEncoding = Encoding.UTF8,
|
||||||
|
};
|
||||||
|
|
||||||
|
Process process;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
process = Process.Start(info) ?? throw new InvalidOperationException("git.exe 를 실행하지 못했습니다.");
|
||||||
|
}
|
||||||
|
catch (System.ComponentModel.Win32Exception)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Git(git.exe)을 찾지 못했습니다. [다시 충돌로] 는 Git 이 설치되어 PATH 에 있어야 합니다.");
|
||||||
|
}
|
||||||
|
|
||||||
|
using (process)
|
||||||
|
{
|
||||||
|
var error = process.StandardError.ReadToEndAsync();
|
||||||
|
process.StandardOutput.ReadToEnd();
|
||||||
|
if (!process.WaitForExit(30000))
|
||||||
|
{
|
||||||
|
try { process.Kill(); } catch (InvalidOperationException) { }
|
||||||
|
throw new InvalidOperationException("git.exe 가 30초 안에 끝나지 않았습니다.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.ExitCode != 0)
|
||||||
|
throw new InvalidOperationException($"git 이 실패했습니다({process.ExitCode}): {error.Result.Trim()}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Windows 명령줄 규칙으로 인자 하나를 감싼다(빈칸 · 따옴표가 든 경로).</summary>
|
||||||
|
private static string QuoteArg(string arg)
|
||||||
|
{
|
||||||
|
if (arg.Length > 0 && arg.IndexOfAny(new[] { ' ', '\t', '"' }) < 0) return arg;
|
||||||
|
return "\"" + arg.Replace("\"", "\\\"") + "\"";
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 쓸모없어진 충돌 목록 파일을 지운다(7차 B-1). 지웠으면 true.
|
/// 쓸모없어진 충돌 목록 파일을 지운다(7차 B-1). 지웠으면 true.
|
||||||
/// 병합 커밋 · 작업 취소 직후와 상태를 다시 읽을 때마다 부른다 - 터미널에서 병합을 끝낸 경우도 다음 새로고침에 치운다.
|
/// 병합 커밋 · 작업 취소 직후와 상태를 다시 읽을 때마다 부른다 - 터미널에서 병합을 끝낸 경우도 다음 새로고침에 치운다.
|
||||||
|
|||||||
@ -150,6 +150,9 @@ namespace MergeHub.Services
|
|||||||
public ObservableCollection<CommitNode> CommitNodes { get; } = new ObservableCollection<CommitNode>();
|
public ObservableCollection<CommitNode> CommitNodes { get; } = new ObservableCollection<CommitNode>();
|
||||||
public ObservableCollection<FileStatusInfo> StatusFiles { get; } = new ObservableCollection<FileStatusInfo>();
|
public ObservableCollection<FileStatusInfo> StatusFiles { get; } = new ObservableCollection<FileStatusInfo>();
|
||||||
public ObservableCollection<ConflictInfo> Conflicts { get; } = new ObservableCollection<ConflictInfo>();
|
public ObservableCollection<ConflictInfo> Conflicts { get; } = new ObservableCollection<ConflictInfo>();
|
||||||
|
|
||||||
|
/// <summary>이번 병합에서 푼 파일 - [다시 충돌로] 로 되돌릴 수 있는 것(11차 F-8). 병합 중이 아니면 빈다.</summary>
|
||||||
|
public ObservableCollection<string> ResolvedConflicts { get; } = new ObservableCollection<string>();
|
||||||
public ObservableCollection<TagInfo> Tags { get; } = new ObservableCollection<TagInfo>();
|
public ObservableCollection<TagInfo> Tags { get; } = new ObservableCollection<TagInfo>();
|
||||||
public ObservableCollection<SubmoduleInfo> Submodules { get; } = new ObservableCollection<SubmoduleInfo>();
|
public ObservableCollection<SubmoduleInfo> Submodules { get; } = new ObservableCollection<SubmoduleInfo>();
|
||||||
|
|
||||||
@ -408,6 +411,7 @@ namespace MergeHub.Services
|
|||||||
var mergeInProgress = Git.IsMergeInProgress;
|
var mergeInProgress = Git.IsMergeInProgress;
|
||||||
var pendingHasChanges = !operation.IsInProgress || Git.HasStagedChanges();
|
var pendingHasChanges = !operation.IsInProgress || Git.HasStagedChanges();
|
||||||
var conflictTotal = mergeInProgress ? Git.GetMergeConflictTotal() : null;
|
var conflictTotal = mergeInProgress ? Git.GetMergeConflictTotal() : null;
|
||||||
|
var resolvedConflicts = mergeInProgress ? Git.GetResolvedMergeConflicts() : Array.Empty<string>();
|
||||||
var resolvedOperation = CommitSelection.IsResolvedOperation(operation.IsInProgress, conflicts.Count);
|
var resolvedOperation = CommitSelection.IsResolvedOperation(operation.IsInProgress, conflicts.Count);
|
||||||
|
|
||||||
var remote = remotes.FirstOrDefault(r => r.Name == RemoteName) ?? remotes.FirstOrDefault();
|
var remote = remotes.FirstOrDefault(r => r.Name == RemoteName) ?? remotes.FirstOrDefault();
|
||||||
@ -453,6 +457,7 @@ namespace MergeHub.Services
|
|||||||
Replace(CommitNodes, nodes);
|
Replace(CommitNodes, nodes);
|
||||||
Replace(StatusFiles, status.Files);
|
Replace(StatusFiles, status.Files);
|
||||||
Replace(Conflicts, conflicts);
|
Replace(Conflicts, conflicts);
|
||||||
|
Replace(ResolvedConflicts, resolvedConflicts);
|
||||||
MergeConflictTotal = conflictTotal;
|
MergeConflictTotal = conflictTotal;
|
||||||
MergeResolutionBody = resolutionBody;
|
MergeResolutionBody = resolutionBody;
|
||||||
OnPropertyChanged(nameof(ConflictProgressText));
|
OnPropertyChanged(nameof(ConflictProgressText));
|
||||||
|
|||||||
@ -87,6 +87,12 @@ namespace MergeHub.Services
|
|||||||
/// <summary>이번 병합에서 처음 난 충돌 파일 수. MergeHub 가 병합할 때 적어 둔 것, 모르면 null(6차 C-1).</summary>
|
/// <summary>이번 병합에서 처음 난 충돌 파일 수. MergeHub 가 병합할 때 적어 둔 것, 모르면 null(6차 C-1).</summary>
|
||||||
int? GetMergeConflictTotal();
|
int? GetMergeConflictTotal();
|
||||||
|
|
||||||
|
/// <summary>이번 병합에서 처음 충돌 났다가 지금은 푼 파일 - [다시 충돌로] 로 되돌릴 수 있는 것(11차 F-8).</summary>
|
||||||
|
IReadOnlyList<string> GetResolvedMergeConflicts();
|
||||||
|
|
||||||
|
/// <summary>푼 파일을 다시 충돌로 되돌린다(11차 F-8). 워킹트리의 해결 내용은 충돌 표시 글로 바뀐다.</summary>
|
||||||
|
void ReopenConflict(string path);
|
||||||
|
|
||||||
/// <summary>쓸모없어진 처음 충돌 목록(.git/MERGEHUB_CONFLICTS)을 지운다. 지웠으면 true(7차 B-1).</summary>
|
/// <summary>쓸모없어진 처음 충돌 목록(.git/MERGEHUB_CONFLICTS)을 지운다. 지웠으면 true(7차 B-1).</summary>
|
||||||
bool ForgetStaleMergeConflicts();
|
bool ForgetStaleMergeConflicts();
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace MergeHub.Services
|
namespace MergeHub.Services
|
||||||
{
|
{
|
||||||
@ -23,5 +25,21 @@ namespace MergeHub.Services
|
|||||||
if (string.IsNullOrWhiteSpace(recordFirstLine)) return true;
|
if (string.IsNullOrWhiteSpace(recordFirstLine)) return true;
|
||||||
return !string.Equals(recordFirstLine!.Trim(), mergeHeadSha!.Trim(), StringComparison.OrdinalIgnoreCase);
|
return !string.Equals(recordFirstLine!.Trim(), mergeHeadSha!.Trim(), StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// [다시 충돌로] 로 되돌릴 수 있는 파일(11차 F-8) - 이번 병합에서 처음 충돌 났고(<paramref name="initial"/>),
|
||||||
|
/// 지금은 충돌이 아니며(<paramref name="current"/> 에 없음), git 이 해결 전 세 벌을 기억하는(<paramref name="resolveUndo"/>, REUC) 파일.
|
||||||
|
/// REUC 만 보면 안 된다 - libgit2 는 저절로 병합된 파일도 거기 남긴다(6차 C-1). 처음 목록 차례를 지킨다.
|
||||||
|
/// </summary>
|
||||||
|
public static IReadOnlyList<string> Resolved(IEnumerable<string> initial, IEnumerable<string> current, IEnumerable<string> resolveUndo)
|
||||||
|
{
|
||||||
|
static string N(string p) => (p ?? string.Empty).Trim().Replace('\\', '/');
|
||||||
|
var now = new HashSet<string>(current.Select(N), StringComparer.Ordinal);
|
||||||
|
var undo = new HashSet<string>(resolveUndo.Select(N), StringComparer.Ordinal);
|
||||||
|
var seen = new HashSet<string>(StringComparer.Ordinal);
|
||||||
|
return initial.Select(N)
|
||||||
|
.Where(p => p.Length > 0 && !now.Contains(p) && undo.Contains(p) && seen.Add(p))
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@ -104,6 +104,21 @@ namespace MergeHub.Services
|
|||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 한 파일의 기록을 뺀 글(11차 F-8 - [다시 충돌로] 한 파일은 아직 푼 것이 아니다).
|
||||||
|
/// 지난 병합의 기록이었으면 머리 줄만 남긴 새 글. 뺄 것이 없으면 같은 내용.
|
||||||
|
/// </summary>
|
||||||
|
public static string Forget(string? existing, string mergeHead, string path)
|
||||||
|
{
|
||||||
|
var normalized = Normalize(path);
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.Append(mergeHead.Trim()).Append('\n');
|
||||||
|
foreach (var entry in Parse(existing, mergeHead))
|
||||||
|
if (!string.Equals(entry.Path, normalized, StringComparison.Ordinal))
|
||||||
|
sb.Append(entry.Kind).Append('\t').Append(entry.Path).Append('\n');
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 병합 커밋 본문 블록. 기록이 없으면 null.
|
/// 병합 커밋 본문 블록. 기록이 없으면 null.
|
||||||
/// <code>
|
/// <code>
|
||||||
|
|||||||
50
src/MergeHub/Services/SaveBaseline.cs
Normal file
50
src/MergeHub/Services/SaveBaseline.cs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace MergeHub.Services
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 편집 칸의 두 기준 글(11차 F-8) — <b>파일을 연 시점</b>과 <b>마지막으로 저장한 것</b>.
|
||||||
|
///
|
||||||
|
/// 예전에는 저장하는 길마다 되돌리기 목록을 비우고, "저장할 것 있음" 을 "되돌리기 목록이 비었는가" 로 봤다.
|
||||||
|
/// 그래서 저장한 뒤에는 Undo 가 꺼졌다. 이제 목록은 두고, 저장할 것이 있는지는 <b>지금 글이 마지막 저장과 다른가</b>로 본다 —
|
||||||
|
/// 저장 뒤 Undo 하면 달라져 "있음", Redo 로 저장 지점에 오면 같아져 "없음". 저장 표시를 따로 들고 다닐 필요가 없다.
|
||||||
|
/// [전체 되돌리기] 는 파일을 연 시점으로 간다(사용자 결정).
|
||||||
|
/// </summary>
|
||||||
|
public sealed class SaveBaseline
|
||||||
|
{
|
||||||
|
/// <summary>파일을 연 시점의 글. [전체 되돌리기] 가 가는 곳.</summary>
|
||||||
|
public string Opened { get; private set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>마지막으로 저장한 글(연 뒤 저장하지 않았으면 연 시점과 같다).</summary>
|
||||||
|
public string Saved { get; private set; } = string.Empty;
|
||||||
|
|
||||||
|
public string RevertTarget => Opened;
|
||||||
|
|
||||||
|
/// <summary>파일을 새로 열었다.</summary>
|
||||||
|
public void Open(string text)
|
||||||
|
{
|
||||||
|
Opened = text ?? string.Empty;
|
||||||
|
Saved = Opened;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>화면을 잠깐 비웠다가 같은 파일 · 같은 글을 다시 실었다 - 맡겨 둔 두 기준을 되찾는다.</summary>
|
||||||
|
public void Resume(string opened, string saved)
|
||||||
|
{
|
||||||
|
Opened = opened ?? string.Empty;
|
||||||
|
Saved = saved ?? string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>줄바꿈(CRLF · LF)과 끝 줄바꿈만 다르면 같은 글. 파일에서 읽은 글과 화면 글을 견줄 때 쓴다.</summary>
|
||||||
|
public static bool SameText(string? a, string? b) =>
|
||||||
|
string.Equals(Norm(a), Norm(b), StringComparison.Ordinal);
|
||||||
|
|
||||||
|
private static string Norm(string? s) => (s ?? string.Empty).Replace("\r\n", "\n").TrimEnd('\n');
|
||||||
|
|
||||||
|
/// <summary>파일에 썼다.</summary>
|
||||||
|
public void MarkSaved(string text) => Saved = text ?? string.Empty;
|
||||||
|
|
||||||
|
/// <param name="stylePending">C# 스타일 맞춤으로 연 글이 파일과 다르다(저장하면 반영된다).</param>
|
||||||
|
public bool IsDirty(string current, bool stylePending) =>
|
||||||
|
stylePending || !string.Equals(current ?? string.Empty, Saved, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -97,6 +97,7 @@ namespace MergeHub.ViewModels
|
|||||||
[ObservableProperty] private string _commitMessage = string.Empty;
|
[ObservableProperty] private string _commitMessage = string.Empty;
|
||||||
[ObservableProperty] private FileStatusInfo? _selectedStatusFile;
|
[ObservableProperty] private FileStatusInfo? _selectedStatusFile;
|
||||||
[ObservableProperty] private ConflictInfo? _selectedConflict;
|
[ObservableProperty] private ConflictInfo? _selectedConflict;
|
||||||
|
[ObservableProperty] private string? _selectedResolvedConflict;
|
||||||
[ObservableProperty] private TagInfo? _selectedTag;
|
[ObservableProperty] private TagInfo? _selectedTag;
|
||||||
[ObservableProperty] private string _diffText = string.Empty;
|
[ObservableProperty] private string _diffText = string.Empty;
|
||||||
[ObservableProperty] private StashInfo? _selectedStash;
|
[ObservableProperty] private StashInfo? _selectedStash;
|
||||||
@ -121,6 +122,7 @@ namespace MergeHub.ViewModels
|
|||||||
partial void OnBlamePathChanged(string value) => RaiseCanExecuteChanged();
|
partial void OnBlamePathChanged(string value) => RaiseCanExecuteChanged();
|
||||||
partial void OnSelectedStatusFileChanged(FileStatusInfo? value) => RaiseCanExecuteChanged();
|
partial void OnSelectedStatusFileChanged(FileStatusInfo? value) => RaiseCanExecuteChanged();
|
||||||
partial void OnSelectedConflictChanged(ConflictInfo? value) => RaiseCanExecuteChanged();
|
partial void OnSelectedConflictChanged(ConflictInfo? value) => RaiseCanExecuteChanged();
|
||||||
|
partial void OnSelectedResolvedConflictChanged(string? value) => ReopenConflictCommand.NotifyCanExecuteChanged();
|
||||||
partial void OnSelectedTagChanged(TagInfo? value) => RaiseCanExecuteChanged();
|
partial void OnSelectedTagChanged(TagInfo? value) => RaiseCanExecuteChanged();
|
||||||
partial void OnSelectedStashChanged(StashInfo? value) => RaiseCanExecuteChanged();
|
partial void OnSelectedStashChanged(StashInfo? value) => RaiseCanExecuteChanged();
|
||||||
|
|
||||||
@ -486,6 +488,28 @@ namespace MergeHub.ViewModels
|
|||||||
return go ? ResolveAsync(ConflictResolution.Both) : Task.CompletedTask;
|
return go ? ResolveAsync(ConflictResolution.Both) : Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool CanReopenConflict() => _workspace.IsMergeInProgress && !string.IsNullOrEmpty(SelectedResolvedConflict);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 푼 파일을 다시 충돌로 되돌린다(11차 F-8, 사용자 결정 "되돌리면 다시 충돌로").
|
||||||
|
/// 병합 중 [Save](해결로 저장)는 파일을 풀고 다음 충돌로 넘어가 편집기의 Undo 로는 돌아올 수 없다 - 여기서 되돌린다.
|
||||||
|
/// 워킹트리의 해결 내용이 충돌 표시 글로 바뀌므로 먼저 묻는다.
|
||||||
|
/// </summary>
|
||||||
|
[RelayCommand(CanExecute = nameof(CanReopenConflict))]
|
||||||
|
private async Task ReopenConflictAsync()
|
||||||
|
{
|
||||||
|
var path = SelectedResolvedConflict;
|
||||||
|
if (string.IsNullOrEmpty(path)) return;
|
||||||
|
|
||||||
|
if (!MessageUtil.ShowYesNo("다시 충돌로",
|
||||||
|
$"{path}\n\n이 파일을 푼 것을 되돌려 다시 충돌로 만듭니다.\n" +
|
||||||
|
"파일에 저장해 둔 해결 내용은 충돌 표시(<<<<<<< ======= >>>>>>>)가 든 글로 바뀌고, 다시 풀어야 병합 커밋을 할 수 있습니다.\n\n계속할까요?"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var ok = await _workspace.RunAndRefreshAsync("다시 충돌로", () => _workspace.Git.ReopenConflict(path!));
|
||||||
|
if (ok) MessageUtil.ShowSuccessToast($"다시 충돌로 · {path} - 충돌 목록에서 다시 풀 수 있습니다");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 고른 충돌 파일을 파일 비교 탭의 3분할로 연다(6차 B-1). 목록을 두 번 누르거나 Enter 로도 온다.
|
/// 고른 충돌 파일을 파일 비교 탭의 3분할로 연다(6차 B-1). 목록을 두 번 누르거나 Enter 로도 온다.
|
||||||
/// 켜짐/꺼짐을 따로 두지 않는다 - 고른 것이 없으면 아무 일도 안 한다. 이진 파일은 줄 단위로 볼 수 없어 알린다.
|
/// 켜짐/꺼짐을 따로 두지 않는다 - 고른 것이 없으면 아무 일도 안 한다. 이진 파일은 줄 단위로 볼 수 없어 알린다.
|
||||||
@ -1068,6 +1092,7 @@ namespace MergeHub.ViewModels
|
|||||||
/// <summary>CommunityToolkit 의 커맨드는 CommandManager 를 구독하지 않아 직접 알려야 한다.</summary>
|
/// <summary>CommunityToolkit 의 커맨드는 CommandManager 를 구독하지 않아 직접 알려야 한다.</summary>
|
||||||
private void RaiseCanExecuteChanged()
|
private void RaiseCanExecuteChanged()
|
||||||
{
|
{
|
||||||
|
ReopenConflictCommand.NotifyCanExecuteChanged();
|
||||||
OpenCommand.NotifyCanExecuteChanged();
|
OpenCommand.NotifyCanExecuteChanged();
|
||||||
CloneCommand.NotifyCanExecuteChanged();
|
CloneCommand.NotifyCanExecuteChanged();
|
||||||
AddRemoteCommand.NotifyCanExecuteChanged();
|
AddRemoteCommand.NotifyCanExecuteChanged();
|
||||||
|
|||||||
@ -235,15 +235,14 @@ namespace MergeHub.ViewModels
|
|||||||
_theirsSideName = Path.GetFileName(request.Remote);
|
_theirsSideName = Path.GetFileName(request.Remote);
|
||||||
_mineSideName = Path.GetFileName(request.Local);
|
_mineSideName = Path.GetFileName(request.Local);
|
||||||
|
|
||||||
_originalLocalText = BuildLocalText();
|
_baseline.Open(BuildLocalText());
|
||||||
SyncEditorFromLines();
|
SyncEditorFromLines();
|
||||||
|
|
||||||
var info = TextFile.Describe(File.Exists(request.Local) ? request.Local : request.Remote);
|
var info = TextFile.Describe(File.Exists(request.Local) ? request.Local : request.Remote);
|
||||||
FileEncoding = info.Encoding;
|
FileEncoding = info.Encoding;
|
||||||
FileNewLine = info.NewLine;
|
FileNewLine = info.NewLine;
|
||||||
|
|
||||||
_undo.Clear();
|
ClearHistory();
|
||||||
_redo.Clear();
|
|
||||||
MarkStyleChange(rawLocalText, localText);
|
MarkStyleChange(rawLocalText, localText);
|
||||||
CaretRow = 0;
|
CaretRow = 0;
|
||||||
InvalidateDiffBlocks();
|
InvalidateDiffBlocks();
|
||||||
@ -811,7 +810,7 @@ namespace MergeHub.ViewModels
|
|||||||
|
|
||||||
_undo.Clear();
|
_undo.Clear();
|
||||||
foreach (var e in remaining) _undo.Push(e);
|
foreach (var e in remaining) _undo.Push(e);
|
||||||
_redo.Clear();
|
ClearRedo();
|
||||||
|
|
||||||
_workspace.AppendLog($"{line.LocalNumber ?? line.BaseNumber}행 되돌림");
|
_workspace.AppendLog($"{line.LocalNumber ?? line.BaseNumber}행 되돌림");
|
||||||
AfterEdit();
|
AfterEdit();
|
||||||
@ -819,12 +818,16 @@ namespace MergeHub.ViewModels
|
|||||||
|
|
||||||
private bool HasEditFor(MergeLine line) => _undo.Any(e => ReferenceEquals(e.Line, line));
|
private bool HasEditFor(MergeLine line) => _undo.Any(e => ReferenceEquals(e.Line, line));
|
||||||
|
|
||||||
private bool CanUndo() => _undo.Count > 0;
|
private bool CanUndo() => _undo.Count > 0 || _textUndo.Count > 0;
|
||||||
|
|
||||||
[RelayCommand(CanExecute = nameof(CanUndo))]
|
[RelayCommand(CanExecute = nameof(CanUndo))]
|
||||||
private void Undo()
|
private void Undo()
|
||||||
{
|
{
|
||||||
if (_undo.Count == 0) return;
|
if (_undo.Count == 0)
|
||||||
|
{
|
||||||
|
if (_textUndo.Count > 0) StepText(_textUndo, _textRedo);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 덩어리 · 메서드 반영은 여러 줄이 한 묶음이다 - 한 번에 되돌린다(11차 F-2).
|
// 덩어리 · 메서드 반영은 여러 줄이 한 묶음이다 - 한 번에 되돌린다(11차 F-2).
|
||||||
var edit = _undo.Pop();
|
var edit = _undo.Pop();
|
||||||
@ -840,12 +843,16 @@ namespace MergeHub.ViewModels
|
|||||||
AfterEdit();
|
AfterEdit();
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool CanRedo() => _redo.Count > 0;
|
private bool CanRedo() => _redo.Count > 0 || _textRedo.Count > 0;
|
||||||
|
|
||||||
[RelayCommand(CanExecute = nameof(CanRedo))]
|
[RelayCommand(CanExecute = nameof(CanRedo))]
|
||||||
private void Redo()
|
private void Redo()
|
||||||
{
|
{
|
||||||
if (_redo.Count == 0) return;
|
if (_redo.Count == 0)
|
||||||
|
{
|
||||||
|
if (_textRedo.Count > 0) StepText(_textRedo, _textUndo);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var edit = _redo.Pop();
|
var edit = _redo.Pop();
|
||||||
Restore(edit, toBefore: false);
|
Restore(edit, toBefore: false);
|
||||||
@ -871,7 +878,9 @@ namespace MergeHub.ViewModels
|
|||||||
AfterEdit();
|
AfterEdit();
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool CanRevertAll() => HasChanges;
|
/// <summary>저장할 것이 있거나, 저장은 했지만 연 시점과 달라졌다(11차 F-8 - 저장해도 연 시점으로 돌아갈 수 있다).</summary>
|
||||||
|
private bool CanRevertAll() =>
|
||||||
|
HasChanges || !string.Equals(_baseline.Saved, _baseline.Opened, StringComparison.Ordinal);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 파일을 연 시점의 LOCAL 로 통째로 되돌린다.
|
/// 파일을 연 시점의 LOCAL 로 통째로 되돌린다.
|
||||||
@ -882,8 +891,14 @@ namespace MergeHub.ViewModels
|
|||||||
[RelayCommand(CanExecute = nameof(CanRevertAll))]
|
[RelayCommand(CanExecute = nameof(CanRevertAll))]
|
||||||
private void RevertAll()
|
private void RevertAll()
|
||||||
{
|
{
|
||||||
|
// 연 뒤 저장했으면 파일은 이미 바뀌어 있다 - 화면만 연 시점으로 가고, 파일에는 다시 저장해야 들어간다(11차 F-8).
|
||||||
|
var saved = !string.Equals(_baseline.Saved, _baseline.Opened, StringComparison.Ordinal);
|
||||||
if (!MessageUtil.ShowYesNo("전체 되돌리기",
|
if (!MessageUtil.ShowYesNo("전체 되돌리기",
|
||||||
"고친 내용을 모두 버리고 파일을 연 시점으로 돌아갑니다.\n\n되살릴 수 없습니다. 계속할까요?"))
|
saved
|
||||||
|
? "고친 내용을 모두 버리고 파일을 연 시점으로 돌아갑니다.\n\n" +
|
||||||
|
"이미 저장한 파일 · 커밋은 그대로입니다 - 화면만 돌아가고, 파일에 넣으려면 다시 저장하세요.\n" +
|
||||||
|
"되돌리기 목록은 비워집니다. 계속할까요?"
|
||||||
|
: "고친 내용을 모두 버리고 파일을 연 시점으로 돌아갑니다.\n\n되살릴 수 없습니다. 계속할까요?"))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ReloadLinesFrom(_originalLocalText, keepAsEdited: false);
|
ReloadLinesFrom(_originalLocalText, keepAsEdited: false);
|
||||||
@ -961,10 +976,7 @@ namespace MergeHub.ViewModels
|
|||||||
|
|
||||||
_workspace.OnUi(() =>
|
_workspace.OnUi(() =>
|
||||||
{
|
{
|
||||||
_originalLocalText = content;
|
MarkSaved(content);
|
||||||
_undo.Clear();
|
|
||||||
_redo.Clear();
|
|
||||||
HasChanges = false;
|
|
||||||
CommitMessage = string.Empty;
|
CommitMessage = string.Empty;
|
||||||
_autoCommitMessage = string.Empty;
|
_autoCommitMessage = string.Empty;
|
||||||
MessageUtil.ShowSuccessToast(
|
MessageUtil.ShowSuccessToast(
|
||||||
@ -1042,9 +1054,9 @@ namespace MergeHub.ViewModels
|
|||||||
{
|
{
|
||||||
// 저장한 내용이 이제 "원본" 이다. 이걸 안 하면 저장한 뒤에도 편집이 남은 것으로 보여
|
// 저장한 내용이 이제 "원본" 이다. 이걸 안 하면 저장한 뒤에도 편집이 남은 것으로 보여
|
||||||
// 다른 파일을 고를 때마다 확인 창이 다시 뜬다(5차 C-1 에서 겪었다).
|
// 다른 파일을 고를 때마다 확인 창이 다시 뜬다(5차 C-1 에서 겪었다).
|
||||||
_originalLocalText = content;
|
_baseline.Open(content);
|
||||||
_undo.Clear();
|
_styleRawLocalText = null;
|
||||||
_redo.Clear();
|
ClearHistory();
|
||||||
HasChanges = false;
|
HasChanges = false;
|
||||||
RaiseCanExecuteChanged();
|
RaiseCanExecuteChanged();
|
||||||
|
|
||||||
@ -1127,8 +1139,7 @@ namespace MergeHub.ViewModels
|
|||||||
|
|
||||||
_workspace.OnUi(() =>
|
_workspace.OnUi(() =>
|
||||||
{
|
{
|
||||||
_undo.Clear();
|
ClearHistory();
|
||||||
_redo.Clear();
|
|
||||||
HasChanges = false;
|
HasChanges = false;
|
||||||
RaiseCanExecuteChanged();
|
RaiseCanExecuteChanged();
|
||||||
|
|
||||||
@ -1464,6 +1475,33 @@ namespace MergeHub.ViewModels
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 새로고침이 <b>화면에 있는 파일을 화면 그대로의 글로</b> 다시 읽었다(11차 F-8) - [저장 후 커밋] 뒤가 그렇다.
|
||||||
|
// 커밋하면 BASE 가 새 HEAD 로 옮겨 가(9차 A-2) 줄을 다시 세울 수밖에 없는데, 그러면 되돌리기 목록이 비어
|
||||||
|
// 커밋한 뒤에는 Undo 가 꺼졌다. 글이 같으니 이력은 그대로 뜻이 있다 - 글 단위로 옮겨 지킨다(커밋은 두고 화면만 되돌림, 사용자 결정).
|
||||||
|
// 새로고침은 한 번에 두 번 읽기도 한다(고른 커밋이 바뀜 · 목록 다시 세우기). 표시를 한 번 쓰고 버리면 두 번째가 비웠다 - 조건만 본다.
|
||||||
|
// 파일에서 읽은 글은 CRLF · 끝 줄바꿈이 붙어 있다 - 줄바꿈 차이는 같은 글로 본다.
|
||||||
|
// 새로고침 사이에 화면이 잠깐 비워지기도 한다(목록을 다시 세우는 동안 고른 파일 · 커밋이 잠깐 빔) - 그때 맡겨 둔 이력을 되찾는다.
|
||||||
|
var parked = _parkedHistory;
|
||||||
|
_parkedHistory = null;
|
||||||
|
var keepHistory = false;
|
||||||
|
if (!threeWay && !isConflict && !IsThreeWay)
|
||||||
|
{
|
||||||
|
if (string.Equals(_shownFile, path, StringComparison.Ordinal) && SaveBaseline.SameText(localText, BuildLocalText()))
|
||||||
|
{
|
||||||
|
FlattenHistory();
|
||||||
|
keepHistory = true;
|
||||||
|
}
|
||||||
|
else if (_shownFile is null && parked is not null && string.Equals(parked.File, path, StringComparison.Ordinal) &&
|
||||||
|
SaveBaseline.SameText(localText, parked.Text))
|
||||||
|
{
|
||||||
|
ClearHistory();
|
||||||
|
for (var i = parked.Undo.Length - 1; i >= 0; i--) _textUndo.Push(parked.Undo[i]);
|
||||||
|
for (var i = parked.Redo.Length - 1; i >= 0; i--) _textRedo.Push(parked.Redo[i]);
|
||||||
|
_baseline.Resume(parked.Opened, parked.Saved);
|
||||||
|
keepHistory = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 여기서부터 화면에 실리는 것은 이 파일의 내용이다. 저장은 이 이름으로만 쓴다.
|
// 여기서부터 화면에 실리는 것은 이 파일의 내용이다. 저장은 이 이름으로만 쓴다.
|
||||||
_shownFile = path;
|
_shownFile = path;
|
||||||
|
|
||||||
@ -1516,7 +1554,7 @@ namespace MergeHub.ViewModels
|
|||||||
_loadedAsConflict = isConflict;
|
_loadedAsConflict = isConflict;
|
||||||
_appliedBaseNumbers.Clear(); // 파일이 바뀌면 앞 파일의 결정은 뜻이 없다
|
_appliedBaseNumbers.Clear(); // 파일이 바뀌면 앞 파일의 결정은 뜻이 없다
|
||||||
_appliedMineNumbers.Clear();
|
_appliedMineNumbers.Clear();
|
||||||
_originalLocalText = BuildLocalText();
|
if (!keepHistory) _baseline.Open(BuildLocalText()); // 이어서 읽은 것이면 연 시점 · 저장 지점을 그대로 둔다
|
||||||
SyncEditorFromLines();
|
SyncEditorFromLines();
|
||||||
|
|
||||||
// 충돌 파일은 충돌 표시가 든 워킹트리가 아니라 내 쪽 파일로 본다 - 저장도 그 형식을 따른다(6차 B-7).
|
// 충돌 파일은 충돌 표시가 든 워킹트리가 아니라 내 쪽 파일로 본다 - 저장도 그 형식을 따른다(6차 B-7).
|
||||||
@ -1530,8 +1568,7 @@ namespace MergeHub.ViewModels
|
|||||||
InvalidateDiffBlocks();
|
InvalidateDiffBlocks();
|
||||||
RefreshDiffPosition();
|
RefreshDiffPosition();
|
||||||
|
|
||||||
_undo.Clear();
|
if (!keepHistory) ClearHistory();
|
||||||
_redo.Clear();
|
|
||||||
SelectedLine = Lines.FirstOrDefault(l => l.IsDifferent) ?? Lines.FirstOrDefault();
|
SelectedLine = Lines.FirstOrDefault(l => l.IsDifferent) ?? Lines.FirstOrDefault();
|
||||||
|
|
||||||
// 병합 커밋 메시지 기본값은 <b>병합 전체의 이름</b>이다(6차 B-3). 예전에는 "Merge: 첫 충돌 파일 충돌 해결" 을
|
// 병합 커밋 메시지 기본값은 <b>병합 전체의 이름</b>이다(6차 B-3). 예전에는 "Merge: 첫 충돌 파일 충돌 해결" 을
|
||||||
@ -1555,14 +1592,25 @@ namespace MergeHub.ViewModels
|
|||||||
|
|
||||||
private void ClearPanes()
|
private void ClearPanes()
|
||||||
{
|
{
|
||||||
|
// 비우기 전에 이력을 맡겨 둔다(11차 F-8). 새로고침 중에 잠깐 비웠다가 같은 파일 · 같은 글을 다시 싣는 일이 있다 -
|
||||||
|
// [저장 후 커밋] 뒤가 그랬다. 다른 파일이거나 글이 다르면 다음 불러오기가 버린다.
|
||||||
|
_parkedHistory = null;
|
||||||
|
if (_shownFile is not null && Lines.Count > 0)
|
||||||
|
{
|
||||||
|
FlattenHistory();
|
||||||
|
if (_textUndo.Count > 0 || _textRedo.Count > 0)
|
||||||
|
_parkedHistory = new ParkedHistory(_shownFile, BuildLocalText(), _textUndo.ToArray(), _textRedo.ToArray(),
|
||||||
|
_baseline.Opened, _baseline.Saved);
|
||||||
|
}
|
||||||
|
|
||||||
Lines.Clear();
|
Lines.Clear();
|
||||||
Files.Clear();
|
Files.Clear();
|
||||||
_undo.Clear();
|
ClearHistory();
|
||||||
_redo.Clear();
|
|
||||||
_syncingEditor = true;
|
_syncingEditor = true;
|
||||||
EditorTextReplaced?.Invoke(this, string.Empty);
|
EditorTextReplaced?.Invoke(this, string.Empty);
|
||||||
_syncingEditor = false;
|
_syncingEditor = false;
|
||||||
_editorLines = Array.Empty<string>();
|
_editorLines = Array.Empty<string>();
|
||||||
|
_typedOriginals.Clear(); // 편집기를 줄에서 다시 채웠다 - 친 것은 줄에 이미 들어갔다
|
||||||
_editorFiller = Array.Empty<bool>();
|
_editorFiller = Array.Empty<bool>();
|
||||||
|
|
||||||
InvalidateDiffBlocks();
|
InvalidateDiffBlocks();
|
||||||
@ -1573,6 +1621,7 @@ namespace MergeHub.ViewModels
|
|||||||
_baseSourceName = null;
|
_baseSourceName = null;
|
||||||
DiffSummary = "-";
|
DiffSummary = "-";
|
||||||
EditSummary = "-";
|
EditSummary = "-";
|
||||||
|
_baseline.Open(string.Empty);
|
||||||
HasChanges = false;
|
HasChanges = false;
|
||||||
_loadedAsConflict = false;
|
_loadedAsConflict = false;
|
||||||
_shownFile = null;
|
_shownFile = null;
|
||||||
@ -1846,7 +1895,7 @@ namespace MergeHub.ViewModels
|
|||||||
|
|
||||||
// 반영 표시는 되돌리기 스택 밖에도 남긴다 - 규칙을 바꾸면 스택이 비워지기 때문이다.
|
// 반영 표시는 되돌리기 스택 밖에도 남긴다 - 규칙을 바꾸면 스택이 비워지기 때문이다.
|
||||||
MarkApplied(line);
|
MarkApplied(line);
|
||||||
_redo.Clear();
|
ClearRedo();
|
||||||
|
|
||||||
if (group == 0) _workspace.AppendLog(description);
|
if (group == 0) _workspace.AppendLog(description);
|
||||||
if (requery) AfterEdit();
|
if (requery) AfterEdit();
|
||||||
@ -2069,7 +2118,7 @@ namespace MergeHub.ViewModels
|
|||||||
{
|
{
|
||||||
InsertedNote = InsertNoteAbove(line, chosenNote),
|
InsertedNote = InsertNoteAbove(line, chosenNote),
|
||||||
});
|
});
|
||||||
_redo.Clear();
|
ClearRedo();
|
||||||
}
|
}
|
||||||
|
|
||||||
_workspace.AppendLog(chosen);
|
_workspace.AppendLog(chosen);
|
||||||
@ -2098,7 +2147,7 @@ namespace MergeHub.ViewModels
|
|||||||
|
|
||||||
// 반영 표시는 되돌리기 스택 밖에도 남긴다 - 규칙을 바꾸면 스택이 비워지기 때문이다.
|
// 반영 표시는 되돌리기 스택 밖에도 남긴다 - 규칙을 바꾸면 스택이 비워지기 때문이다.
|
||||||
MarkApplied(line);
|
MarkApplied(line);
|
||||||
_redo.Clear();
|
ClearRedo();
|
||||||
|
|
||||||
_workspace.AppendLog(description);
|
_workspace.AppendLog(description);
|
||||||
AfterEdit();
|
AfterEdit();
|
||||||
@ -2172,7 +2221,7 @@ namespace MergeHub.ViewModels
|
|||||||
|
|
||||||
// 반영 표시는 되돌리기 스택 밖에도 남긴다 - 규칙을 바꾸면 스택이 비워지기 때문이다.
|
// 반영 표시는 되돌리기 스택 밖에도 남긴다 - 규칙을 바꾸면 스택이 비워지기 때문이다.
|
||||||
MarkApplied(line);
|
MarkApplied(line);
|
||||||
_redo.Clear();
|
ClearRedo();
|
||||||
|
|
||||||
if (group == 0) _workspace.AppendLog(description);
|
if (group == 0) _workspace.AppendLog(description);
|
||||||
if (requery) AfterEdit();
|
if (requery) AfterEdit();
|
||||||
@ -2278,8 +2327,28 @@ namespace MergeHub.ViewModels
|
|||||||
/// <summary>편집기 글을 통째로 갈아 끼운다. 새 글을 함께 넘긴다.</summary>
|
/// <summary>편집기 글을 통째로 갈아 끼운다. 새 글을 함께 넘긴다.</summary>
|
||||||
public event EventHandler<string>? EditorTextReplaced;
|
public event EventHandler<string>? EditorTextReplaced;
|
||||||
|
|
||||||
/// <summary>파일을 연 직후의 LOCAL 전문. "전체 되돌리기"의 기준점.</summary>
|
/// <summary>
|
||||||
private string _originalLocalText = string.Empty;
|
/// 파일을 연 시점 · 마지막으로 저장한 글(11차 F-8). 저장할 것이 있는지는 되돌리기 목록이 아니라
|
||||||
|
/// "지금 글이 마지막 저장과 다른가" 로 본다 - 그래서 저장해도 되돌리기 목록을 비우지 않는다.
|
||||||
|
/// </summary>
|
||||||
|
private readonly SaveBaseline _baseline = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 글 단위 되돌리기 · 다시하기(11차 F-8). 줄 단위 목록(<see cref="_undo"/>)은 줄 객체를 가리키므로
|
||||||
|
/// 줄을 다시 세우면(커밋 뒤 새 HEAD 로 다시 읽기 · 비교 규칙 바꾸기) 쓸 수 없다. 그 전에 각 지점의 <b>전문</b>을 여기로 옮긴다.
|
||||||
|
/// 여기 든 것은 줄 단위 목록보다 늘 먼 쪽이다(되돌리기는 더 옛날, 다시하기는 더 나중).
|
||||||
|
/// </summary>
|
||||||
|
private readonly Stack<string> _textUndo = new();
|
||||||
|
private readonly Stack<string> _textRedo = new();
|
||||||
|
|
||||||
|
/// <summary>화면을 비울 때 맡겨 둔 이력(11차 F-8). 맨 위가 첫 칸(가장 가까운 것) - <see cref="Stack{T}.ToArray"/> 차례.</summary>
|
||||||
|
private sealed record ParkedHistory(string File, string Text, string[] Undo, string[] Redo, string Opened, string Saved);
|
||||||
|
|
||||||
|
private ParkedHistory? _parkedHistory;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>파일을 연 직후의 LOCAL 전문. "전체 되돌리기"의 기준점 · 직접 고친 줄을 가리는 기준.</summary>
|
||||||
|
private string _originalLocalText => _baseline.Opened;
|
||||||
|
|
||||||
/// <summary>정렬 기준이 되는 BASE 전문. 편집 후 다시 정렬할 때 쓴다.</summary>
|
/// <summary>정렬 기준이 되는 BASE 전문. 편집 후 다시 정렬할 때 쓴다.</summary>
|
||||||
private string _baseTextSnapshot = string.Empty;
|
private string _baseTextSnapshot = string.Empty;
|
||||||
@ -2376,26 +2445,54 @@ namespace MergeHub.ViewModels
|
|||||||
// 글자가 그대로면 그릴 것이 없다. 이것도 "맞춰 둔 것"이라 true 다.
|
// 글자가 그대로면 그릴 것이 없다. 이것도 "맞춰 둔 것"이라 true 다.
|
||||||
if (string.Equals(_editorLines[line], value, StringComparison.Ordinal)) return true;
|
if (string.Equals(_editorLines[line], value, StringComparison.Ordinal)) return true;
|
||||||
|
|
||||||
|
// 친 줄의 원래 글을 한 번만 적어 둔다 - 재정렬 때 되돌리기 한 걸음으로 삼는다(11차 F-8).
|
||||||
|
if (!_typedOriginals.ContainsKey(line)) _typedOriginals[line] = Lines[line].LocalText;
|
||||||
_editorLines[line] = value;
|
_editorLines[line] = value;
|
||||||
Lines[line].LocalText = value;
|
Lines[line].LocalText = value;
|
||||||
DiffVersion++;
|
DiffVersion++;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 편집기에서 친 줄의 원래 글(줄 번호 → 글, 11차 F-8). 치는 동안 화면을 바로 보이려고 줄 객체를 먼저 고치는데
|
||||||
|
/// 되돌리기 목록에는 안 들어간다. 재정렬할 때 이것으로 친 것 전의 글을 되살려 한 걸음으로 올린다.
|
||||||
|
/// </summary>
|
||||||
|
private readonly Dictionary<int, string> _typedOriginals = new();
|
||||||
|
|
||||||
/// <summary>타이핑이 멈췄다. 필러를 걷어내고 BASE 와 다시 맞춘다.</summary>
|
/// <summary>타이핑이 멈췄다. 필러를 걷어내고 BASE 와 다시 맞춘다.</summary>
|
||||||
private void Reflow()
|
private void Reflow()
|
||||||
{
|
{
|
||||||
_reflowTimer.Stop();
|
_reflowTimer.Stop();
|
||||||
|
|
||||||
var current = SplitLines(CurrentEditorText);
|
var current = SplitLines(CurrentEditorText);
|
||||||
var real = EditorLayout.StripFillers(_editorLines, _editorFiller, current);
|
var real = string.Join("\n", EditorLayout.StripFillers(_editorLines, _editorFiller, current));
|
||||||
|
|
||||||
ReloadLinesFrom(string.Join("\n", real), keepAsEdited: true, moveSelection: false);
|
// 편집기에서 친 것도 되돌리기 한 걸음이다(11차 F-8). 예전에는 재정렬이 목록을 통째로 비워,
|
||||||
|
// 글자 하나만 쳐도 그 전의 Accept 들을 Undo 할 수 없었다. 친 줄을 원래 글로 돌려 친 것 전의 글을 얻고,
|
||||||
|
// 그 상태에서 목록을 글 단위로 옮긴 뒤 친 것 전의 글을 한 걸음으로 올린다.
|
||||||
|
foreach (var typed in _typedOriginals)
|
||||||
|
if (typed.Key < Lines.Count) Lines[typed.Key].LocalText = typed.Value;
|
||||||
|
_typedOriginals.Clear();
|
||||||
|
|
||||||
|
var before = BuildLocalText();
|
||||||
|
FlattenHistory();
|
||||||
|
if (!string.Equals(before, real, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
_textUndo.Push(before);
|
||||||
|
KeepNewest(_textUndo);
|
||||||
|
ClearRedo();
|
||||||
|
}
|
||||||
|
|
||||||
|
ReloadLinesFrom(real, keepAsEdited: true, moveSelection: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>주어진 LOCAL 전문으로 비교 화면을 다시 만든다.</summary>
|
/// <summary>주어진 LOCAL 전문으로 비교 화면을 다시 만든다.</summary>
|
||||||
private void ReloadLinesFrom(string localText, bool keepAsEdited, bool moveSelection = true)
|
private void ReloadLinesFrom(string localText, bool keepAsEdited, bool moveSelection = true)
|
||||||
{
|
{
|
||||||
|
// 줄을 다시 세우면 되돌리기 목록이 가리키던 줄이 사라진다 - 그 전에 목록을 글 단위로 옮겨 둔다(11차 F-8).
|
||||||
|
// 예전에는 비교 규칙 하나만 바꿔도 목록이 비었다. [전체 되돌리기](keepAsEdited 아님)만 목록을 비운다.
|
||||||
|
if (keepAsEdited) FlattenHistory();
|
||||||
|
|
||||||
// 3-Way 면 3-Way 로 다시 맞춰야 한다. 2-Way 로 만들면 REMOTE·LOCAL 원본 칸의
|
// 3-Way 면 3-Way 로 다시 맞춰야 한다. 2-Way 로 만들면 REMOTE·LOCAL 원본 칸의
|
||||||
// 내용이 통째로 사라진다(칸은 그대로 있고 글만 빈다).
|
// 내용이 통째로 사라진다(칸은 그대로 있고 글만 빈다).
|
||||||
var rows = IsThreeWay
|
var rows = IsThreeWay
|
||||||
@ -2431,13 +2528,13 @@ namespace MergeHub.ViewModels
|
|||||||
Lines.Add(row);
|
Lines.Add(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
_undo.Clear();
|
if (keepAsEdited) { _undo.Clear(); _redo.Clear(); } // 위에서 글로 옮겨 이미 비었다
|
||||||
_redo.Clear();
|
else ClearHistory();
|
||||||
|
|
||||||
if (moveSelection)
|
if (moveSelection)
|
||||||
SelectedLine = Lines.FirstOrDefault(l => l.IsDifferent) ?? Lines.FirstOrDefault();
|
SelectedLine = Lines.FirstOrDefault(l => l.IsDifferent) ?? Lines.FirstOrDefault();
|
||||||
|
|
||||||
HasChanges = !string.Equals(localText, _originalLocalText, StringComparison.Ordinal) || _styleRawLocalText is not null;
|
HasChanges = _baseline.IsDirty(localText, _styleRawLocalText is not null);
|
||||||
|
|
||||||
InvalidateDiffBlocks();
|
InvalidateDiffBlocks();
|
||||||
SyncEditorFromLines();
|
SyncEditorFromLines();
|
||||||
@ -2460,6 +2557,7 @@ namespace MergeHub.ViewModels
|
|||||||
var snapshot = EditorLayout.Build(rows);
|
var snapshot = EditorLayout.Build(rows);
|
||||||
|
|
||||||
_editorLines = snapshot.Lines;
|
_editorLines = snapshot.Lines;
|
||||||
|
_typedOriginals.Clear(); // 편집기를 줄에서 다시 채웠다 - 친 것은 줄에 이미 들어갔다
|
||||||
_editorFiller = snapshot.Filler;
|
_editorFiller = snapshot.Filler;
|
||||||
|
|
||||||
var text = string.Join(Environment.NewLine, snapshot.Lines);
|
var text = string.Join(Environment.NewLine, snapshot.Lines);
|
||||||
@ -2733,7 +2831,7 @@ namespace MergeHub.ViewModels
|
|||||||
|
|
||||||
SetPlainHeaders();
|
SetPlainHeaders();
|
||||||
|
|
||||||
_originalLocalText = BuildLocalText();
|
_baseline.Open(BuildLocalText());
|
||||||
SyncEditorFromLines();
|
SyncEditorFromLines();
|
||||||
|
|
||||||
var info = TextFile.Describe(RightFile);
|
var info = TextFile.Describe(RightFile);
|
||||||
@ -2741,8 +2839,7 @@ namespace MergeHub.ViewModels
|
|||||||
FileEncoding = info.Encoding;
|
FileEncoding = info.Encoding;
|
||||||
FileNewLine = info.NewLine;
|
FileNewLine = info.NewLine;
|
||||||
|
|
||||||
_undo.Clear();
|
ClearHistory();
|
||||||
_redo.Clear();
|
|
||||||
|
|
||||||
// 새로 읽은 파일에는 고친 것이 없다. 이걸 안 끄면 앞 파일의 상태가 그대로 끌려와
|
// 새로 읽은 파일에는 고친 것이 없다. 이걸 안 끄면 앞 파일의 상태가 그대로 끌려와
|
||||||
// 저장 단추가 켜진 채로 남고, 다음에 파일을 열 때마다 "저장하지 않은 편집" 을 묻는다.
|
// 저장 단추가 켜진 채로 남고, 다음에 파일을 열 때마다 "저장하지 않은 편집" 을 묻는다.
|
||||||
@ -2814,10 +2911,7 @@ namespace MergeHub.ViewModels
|
|||||||
|
|
||||||
_workspace.OnUi(() =>
|
_workspace.OnUi(() =>
|
||||||
{
|
{
|
||||||
_originalLocalText = content;
|
MarkSaved(content);
|
||||||
_undo.Clear();
|
|
||||||
_redo.Clear();
|
|
||||||
HasChanges = false;
|
|
||||||
MessageUtil.ShowSuccessToast($"저장 완료 · {Path.GetFileName(target)}");
|
MessageUtil.ShowSuccessToast($"저장 완료 · {Path.GetFileName(target)}");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -2967,10 +3061,7 @@ namespace MergeHub.ViewModels
|
|||||||
WritePlainFile(target, content, SaveEncoding, SaveNewLine);
|
WritePlainFile(target, content, SaveEncoding, SaveNewLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
_originalLocalText = content;
|
MarkSaved(content);
|
||||||
_undo.Clear();
|
|
||||||
_redo.Clear();
|
|
||||||
HasChanges = false;
|
|
||||||
|
|
||||||
MessageUtil.ShowSuccessToast($"저장 완료 · {Path.GetFileName(target)}");
|
MessageUtil.ShowSuccessToast($"저장 완료 · {Path.GetFileName(target)}");
|
||||||
return true;
|
return true;
|
||||||
@ -3503,9 +3594,104 @@ namespace MergeHub.ViewModels
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>새 편집을 했다 - 다시하기는 줄 단위 · 글 단위 모두 뜻을 잃는다.</summary>
|
||||||
|
private void ClearRedo()
|
||||||
|
{
|
||||||
|
_redo.Clear();
|
||||||
|
_textRedo.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>다른 파일을 열었거나 화면을 비웠다 - 이력을 모두 버린다.</summary>
|
||||||
|
private void ClearHistory()
|
||||||
|
{
|
||||||
|
_undo.Clear();
|
||||||
|
_redo.Clear();
|
||||||
|
_textUndo.Clear();
|
||||||
|
_textRedo.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>한 묶음(덩어리 · 메서드 반영은 여러 줄이 한 묶음, 11차 F-2)을 꺼낸다. 꺼낸 순서대로.</summary>
|
||||||
|
private static List<LineEdit> PopGroup(Stack<LineEdit> from)
|
||||||
|
{
|
||||||
|
var edit = from.Pop();
|
||||||
|
var group = new List<LineEdit> { edit };
|
||||||
|
while (edit.Group != 0 && from.Count > 0 && from.Peek().Group == edit.Group) group.Add(from.Pop());
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 줄 단위 이력을 글 단위로 옮긴다(11차 F-8). 줄을 다시 세우기 <b>전에</b> 부른다.
|
||||||
|
/// 되돌리기 쪽은 한 묶음씩 거꾸로 밟아 가며 그 지점의 전문을 적고, 다시하기 쪽은 앞으로 밟아 적은 뒤
|
||||||
|
/// 화면을 지금 상태로 돌려 놓는다. 한 묶음이 한 걸음이다 - Undo 한 번에 돌아가던 만큼 그대로 돌아간다.
|
||||||
|
/// </summary>
|
||||||
|
private void FlattenHistory()
|
||||||
|
{
|
||||||
|
if (_undo.Count == 0 && _redo.Count == 0) return;
|
||||||
|
|
||||||
|
var undoTexts = new List<string>(); // 가까운 것부터
|
||||||
|
var undone = new List<LineEdit>();
|
||||||
|
while (_undo.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (var e in PopGroup(_undo)) { Restore(e, toBefore: true); undone.Add(e); }
|
||||||
|
undoTexts.Add(BuildLocalText());
|
||||||
|
}
|
||||||
|
for (var i = undone.Count - 1; i >= 0; i--) Restore(undone[i], toBefore: false);
|
||||||
|
|
||||||
|
var redoTexts = new List<string>(); // 가까운 것부터
|
||||||
|
var redone = new List<LineEdit>();
|
||||||
|
while (_redo.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (var e in PopGroup(_redo)) { Restore(e, toBefore: false); redone.Add(e); }
|
||||||
|
redoTexts.Add(BuildLocalText());
|
||||||
|
}
|
||||||
|
for (var i = redone.Count - 1; i >= 0; i--) Restore(redone[i], toBefore: true);
|
||||||
|
|
||||||
|
// 이미 든 글 단위 이력은 더 먼 쪽이다 - 그 위에 먼 것부터 쌓아 가장 가까운 것이 맨 위.
|
||||||
|
for (var i = undoTexts.Count - 1; i >= 0; i--) _textUndo.Push(undoTexts[i]);
|
||||||
|
for (var i = redoTexts.Count - 1; i >= 0; i--) _textRedo.Push(redoTexts[i]);
|
||||||
|
KeepNewest(_textUndo);
|
||||||
|
KeepNewest(_textRedo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>글 단위 이력은 한 걸음이 파일 전문 한 벌이다 - 가장 먼 것부터 버려 이만큼만 둔다.</summary>
|
||||||
|
private const int TextHistoryLimit = 300;
|
||||||
|
|
||||||
|
private static void KeepNewest(Stack<string> stack)
|
||||||
|
{
|
||||||
|
if (stack.Count <= TextHistoryLimit) return;
|
||||||
|
var keep = stack.Take(TextHistoryLimit).Reverse().ToList(); // 맨 위(가까운 것)부터 300개
|
||||||
|
stack.Clear();
|
||||||
|
foreach (var text in keep) stack.Push(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>글 단위로 한 걸음(되돌리기 또는 다시하기). 지금 글을 반대쪽에 올리고 꺼낸 글로 줄을 다시 세운다.</summary>
|
||||||
|
private void StepText(Stack<string> from, Stack<string> to)
|
||||||
|
{
|
||||||
|
// 줄 단위 다시하기가 남아 있으면(줄 편집을 되돌리다 글 단위로 넘어옴) 그것도 먼저 글로 옮긴다 - 안 그러면 다시 세우며 사라진다.
|
||||||
|
FlattenHistory();
|
||||||
|
var target = from.Pop();
|
||||||
|
to.Push(BuildLocalText());
|
||||||
|
ReloadLinesFrom(target, keepAsEdited: true, moveSelection: false);
|
||||||
|
RaiseCanExecuteChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 파일에 썼다(11차 F-8). 되돌리기 · 다시하기 목록은 <b>두고</b> 저장 지점만 옮긴다 -
|
||||||
|
/// 저장한 뒤에도 Undo 로 저장 전 글로 갈 수 있다. 스타일 맞춤은 파일에 들어갔으니 끝났다.
|
||||||
|
/// </summary>
|
||||||
|
private void MarkSaved(string content)
|
||||||
|
{
|
||||||
|
_baseline.MarkSaved(content);
|
||||||
|
_styleRawLocalText = null;
|
||||||
|
HasChanges = _baseline.IsDirty(BuildLocalText(), stylePending: false);
|
||||||
|
UpdateSummaries();
|
||||||
|
RaiseCanExecuteChanged();
|
||||||
|
}
|
||||||
|
|
||||||
private void AfterEdit()
|
private void AfterEdit()
|
||||||
{
|
{
|
||||||
HasChanges = _undo.Count > 0 || _styleRawLocalText is not null;
|
// 저장한 뒤 Undo 하면 저장한 글과 달라져 "있음", Redo 로 저장 지점에 오면 "없음"(11차 F-8).
|
||||||
|
HasChanges = _baseline.IsDirty(BuildLocalText(), _styleRawLocalText is not null);
|
||||||
RefreshAppliedFlags();
|
RefreshAppliedFlags();
|
||||||
RefreshBlockCommentState(Lines);
|
RefreshBlockCommentState(Lines);
|
||||||
InvalidateDiffBlocks();
|
InvalidateDiffBlocks();
|
||||||
@ -3654,11 +3840,12 @@ namespace MergeHub.ViewModels
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 주석만 든 편집(덩어리 · 메서드 반영의 주석 한 줄, 11차 F-2)은 세지 않는다 - 편집 수는 바뀐 줄 수다.
|
// 주석만 든 편집(덩어리 · 메서드 반영의 주석 한 줄, 11차 F-2)은 세지 않는다 - 편집 수는 바뀐 줄 수다.
|
||||||
var edits = _undo.Count(e => e.Kind != EditKind.NoteOnly);
|
var edits = _undo.Count(e => e.Kind != EditKind.NoteOnly) + _textUndo.Count; // 글 단위는 한 걸음이 한 건(11차 F-8)
|
||||||
var redos = _redo.Count(e => e.Kind != EditKind.NoteOnly);
|
var redos = _redo.Count(e => e.Kind != EditKind.NoteOnly) + _textRedo.Count;
|
||||||
|
// 저장한 뒤에도 목록이 남으므로(11차 F-8) 저장한 지점인지를 같이 적는다.
|
||||||
EditSummary = edits == 0 && redos == 0
|
EditSummary = edits == 0 && redos == 0
|
||||||
? work
|
? work
|
||||||
: $"{work} · 편집 {edits}건 · 되돌리기 {redos}건";
|
: $"{work} · 편집 {edits}건 · 되돌리기 {redos}건{(HasChanges || edits == 0 ? string.Empty : " · 저장됨")}";
|
||||||
|
|
||||||
RefreshProblems();
|
RefreshProblems();
|
||||||
}
|
}
|
||||||
@ -3804,7 +3991,7 @@ namespace MergeHub.ViewModels
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_redo.Clear();
|
ClearRedo();
|
||||||
_workspace.AppendLog(description);
|
_workspace.AppendLog(description);
|
||||||
AfterEdit();
|
AfterEdit();
|
||||||
return changed;
|
return changed;
|
||||||
|
|||||||
@ -237,6 +237,7 @@
|
|||||||
<RowDefinition Height="Auto"/>
|
<RowDefinition Height="Auto"/>
|
||||||
<RowDefinition Height="*"/>
|
<RowDefinition Height="*"/>
|
||||||
<RowDefinition Height="Auto"/>
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<!-- 진행 한 줄(6차 C-1): "남은 충돌 N개 · 푼 것 M개 (M/전체)". 병합이 아니면 비어 접힌다. -->
|
<!-- 진행 한 줄(6차 C-1): "남은 충돌 N개 · 푼 것 M개 (M/전체)". 병합이 아니면 비어 접힌다. -->
|
||||||
@ -304,6 +305,37 @@
|
|||||||
<TextBlock Text="{Binding ConflictSelectionText}" Style="{StaticResource Muted}"
|
<TextBlock Text="{Binding ConflictSelectionText}" Style="{StaticResource Muted}"
|
||||||
VerticalAlignment="Center" Margin="8,0,0,0"/>
|
VerticalAlignment="Center" Margin="8,0,0,0"/>
|
||||||
</WrapPanel>
|
</WrapPanel>
|
||||||
|
|
||||||
|
<!-- 이번 병합에서 푼 파일(11차 F-8). [Save](해결로 저장)는 다음 충돌로 넘어가 편집기 Undo 로 돌아올 수 없다 - 여기서 다시 충돌로 되돌린다.
|
||||||
|
병합 중이고 푼 파일이 있을 때만 보인다. -->
|
||||||
|
<Grid Grid.Row="3" Margin="0,10,0,0">
|
||||||
|
<Grid.Style>
|
||||||
|
<Style TargetType="Grid">
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding Workspace.ResolvedConflicts.Count}" Value="0">
|
||||||
|
<Setter Property="Visibility" Value="Collapsed"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Grid.Style>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock Grid.ColumnSpan="2" Style="{StaticResource Muted}" Margin="0,0,0,4"
|
||||||
|
Text="{Binding Workspace.ResolvedConflicts.Count, StringFormat=이번 병합에서 푼 파일 {0}개 - 잘못 풀었으면 골라 [다시 충돌로]}"/>
|
||||||
|
<ListBox Grid.Row="1" ItemsSource="{Binding Workspace.ResolvedConflicts}" MaxHeight="120"
|
||||||
|
SelectedItem="{Binding SelectedResolvedConflict}"
|
||||||
|
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||||
|
AutomationProperties.Name="푼 파일"/>
|
||||||
|
<Button Grid.Row="1" Grid.Column="1" Content="다시 충돌로" Command="{Binding ReopenConflictCommand}"
|
||||||
|
MinWidth="100" Margin="6,0,0,0" VerticalAlignment="Top"
|
||||||
|
ToolTip="고른 파일을 푼 것을 되돌려 다시 충돌로 만듭니다. 저장해 둔 해결 내용은 충돌 표시 글로 바뀝니다"/>
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user