11차 F-9: 저장할 때 백업
- 설정 "저장할 때 백업"(기본 끔). 켜면 모든 저장 직전 지금 파일을 바이트 그대로 {설정 폴더}\Backup\MergeHub\날짜\... 로 복사(FileBackup)
- 두 파일 · mergetool · Save & Commit · 병합 중 Save · 파일 단위 Accept(IGitService.BeforeOverwrite) · 폴더 비교 복사
- 날짜 폴더마다 목록.txt, 저절로 지우지 않음, 못 만들면 저장하지 않음, ACTIONS [백업 폴더 열기]
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
a9cbb50884
commit
ce731c7b07
@ -166,6 +166,7 @@ A-1 에서 값으로 확인한 것 — 지운 파일은 "삭제되어 건너뜀
|
|||||||
- 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건.
|
- F-8: 매뉴얼 되돌리기 설명 — 저장 · 저장 후 커밋 뒤에도 Undo/Redo(커밋은 그대로, 화면만), 편집기에 친 것 · 규칙 바꾸기도 이어짐, 요약 `· 저장됨`, [전체 되돌리기] 는 파일을 연 시점(저장했으면 화면만). Git 탭 충돌 화면 "이번 병합에서 푼 파일" + [다시 충돌로](git.exe 필요 — 알아 둘 한계에도). 숫자 24벌 1,593건.
|
||||||
|
- F-9: 매뉴얼 설정 표 "저장 > 저장할 때 백업"(기본 끔) — 백업 자리 `{설정 폴더}\Backup\MergeHub\날짜\드라이브\원래 폴더\이름.날짜-시각.확장자` · 목록.txt, 모든 저장(병합 중 Save · 파일 단위 Accept · 폴더 복사 포함), 못 만들면 저장 안 함, 저절로 지우지 않음, ACTIONS [백업 폴더 열기]. 숫자 24벌 1,618건.
|
||||||
|
|
||||||
## B-1. 배포 묶음을 다시 만들고 꼭 필요한 파일을 가림 (2026-09-22, 완료)
|
## B-1. 배포 묶음을 다시 만들고 꼭 필요한 파일을 가림 (2026-09-22, 완료)
|
||||||
|
|
||||||
@ -843,6 +844,44 @@ libgit2 의 다듬기(`PrettifyMessages`)를 켜면 **손대지 않은 뒤 커
|
|||||||
- 세 칸(3-Way) 화면에서 규칙을 바꾸거나 치면 이력은 지키지만, 커밋 뒤 다시 읽기처럼 **파일에서 새로 읽는** 경우는 두 칸일 때만 지킨다(충돌 · 3-Way 는 저장이 곧 다음 충돌로 넘어감).
|
- 세 칸(3-Way) 화면에서 규칙을 바꾸거나 치면 이력은 지키지만, 커밋 뒤 다시 읽기처럼 **파일에서 새로 읽는** 경우는 두 칸일 때만 지킨다(충돌 · 3-Way 는 저장이 곧 다음 충돌로 넘어감).
|
||||||
- [다시 충돌로] 는 MergeHub 로 시작한 병합만(처음 충돌 목록이 있어야). rebase · cherry-pick · revert 중에는 보이지 않는다.
|
- [다시 충돌로] 는 MergeHub 로 시작한 병합만(처음 충돌 목록이 있어야). rebase · cherry-pick · revert 중에는 보이지 않는다.
|
||||||
|
|
||||||
|
## F-9. 저장할 때 백업 — 원본을 남기기 (2026-09-22, 완료)
|
||||||
|
|
||||||
|
사용자 결정: 방법은 **설정 "저장할 때 백업"**(기본 끔, [다른 이름으로 저장] 은 두지 않음), 자리는 **한곳에 모아 날짜 이름**, **저절로 지우지 않음**, 범위는 **모든 저장**(병합 중 Save 포함).
|
||||||
|
|
||||||
|
### 고치기 전
|
||||||
|
|
||||||
|
- 저장은 늘 원본을 덮어썼다. `.bak` 도 다른 이름으로 저장도 없었다. 덮어쓰는 자리 여섯: 두 파일 비교 [저장](`WritePlainFile`) · mergetool 저장(같은 함수) · Git [Save & Commit] · 병합 중 [Save] · Git 탭 파일 단위 Accept Mine/Theirs/Both · 폴더 비교 복사(파일 · 폴더).
|
||||||
|
|
||||||
|
### 고친 것
|
||||||
|
|
||||||
|
- 설정 `저장 > 저장할 때 백업` (`MergeHubSettings.BackupBeforeSave`, 기본 끔).
|
||||||
|
- `FileBackup`(새) — 쓰기 직전 지금 파일을 **바이트 그대로** 복사한다. 자리 `{설정 폴더}\Backup\MergeHub\2026-09-22\D\원래\폴더\A.20260922-210501.cs` — 날짜 폴더 아래 드라이브와 원래 폴더 모양 그대로, 이름에 날짜-시각(확장자는 끝이라 두 번 누르면 원래 프로그램으로 열림). 같은 초에 또 저장하면 `-2`. 네트워크 경로는 `UNC\서버\공유`. 240자를 넘으면 `_긴경로\폴더해시8_A.날짜-시각.cs`. 날짜 폴더마다 `목록.txt`(시각 · 원본 · 백업). 새로 만드는 저장(파일이 없음)은 백업할 것이 없다.
|
||||||
|
- 백업을 **못 만들면 저장하지 않는다** — "백업을 만들지 못해 저장하지 않았습니다 - 까닭 (설정을 끄면 백업 없이 저장)". 백업을 약속한 설정이라 원본을 그냥 덮지 않는다.
|
||||||
|
- 여섯 자리에 모두 붙임. Git 이 쓰는 길(병합 중 Save · 파일 단위 Accept · 머지 주석만 다른 충돌 자동 풀기)은 `IGitService.BeforeOverwrite` 로, 폴더 복사는 `FileOps.CopyDirectory` 의 덮어쓰기 직전 알림으로.
|
||||||
|
- 로그에 `백업: 원본 → 백업` 한 줄. 파일 비교 ACTIONS 저장 단추 밑 **[백업 폴더 열기]**(켰을 때만).
|
||||||
|
- 원본 폴더 · Git 저장소 밖에 모이므로 폴더 비교 · 변경분 추출 · git status 에 섞이지 않는다(따로 빼는 규칙이 필요 없음).
|
||||||
|
|
||||||
|
### 확인 (`f9-ui.ps1`, 설정을 켜고)
|
||||||
|
|
||||||
|
| 경우 | 결과 |
|
||||||
|
|---|---|
|
||||||
|
| 두 파일 비교 반영 → [저장] | 백업 1개 = 저장 전 파일(SHA-256 같음), 원본은 바뀜, 자리 `…\2026-09-22\C\Users\…\mergehub-f9\right.20260922-210729.cs`, 원본 폴더에는 `left.cs · right.cs` 뿐 |
|
||||||
|
| 저장 → Undo(F-8) → 다시 [저장] | 백업 2개, 두 번째 = 첫 저장 뒤 파일. `목록.txt` 두 줄 |
|
||||||
|
| 병합 직후 | 머지 주석만 다른 충돌 자동 풀기 2개도 백업 |
|
||||||
|
| Git 탭 Accept Theirs | 백업 = 충돌 표시 든 파일(해시 같음, `<<<<<<<` 줄 있음) |
|
||||||
|
| 3분할 [Save] | 백업 = 저장 전 파일 |
|
||||||
|
| 저장소 안 새 파일 | untracked 0 |
|
||||||
|
| 로그 | `백업: …\Motion\Unit0.cs → {백업}\2026-09-22\…\Unit0.20260922-211310.cs` |
|
||||||
|
| 끈 채(기본) | F-1~F-8 시험 내내 저장을 많이 했지만 백업 폴더가 생기지 않았다 · 하네스로도 확인 |
|
||||||
|
|
||||||
|
- 하네스 MergeSaveTest **+18건(317 → 335)** — 이름 규칙(날짜-시각 · 같은 초 `-2` · 점 파일 · 확장자 없음), 자리(드라이브 · UNC · 긴 경로), 끄면 아무것도 안 함, 켜면 바이트 그대로 · 원본 그대로, 없는 파일 · 빈 경로, 목록.txt, 알림, 원본 폴더에 안 생김, 못 만들면 던짐. FolderTest **+7건(81 → 88)** — 폴더 복사가 덮기 직전에만 알림 · 새 파일은 안 알림 · 알림이 던지면 안 덮음. Debug 빌드 경고 0 · 오류 0.
|
||||||
|
- 시험이 만든 백업 폴더(`D:\UbisamConfig\Backup`, 시험 전에 없었음)는 시험 끝에 치웠다.
|
||||||
|
|
||||||
|
### 남은 것
|
||||||
|
|
||||||
|
- 백업은 저절로 지워지지 않는다(사용자 결정) — 오래 쓰면 쌓인다. [백업 폴더 열기] 에서 날짜 폴더째 지우면 된다(매뉴얼에 적는다).
|
||||||
|
- 폴더 비교의 [삭제] 는 저장이 아니라 백업하지 않는다(지금처럼 먼저 묻는다).
|
||||||
|
|
||||||
## 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-9** — 저장할 때 백업: 설정 "저장할 때 백업"(기본 끔)을 켜면 모든 저장(두 파일 · mergetool · Save & Commit · 병합 중 Save · 파일 단위 Accept · 폴더 비교 복사) 직전에 지금 파일을 바이트 그대로 `{설정 폴더}\Backup\MergeHub\날짜\드라이브\원래 폴더\이름.날짜-시각.확장자` 로 복사(`FileBackup`, 날짜 폴더마다 목록.txt, 저절로 안 지움 — 사용자 결정). 원본 폴더 · 저장소 밖이라 폴더 비교 · 변경분 · git status 에 안 섞임. 못 만들면 저장하지 않음 | 아래 |
|
||||||
| 2026-09-22 | **11차 F-8** — 저장한 뒤에도 Undo/Redo: 저장하는 길마다 되돌리기 목록을 비우던 것을 두고, "저장할 것 있음" 을 목록이 아니라 "지금 글이 마지막 저장과 다른가" 로(`SaveBaseline`). 커밋 뒤 새 HEAD 로 다시 읽기 · 비교 규칙 바꾸기 · 편집기에 친 뒤 재정렬처럼 줄을 다시 세울 때 이력을 글 단위로 옮겨 지킴(친 것도 한 걸음, 300걸음까지). 전체 되돌리기는 파일을 연 시점. 병합 중 푼 파일은 Git 탭 [다시 충돌로] 로 되돌림 — LibGit2Sharp 에 길이 없어 이 단추만 `git checkout -m`(사용자 결정), 해결 기록에서도 뺌 | 아래 |
|
| 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 안팎)는 기록에 | 아래 |
|
||||||
|
|||||||
@ -378,7 +378,7 @@
|
|||||||
| **완료** | 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 저장한 뒤에도 되돌리기 — 저장 · 저장 후 커밋 뒤 Undo/Redo 유지(저장할 것 있음은 "마지막 저장과 다른가" 로), 줄을 다시 세울 때(커밋 뒤 새 HEAD · 규칙 바꾸기 · 친 뒤 재정렬) 이력을 글 단위로 옮겨 지킴, 친 것도 한 걸음. 전체 되돌리기는 파일을 연 시점. Git 탭 "푼 파일" + [다시 충돌로](이 단추만 git.exe, 사용자 결정). MergeSaveTest +21 |
|
| **완료** | F-8 저장한 뒤에도 되돌리기 — 저장 · 저장 후 커밋 뒤 Undo/Redo 유지(저장할 것 있음은 "마지막 저장과 다른가" 로), 줄을 다시 세울 때(커밋 뒤 새 HEAD · 규칙 바꾸기 · 친 뒤 재정렬) 이력을 글 단위로 옮겨 지킴, 친 것도 한 걸음. 전체 되돌리기는 파일을 연 시점. Git 탭 "푼 파일" + [다시 충돌로](이 단추만 git.exe, 사용자 결정). MergeSaveTest +21 |
|
||||||
| 대기 | F-9 백업본으로 저장 · 원본을 건드릴지 고르게 |
|
| **완료** | F-9 저장할 때 백업 — 설정(기본 끔)을 켜면 모든 저장(두 파일 · mergetool · Save & Commit · 병합 중 Save · 파일 단위 Accept · 폴더 복사) 직전에 지금 파일을 바이트 그대로 `{설정 폴더}\Backup\MergeHub\날짜\…` 에 모음(목록.txt, 저절로 안 지움). 못 만들면 저장 안 함. ACTIONS [백업 폴더 열기]. 하네스 +25 |
|
||||||
| 대기 | F-10 덩어리가 어디로 갔는지 UI 로 잇기 (지금은 "자리 옮김" 색만) |
|
| 대기 | F-10 덩어리가 어디로 갔는지 UI 로 잇기 (지금은 "자리 옮김" 색만) |
|
||||||
| 대기 | F-11 Git 계정 · URL · 암호 저장 확인 (지금은 주소 · 계정만 저장, 토큰은 메모리) |
|
| 대기 | F-11 Git 계정 · URL · 암호 저장 확인 (지금은 주소 · 계정만 저장, 토큰은 메모리) |
|
||||||
| **완료** | F-12 C# 코드 스타일 맞춤 기본값을 AURORA 로 (2026-09-18 사용자 결정) — 설정 없는 첫 실행에서 AURORA, 중괄호만 다른 C# 짝 "차이 없음" · 같은 모양의 C++ 짝은 "남은 차이 4줄"(원본 그대로). 저장 동작은 그대로. 하네스 StyleTest +12(90건) |
|
| **완료** | F-12 C# 코드 스타일 맞춤 기본값을 AURORA 로 (2026-09-18 사용자 결정) — 설정 없는 첫 실행에서 AURORA, 중괄호만 다른 C# 짝 "차이 없음" · 같은 모양의 C++ 짝은 "남은 차이 4줄"(원본 그대로). 저장 동작은 그대로. 하네스 StyleTest +12(90건) |
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
|
||||||
namespace MergeHub
|
namespace MergeHub
|
||||||
{
|
{
|
||||||
@ -103,6 +103,15 @@ namespace MergeHub
|
|||||||
[DisplayName("코드 글자 크기 (8~24, 기본 12)")]
|
[DisplayName("코드 글자 크기 (8~24, 기본 12)")]
|
||||||
public int CodeFontSize { get; set; } = 12;
|
public int CodeFontSize { get; set; } = 12;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 저장하기 전 파일을 백업해 둔다(11차 F-9). 모든 저장 - 두 파일 비교 · mergetool · Save & Commit · 병합 중 Save ·
|
||||||
|
/// 파일 단위 Accept · 폴더 비교 복사. 자리는 {설정 폴더}\Backup\MergeHub\날짜 아래 원래 폴더 모양 그대로(<see cref="Services.FileBackup"/>).
|
||||||
|
/// 저절로 지우지 않는다(사용자 결정). 기본은 끔.
|
||||||
|
/// </summary>
|
||||||
|
[Category("저장")]
|
||||||
|
[DisplayName("저장할 때 백업 (저장 전 파일을 설정 폴더\\Backup\\MergeHub 에 날짜별로 모음 · 저절로 지우지 않음)")]
|
||||||
|
public bool BackupBeforeSave { get; set; }
|
||||||
|
|
||||||
[Category("머지 주석")]
|
[Category("머지 주석")]
|
||||||
[DisplayName("반영한 줄에 주석 남기기")]
|
[DisplayName("반영한 줄에 주석 남기기")]
|
||||||
public bool InsertMergeComment { get; set; } = true;
|
public bool InsertMergeComment { get; set; } = true;
|
||||||
|
|||||||
144
src/MergeHub/Services/FileBackup.cs
Normal file
144
src/MergeHub/Services/FileBackup.cs
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace MergeHub.Services
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 저장하기 전 파일을 백업해 둔다(11차 F-9, 설정 "저장할 때 백업" 을 켰을 때만).
|
||||||
|
///
|
||||||
|
/// 사용자 결정: 원본 옆이 아니라 <b>한곳에 모은다</b> — <c>{설정 폴더}\Backup\MergeHub\날짜\드라이브\원래 폴더\이름.날짜-시각.확장자</c>.
|
||||||
|
/// 원본 폴더 · Git 저장소에 파일이 생기지 않으니 폴더 비교 · 변경분 추출 · git status 에 섞이지 않는다.
|
||||||
|
/// <b>지우지 않는다</b>(사용자 결정) — 날짜 폴더마다 <c>목록.txt</c> 에 원본과 백업을 한 줄씩 적어 찾기 쉽게 한다.
|
||||||
|
/// 저장하는 모든 길(두 파일 비교 · mergetool · Save & Commit · 병합 중 Save · 파일 단위 Accept · 폴더 비교 복사)이 쓰기 직전에 부른다.
|
||||||
|
/// 백업을 못 만들면 던진다 - 부르는 쪽은 저장하지 않는다(백업을 약속한 설정이라 원본을 그냥 덮지 않는다).
|
||||||
|
/// </summary>
|
||||||
|
public sealed class FileBackup
|
||||||
|
{
|
||||||
|
private readonly Func<bool> _enabled;
|
||||||
|
private readonly Func<DateTime> _now;
|
||||||
|
private readonly object _gate = new object();
|
||||||
|
|
||||||
|
/// <summary>백업을 모으는 폴더.</summary>
|
||||||
|
public string Root { get; }
|
||||||
|
|
||||||
|
/// <summary>백업을 만들면 원본 · 백업 경로로 알린다(로그).</summary>
|
||||||
|
public event Action<string, string>? Kept;
|
||||||
|
|
||||||
|
public FileBackup(string root, Func<bool> enabled, Func<DateTime>? now = null)
|
||||||
|
{
|
||||||
|
Root = root;
|
||||||
|
_enabled = enabled;
|
||||||
|
_now = now ?? (() => DateTime.Now);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Enabled => _enabled();
|
||||||
|
|
||||||
|
/// <summary>설정 폴더 아래 백업 자리.</summary>
|
||||||
|
public static string DefaultRoot(string configRoot) => Path.Combine(configRoot, "Backup", "MergeHub");
|
||||||
|
|
||||||
|
/// <summary>날짜 폴더마다 남기는 목록 파일 이름.</summary>
|
||||||
|
public const string ListFileName = "목록.txt";
|
||||||
|
|
||||||
|
/// <summary>경로가 이보다 길면 원래 폴더 모양을 버리고 짧은 자리로(net48 은 260자를 넘으면 못 쓴다).</summary>
|
||||||
|
private const int MaxPath = 240;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 켜져 있고 파일이 있으면 지금 바이트 그대로 백업하고 그 경로를 준다. 꺼져 있거나 파일이 없으면(새로 만드는 저장) null.
|
||||||
|
/// 못 만들면 <see cref="InvalidOperationException"/> - 까닭을 담는다.
|
||||||
|
/// </summary>
|
||||||
|
public string? Keep(string? path)
|
||||||
|
{
|
||||||
|
if (!_enabled() || string.IsNullOrWhiteSpace(path)) return null;
|
||||||
|
|
||||||
|
string full;
|
||||||
|
try { full = Path.GetFullPath(path); }
|
||||||
|
catch (Exception ex) when (ex is ArgumentException || ex is NotSupportedException || ex is PathTooLongException)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"백업을 만들지 못해 저장하지 않았습니다 - 경로를 읽을 수 없습니다: {path}", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!File.Exists(full)) return null;
|
||||||
|
|
||||||
|
lock (_gate)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var when = _now();
|
||||||
|
var target = PathFor(Root, full, when);
|
||||||
|
for (var n = 2; File.Exists(target); n++) target = PathFor(Root, full, when, n); // 같은 초에 또 저장
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(target)!);
|
||||||
|
File.Copy(full, target, overwrite: false);
|
||||||
|
AppendList(Path.Combine(Root, when.ToString("yyyy-MM-dd")), when, full, target);
|
||||||
|
Kept?.Invoke(full, target);
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException || ex is NotSupportedException)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"백업을 만들지 못해 저장하지 않았습니다 - {ex.Message} (설정 \"저장할 때 백업\" 을 끄면 백업 없이 저장합니다)", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 백업 경로(저장소 없이 검사). <c>D:\Src\A.cs</c> → <c>{root}\2026-09-22\D\Src\A.20260922-210501.cs</c>,
|
||||||
|
/// 네트워크 경로 <c>\\서버\공유\A.cs</c> → <c>{root}\날짜\UNC\서버\공유\A.날짜-시각.cs</c>.
|
||||||
|
/// 너무 길면 <c>{root}\날짜\_긴경로\폴더해시8_A.날짜-시각.cs</c>(원래 자리는 목록.txt 에 남는다).
|
||||||
|
/// </summary>
|
||||||
|
public static string PathFor(string root, string fullPath, DateTime when, int number = 1)
|
||||||
|
{
|
||||||
|
var day = when.ToString("yyyy-MM-dd");
|
||||||
|
var name = StampedName(Path.GetFileName(fullPath), when, number);
|
||||||
|
var folder = Path.GetDirectoryName(fullPath) ?? string.Empty;
|
||||||
|
|
||||||
|
var candidate = Path.Combine(root, day, Mirror(folder), name);
|
||||||
|
if (candidate.Length <= MaxPath) return candidate;
|
||||||
|
|
||||||
|
return Path.Combine(root, day, "_긴경로", Hash8(folder) + "_" + name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <c>A.cs</c> → <c>A.20260922-210501.cs</c>. 확장자를 끝에 두어 두 번 누르면 원래 프로그램으로 열린다.
|
||||||
|
/// 같은 초에 두 번째면 <c>A.20260922-210501-2.cs</c>.
|
||||||
|
/// </summary>
|
||||||
|
public static string StampedName(string fileName, DateTime when, int number = 1)
|
||||||
|
{
|
||||||
|
var stamp = when.ToString("yyyyMMdd-HHmmss") + (number > 1 ? "-" + number : string.Empty);
|
||||||
|
var stem = Path.GetFileNameWithoutExtension(fileName);
|
||||||
|
var ext = Path.GetExtension(fileName);
|
||||||
|
// ".gitignore" · 확장자 없는 "Makefile" 은 이름 뒤에 붙인다
|
||||||
|
return string.IsNullOrEmpty(stem) || string.IsNullOrEmpty(ext)
|
||||||
|
? fileName + "." + stamp
|
||||||
|
: stem + "." + stamp + ext;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>원래 폴더를 백업 폴더 안의 상대 경로로. <c>D:\Src</c> → <c>D\Src</c>, <c>\\서버\공유\x</c> → <c>UNC\서버\공유\x</c>.</summary>
|
||||||
|
private static string Mirror(string folder)
|
||||||
|
{
|
||||||
|
if (folder.StartsWith(@"\\", StringComparison.Ordinal))
|
||||||
|
return Path.Combine("UNC", folder.TrimStart('\\'));
|
||||||
|
|
||||||
|
if (folder.Length >= 2 && folder[1] == ':')
|
||||||
|
return folder.Substring(0, 1).ToUpperInvariant() + folder.Substring(2); // "D" + "\Src"
|
||||||
|
|
||||||
|
return folder.TrimStart('\\');
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AppendList(string dayFolder, DateTime when, string original, string backup)
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(dayFolder);
|
||||||
|
File.AppendAllText(Path.Combine(dayFolder, ListFileName),
|
||||||
|
$"{when:HH:mm:ss}\t{original}\t{backup}\r\n", new UTF8Encoding(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Hash8(string text)
|
||||||
|
{
|
||||||
|
using var sha = SHA1.Create();
|
||||||
|
var bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(text.ToUpperInvariant()));
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
for (var i = 0; i < 4; i++) sb.Append(bytes[i].ToString("x2"));
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
namespace MergeHub.Services
|
namespace MergeHub.Services
|
||||||
@ -35,15 +36,20 @@ namespace MergeHub.Services
|
|||||||
/// 폴더를 통째로 복사한다. 대상에 이미 있는 파일은 덮어쓰고, 대상에만 있는 파일은 그대로 둔다.
|
/// 폴더를 통째로 복사한다. 대상에 이미 있는 파일은 덮어쓰고, 대상에만 있는 파일은 그대로 둔다.
|
||||||
/// (지우면서 맞추는 "동기화"가 아니다 - 지우는 것은 삭제 버튼이 따로 맡는다)
|
/// (지우면서 맞추는 "동기화"가 아니다 - 지우는 것은 삭제 버튼이 따로 맡는다)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static void CopyDirectory(string from, string to)
|
/// <param name="beforeOverwrite">있는 파일을 덮어쓰기 직전에 부른다(11차 F-9 백업). 던지면 그 파일부터 멈춘다.</param>
|
||||||
|
public static void CopyDirectory(string from, string to, Action<string>? beforeOverwrite = null)
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(to);
|
Directory.CreateDirectory(to);
|
||||||
|
|
||||||
foreach (var file in Directory.GetFiles(from))
|
foreach (var file in Directory.GetFiles(from))
|
||||||
File.Copy(file, Path.Combine(to, Path.GetFileName(file)), overwrite: true);
|
{
|
||||||
|
var target = Path.Combine(to, Path.GetFileName(file));
|
||||||
|
if (beforeOverwrite is not null && File.Exists(target)) beforeOverwrite(target);
|
||||||
|
File.Copy(file, target, overwrite: true);
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var child in Directory.GetDirectories(from))
|
foreach (var child in Directory.GetDirectories(from))
|
||||||
CopyDirectory(child, Path.Combine(to, Path.GetFileName(child)));
|
CopyDirectory(child, Path.Combine(to, Path.GetFileName(child)), beforeOverwrite);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -46,6 +46,9 @@ namespace MergeHub.Services
|
|||||||
|
|
||||||
public bool WordLevelDiff { get; set; }
|
public bool WordLevelDiff { get; set; }
|
||||||
|
|
||||||
|
/// <summary>워킹트리 파일을 덮어쓰거나 지우기 직전(11차 F-9 백업). 던지면 쓰지 않는다.</summary>
|
||||||
|
public Action<string>? BeforeOverwrite { get; set; }
|
||||||
|
|
||||||
public event EventHandler<string>? Progress;
|
public event EventHandler<string>? Progress;
|
||||||
|
|
||||||
private Repository Repo =>
|
private Repository Repo =>
|
||||||
@ -1218,6 +1221,8 @@ namespace MergeHub.Services
|
|||||||
var directory = Path.GetDirectoryName(fullPath);
|
var directory = Path.GetDirectoryName(fullPath);
|
||||||
if (!string.IsNullOrEmpty(directory)) Directory.CreateDirectory(directory);
|
if (!string.IsNullOrEmpty(directory)) Directory.CreateDirectory(directory);
|
||||||
|
|
||||||
|
BeforeOverwrite?.Invoke(fullPath); // 충돌 표시가 든 지금 파일(11차 F-9)
|
||||||
|
|
||||||
if (resolution == ConflictResolution.Both)
|
if (resolution == ConflictResolution.Both)
|
||||||
{
|
{
|
||||||
// 양쪽 파일을 읽는다. 한쪽이 없으면 빈 것으로 본다.
|
// 양쪽 파일을 읽는다. 한쪽이 없으면 빈 것으로 본다.
|
||||||
@ -1271,6 +1276,7 @@ namespace MergeHub.Services
|
|||||||
? ReadBlobBytes(referenceBlob, normalized)
|
? ReadBlobBytes(referenceBlob, normalized)
|
||||||
: File.Exists(fullPath) ? File.ReadAllBytes(fullPath) : null;
|
: File.Exists(fullPath) ? File.ReadAllBytes(fullPath) : null;
|
||||||
|
|
||||||
|
BeforeOverwrite?.Invoke(fullPath); // 11차 F-9
|
||||||
File.WriteAllBytes(fullPath, TextFile.Encode(content, encodingName, newLineName, reference));
|
File.WriteAllBytes(fullPath, TextFile.Encode(content, encodingName, newLineName, reference));
|
||||||
Commands.Stage(Repo, normalized);
|
Commands.Stage(Repo, normalized);
|
||||||
|
|
||||||
|
|||||||
@ -62,6 +62,12 @@ namespace MergeHub.Services
|
|||||||
Git.IgnoreWhitespace = Settings.IgnoreWhitespace;
|
Git.IgnoreWhitespace = Settings.IgnoreWhitespace;
|
||||||
Git.WordLevelDiff = Settings.WordLevelDiff;
|
Git.WordLevelDiff = Settings.WordLevelDiff;
|
||||||
|
|
||||||
|
// 저장할 때 백업(11차 F-9). Git 이 워킹트리 파일을 쓰는 길(병합 중 Save · 파일 단위 Accept)도 같은 것을 부른다.
|
||||||
|
Backup = new FileBackup(FileBackup.DefaultRoot(UbisamBase.Core.Bootstrap.UbisamAppBuilder.ConfigRoot),
|
||||||
|
() => Settings.BackupBeforeSave);
|
||||||
|
Backup.Kept += (original, backup) => AppendLog($"백업: {original} → {backup}");
|
||||||
|
Git.BeforeOverwrite = path => Backup.Keep(path);
|
||||||
|
|
||||||
// 작성자 이름은 여기서 한 번 정해 둔다.
|
// 작성자 이름은 여기서 한 번 정해 둔다.
|
||||||
// 저장소를 읽을 때(ReadState)만 정하면 Git 없이 파일 두 개를 비교할 때 이름이 비고,
|
// 저장소를 읽을 때(ReadState)만 정하면 Git 없이 파일 두 개를 비교할 때 이름이 비고,
|
||||||
// 그러면 주석에 등록한 이름 대신 윈도우 계정이 찍힌다.
|
// 그러면 주석에 등록한 이름 대신 윈도우 계정이 찍힌다.
|
||||||
@ -123,6 +129,9 @@ namespace MergeHub.Services
|
|||||||
|
|
||||||
public MergeHubSettings Settings { get; }
|
public MergeHubSettings Settings { get; }
|
||||||
|
|
||||||
|
/// <summary>저장할 때 백업(11차 F-9). 설정이 꺼져 있으면 아무것도 하지 않는다.</summary>
|
||||||
|
public FileBackup Backup { get; }
|
||||||
|
|
||||||
private readonly ISettingsService _settingsService;
|
private readonly ISettingsService _settingsService;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -203,6 +203,9 @@ namespace MergeHub.Services
|
|||||||
/// <summary>diff 결과의 각 줄에 단어 단위 변경 구간(DiffLine.Words)을 채운다. (기획안 4p)</summary>
|
/// <summary>diff 결과의 각 줄에 단어 단위 변경 구간(DiffLine.Words)을 채운다. (기획안 4p)</summary>
|
||||||
bool WordLevelDiff { get; set; }
|
bool WordLevelDiff { get; set; }
|
||||||
|
|
||||||
|
/// <summary>워킹트리 파일을 덮어쓰거나 지우기 직전에 부른다(11차 F-9 백업). 던지면 쓰지 않는다.</summary>
|
||||||
|
Action<string>? BeforeOverwrite { get; set; }
|
||||||
|
|
||||||
IReadOnlyList<FileDiff> DiffCommits(string oldCommittish, string newCommittish, params string[] paths);
|
IReadOnlyList<FileDiff> DiffCommits(string oldCommittish, string newCommittish, params string[] paths);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -753,7 +753,7 @@ namespace MergeHub.ViewModels
|
|||||||
|
|
||||||
if (!Confirm(question, "폴더 복사")) return;
|
if (!Confirm(question, "폴더 복사")) return;
|
||||||
|
|
||||||
Run(() => FileOps.CopyDirectory(from, to), $"복사: {from} → {to} (파일 {files}개)");
|
Run(() => FileOps.CopyDirectory(from, to, path => _workspace.Backup.Keep(path)), $"복사: {from} → {to} (파일 {files}개)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -771,6 +771,7 @@ namespace MergeHub.ViewModels
|
|||||||
{
|
{
|
||||||
var folder = Path.GetDirectoryName(to);
|
var folder = Path.GetDirectoryName(to);
|
||||||
if (!string.IsNullOrEmpty(folder)) Directory.CreateDirectory(folder);
|
if (!string.IsNullOrEmpty(folder)) Directory.CreateDirectory(folder);
|
||||||
|
_workspace.Backup.Keep(to); // 덮어쓰기 전(11차 F-9) - 못 만들면 던져 복사하지 않는다
|
||||||
File.Copy(from, to, overwrite: true);
|
File.Copy(from, to, overwrite: true);
|
||||||
}, $"복사: {from} → {to}");
|
}, $"복사: {from} → {to}");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -269,6 +269,7 @@ namespace MergeHub.ViewModels
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void WritePlainFile(string target, string content, string? encoding, string? newLine)
|
private void WritePlainFile(string target, string content, string? encoding, string? newLine)
|
||||||
{
|
{
|
||||||
|
_workspace.Backup.Keep(target); // 11차 F-9 - 못 만들면 던져 쓰지 않는다
|
||||||
if (_mergeTool is { } tool && File.Exists(tool.Local) &&
|
if (_mergeTool is { } tool && File.Exists(tool.Local) &&
|
||||||
string.Equals(Path.GetFullPath(target), Path.GetFullPath(tool.Merged), StringComparison.OrdinalIgnoreCase))
|
string.Equals(Path.GetFullPath(target), Path.GetFullPath(tool.Merged), StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
@ -437,6 +438,24 @@ namespace MergeHub.ViewModels
|
|||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private void FontReset() => CodeFontSize = CodeFontMetrics.Default;
|
private void FontReset() => CodeFontSize = CodeFontMetrics.Default;
|
||||||
|
|
||||||
|
/// <summary>설정 "저장할 때 백업" 이 켜져 있는지(11차 F-9). 켜져 있으면 저장 단추 아래 [백업 폴더] 가 보인다.</summary>
|
||||||
|
public bool IsBackupOn => _workspace.Settings.BackupBeforeSave;
|
||||||
|
|
||||||
|
/// <summary>백업을 모으는 폴더를 탐색기로 연다(11차 F-9). 아직 없으면 만든다.</summary>
|
||||||
|
[RelayCommand]
|
||||||
|
private void OpenBackupFolder()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(_workspace.Backup.Root);
|
||||||
|
System.Diagnostics.Process.Start("explorer.exe", "\"" + _workspace.Backup.Root + "\"");
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException || ex is System.ComponentModel.Win32Exception)
|
||||||
|
{
|
||||||
|
MessageUtil.ShowErrorToast($"백업 폴더를 열지 못했습니다: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private void ToggleActions()
|
private void ToggleActions()
|
||||||
{
|
{
|
||||||
@ -957,6 +976,7 @@ namespace MergeHub.ViewModels
|
|||||||
path!.Replace('/', Path.DirectorySeparatorChar));
|
path!.Replace('/', Path.DirectorySeparatorChar));
|
||||||
|
|
||||||
// 저장 형식을 고른 값으로 쓴다("원본 유지"면 지금까지와 같다).
|
// 저장 형식을 고른 값으로 쓴다("원본 유지"면 지금까지와 같다).
|
||||||
|
_workspace.Backup.Keep(full); // 11차 F-9
|
||||||
TextFile.Write(full, content, encoding, newLine);
|
TextFile.Write(full, content, encoding, newLine);
|
||||||
_workspace.Git.Stage(path!);
|
_workspace.Git.Stage(path!);
|
||||||
}
|
}
|
||||||
@ -1041,6 +1061,7 @@ namespace MergeHub.ViewModels
|
|||||||
// 충돌은 아니지만 병합 중에 손본 파일 - 저장 형식을 따라 쓰고 스테이징한다.
|
// 충돌은 아니지만 병합 중에 손본 파일 - 저장 형식을 따라 쓰고 스테이징한다.
|
||||||
var full = Path.Combine(_workspace.RepositoryPath,
|
var full = Path.Combine(_workspace.RepositoryPath,
|
||||||
path!.Replace('/', Path.DirectorySeparatorChar));
|
path!.Replace('/', Path.DirectorySeparatorChar));
|
||||||
|
_workspace.Backup.Keep(full); // 11차 F-9
|
||||||
TextFile.Write(full, content, encoding, newLine);
|
TextFile.Write(full, content, encoding, newLine);
|
||||||
_workspace.Git.Stage(path!);
|
_workspace.Git.Stage(path!);
|
||||||
}
|
}
|
||||||
@ -3135,6 +3156,7 @@ namespace MergeHub.ViewModels
|
|||||||
{
|
{
|
||||||
var font = CodeFontMetrics.Clamp(_workspace.Settings.CodeFontSize);
|
var font = CodeFontMetrics.Clamp(_workspace.Settings.CodeFontSize);
|
||||||
if (font != CodeFontSize) CodeFontSize = font;
|
if (font != CodeFontSize) CodeFontSize = font;
|
||||||
|
OnPropertyChanged(nameof(IsBackupOn)); // 11차 F-9 - 설정에서 켜고 끄면 [백업 폴더] 단추가 따라간다
|
||||||
|
|
||||||
// C# 스타일은 원본부터 다시 읽어야 맞춰진다(편집 칸만 다시 정렬하면 BASE 가 옛 스타일로 남는다).
|
// C# 스타일은 원본부터 다시 읽어야 맞춰진다(편집 칸만 다시 정렬하면 BASE 가 옛 스타일로 남는다).
|
||||||
if (_workspace.Settings.CSharpBraceStyle != _appliedStyle && Lines.Count > 0)
|
if (_workspace.Settings.CSharpBraceStyle != _appliedStyle && Lines.Count > 0)
|
||||||
|
|||||||
@ -1450,6 +1450,12 @@
|
|||||||
<Border x:Name="ActionsPanel" Grid.Column="8" Grid.RowSpan="3" BorderThickness="1" Padding="8"
|
<Border x:Name="ActionsPanel" Grid.Column="8" Grid.RowSpan="3" BorderThickness="1" Padding="8"
|
||||||
BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}">
|
BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}">
|
||||||
<DockPanel>
|
<DockPanel>
|
||||||
|
<!-- 설정 "저장할 때 백업" 을 켰을 때만(11차 F-9). 맨 아래 - 저장 단추들 밑. -->
|
||||||
|
<Button DockPanel.Dock="Bottom" Content="백업 폴더 열기" Margin="0,6,0,0" FontSize="11"
|
||||||
|
Command="{Binding OpenBackupFolderCommand}"
|
||||||
|
Visibility="{Binding IsBackupOn, Converter={StaticResource BoolToVis}}"
|
||||||
|
ToolTip="저장할 때 남긴 백업을 모아 둔 폴더를 엽니다. 날짜 폴더마다 목록.txt 에 원본과 백업이 적혀 있습니다"/>
|
||||||
|
|
||||||
<!-- Git 이 아니면 커밋할 곳이 없다. 파일에 쓰기만 한다. -->
|
<!-- Git 이 아니면 커밋할 곳이 없다. 파일에 쓰기만 한다. -->
|
||||||
<Button DockPanel.Dock="Bottom" Content="저장" MinHeight="38" Margin="0,8,0,0"
|
<Button DockPanel.Dock="Bottom" Content="저장" MinHeight="38" Margin="0,8,0,0"
|
||||||
Command="{Binding SavePlainCommand}"
|
Command="{Binding SavePlainCommand}"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user