diff --git a/doc/history/2026-09-18-11차.md b/doc/history/2026-09-18-11차.md
index bb9253c..e9e60ae 100644
--- a/doc/history/2026-09-18-11차.md
+++ b/doc/history/2026-09-18-11차.md
@@ -61,6 +61,41 @@
- 폴더 창의 경로 칸(`id 1152`)과 [이 폴더 선택](`id 1`)은 UIA 에서 **Pane** 으로 보이고, 같은 id 를 가진 다른 칸이 먼저 잡혔다 → id + 클래스(`Edit` · `Button`)로 고르고, 경로를 한 글자씩 친 뒤 Enter 로 닫게 함.
- 백그라운드로 돌리면 앱 창이 앞으로 나오지 못해 마우스가 다른 창을 누른다 → **앞에서** 돌린다.
+## A-2. 뽑은 폴더 옆에 "무엇을 뽑았는지" 안내를 남긴다 (2026-09-18, 완료)
+
+사용자 결정(2026-09-18): 안내는 **뽑은 폴더 밖(옆)** 에 두고, 장비에서 지울 파일은 **목록으로만** 적는다(지우는 배치 파일은 만들지 않는다).
+
+### 왜
+
+A-1 에서 값으로 확인한 것 — 지운 파일은 "삭제되어 건너뜀 1개" 라고 개수만 알리고 **어느 파일인지 아무 데도 없었다**. 이름이 바뀐 파일은 새 이름만 뽑혀, 그 폴더를 장비에 덮어 넣으면 **옛 파일이 그대로 남는다.** 무엇과 무엇 사이를 뽑았는지도 폴더만 보고는 알 수 없었다.
+
+### 고친 것
+
+| 자리 | 한 일 |
+|---|---|
+| `Services/ExportReport.cs` (새 파일) | 안내 글을 만든다. 머리글(만든 때 · 만든 사람 · 저장소 · 뽑은 곳 · 범위 두 줄) · **뽑은 파일 목록**(추가 · 수정 · 이름 바뀜은 옛 이름을 괄호로) · **장비에서 지워야 할 파일 목록**(지워진 파일 · 이름 바뀐 옛 경로). `PathFor` 가 폴더 옆 이름을 정한다 — `…\변경분` → `…\변경분_변경분안내.txt`, 드라이브 뿌리처럼 옆자리가 없으면 폴더 안에 |
+| `Services/GitService.cs` | `ExportChangedFiles` 가 `(뽑은 수, 건너뛴 수, **무엇을 어떻게 했는지 목록**)` 을 돌려준다. git 의 상태 글자(Added · Renamed · Copied …)를 안내 글의 말로 옮기고, 이름이 바뀐 파일은 옛 경로를 함께 들고 온다 |
+| `Services/IGitService.cs` | 같은 서명으로 |
+| `ViewModels/GitViewModel.cs` | 뽑은 뒤 안내를 폴더 옆에 쓴다(`TextFile.Write`). 범위 줄은 이력에 있는 커밋이면 **제목과 날짜까지**, 시작점을 안 잡았으면 "(고른 커밋의 부모)" 를 붙인다. 안내 파일 경로를 로그에 남기고, 알림도 "지워야 할 파일 N개는 안내에" 로 바꿈. **안내를 못 써도 추출 자체는 실패로 만들지 않는다**(파일은 이미 다 뽑힌 뒤다) |
+| 하네스 `ReportTest` | **+14건(31 → 45)** — 머리글 · 범위 두 줄 · 요약 수 · 이름 바뀜 표기 · 지울 목록 · 지운 파일이 뽑은 목록에 없음 · 한글 경로 · 빈 목록 · 만든 사람 없음 · 폴더 옆 이름 세 가지 |
+
+### 확인한 것 — 화면에서 뽑아 git 과 대조
+
+| 경우 | 안내에 적힌 것 | git `diff -M --name-status` |
+|---|---|---|
+| 범위 `4eb9ece` → `cbcad8a` | 뽑은 3개(추가 `Extract/logo.bin` · **이름 바뀜** `Motion/Renamed16.cs (옛 이름 Motion/Unit16.cs)` · 추가 `한글폴더/깊은/경로/아래/설명.cs`) · **지울 2개**(옛 이름 `Motion/Unit16.cs` · 지워짐 `Recipe/Unit11.cs`) | `A logo.bin` · `R100 Unit16.cs → Renamed16.cs` · `D Unit11.cs` · `A 설명.cs` — **같음** |
+| 병합 커밋(시작점 없음) | 범위 `cbcad8a … (고른 커밋의 부모)` → `58d5f7f …` · 뽑은 1개 `Side/SideOnly.cs` · 지울 0개 | 같음 |
+
+- 안내 파일은 폴더 **옆**에 생긴다(`note2b_변경분안내.txt` · `note5_변경분안내.txt`). 폴더 안에는 뽑은 파일만 있다.
+- 알림: "3개 파일 추출 (지워야 할 파일 1개는 안내에)" · "1개 파일 추출 · 안내 note5_변경분안내.txt". Git 탭 로그에 안내 경로도 남는다.
+- 하네스 22벌 **1,173건** 통과(1,159 → +14) · 빌드 경고 0 · 오류 0.
+
+### 남긴 것 (시험 도구 쪽)
+
+- 화면 잠금이 걸린 동안에는 마우스가 데스크톱에 닿지 않는다(커서 좌표가 0,0 으로 돌아가고 화면 캡처가 실패한다). 잠긴 줄 모르고 "클릭이 안 먹는다" 로 두 번 헤맸다 — 앞으로는 커서 자리와 맨 앞 창부터 본다.
+- 목록에서 스크롤이 필요한 줄은 **누를 때마다 자리를 다시 읽어야** 한다. 옛 자리로 누르면 조용히 빗나가고, 그 뒤 [여기부터] 가 엉뚱한 커밋(HEAD)으로 잡혀 범위가 거꾸로 됐다.
+- PowerShell 변수는 대소문자를 안 가린다 — `$S`(경로)와 `$s`(파일 글)가 겹쳐 경로가 깨졌다(9차 A-2 와 같은 종류).
+
## F-12. C# 코드 스타일 맞춤의 기본값을 AURORA 로 (2026-09-18, 완료)
사용자: "기본값은 원본형태를 따르고 C#인 경우에 AURORA 스타일로 하자" → 물어서 정한 것은 **제품 기본값 AURORA · C# 파일만 · 저장 동작은 지금 그대로**(켜져 있으면 저장도 그 스타일로 쓴다).
diff --git a/doc/history/changelog.md b/doc/history/changelog.md
index 20e0f0d..e3af49f 100644
--- a/doc/history/changelog.md
+++ b/doc/history/changelog.md
@@ -8,6 +8,7 @@
| 날짜 | 항목 | 커밋 |
|---|---|---|
+| 2026-09-18 | **11차 A-2** — 변경분을 뽑으면 **폴더 옆에 안내 파일**(`…_변경분안내.txt`)을 함께 만든다(사용자 결정: 폴더 밖 · 지울 파일은 목록으로만). A-1 에서 지운 파일 · 이름 바뀐 옛 경로 · 뽑은 범위가 결과에 안 남는 것을 확인했기 때문. 안내에 범위(커밋 제목 · 날짜) · 뽑은 파일(추가 · 수정 · 이름 바뀜은 옛 이름 병기) · **장비에서 지워야 할 파일**을 적는다. 추출이 무엇을 했는지 목록으로 돌려주도록 `ExportChangedFiles` 를 고치고, 안내를 못 써도 추출은 성공으로 둔다. 하네스 ReportTest +14(45건) | 아래 |
| 2026-09-18 | **11차 F-12 (추가 요청)** — C# 코드 스타일 맞춤의 제품 기본값을 끔 → **AURORA** 로(사용자 결정). 사내 코드가 이 스타일이라 설정을 켜지 않아도 중괄호 자리 차이를 안 보게 한다. C/C++ · XAML 은 원래부터 확장자 `.cs` 에만 걸려 손대지 않음. 저장 동작은 그대로. 설정 없는 첫 실행에서 AURORA 확인, C# 짝 "차이 없음" · C++ 짝 "남은 차이 4줄". 하네스 StyleTest +12(90건) | 아래 |
| 2026-09-18 | **11차 A-1** — 변경분 추출을 화면에서 아홉 경우로 뽑아 git 과 대조(고치지 않음). 파일 고르기 · 바이트 보존(이진 · 한글 · 깊은 경로) · 범위 표시는 맞았다. 확정된 문제 넷: 지운 파일 · 이름 바뀐 옛 경로가 결과에 안 남음, 무엇과 무엇 사이를 뽑았는지 안 남음(병합 커밋은 첫 부모와만 · 거꾸로 범위도 말없이 됨), 비어 있지 않은 폴더에 묻지 않고 덮어써 옛 추출이 섞임, 쓰다 막히면 반쯤 뽑힌 채 멈춤. A-2 · A-3 으로 넘김. 제품 코드 변경 없음 | 아래 |
| 2026-09-18 | **11차 F 절 (추가 요청)** — 사용자가 한꺼번에 준 열한 가지를 지시서에 넣음: 칸 이름 · 어느 파일인지(F-1) · 메서드 단위 반영(F-2) · 폴더 두 개 보내기 + 설치 없이 되는지(F-3) · 툴박스 감추기(F-4) · 글자 크기(F-5) · 넓게 보기(F-6) · 모니터(F-7) · 저장 뒤 되돌리기(F-8) · 백업 저장(F-9) · 덩어리 잇기 UI(F-10) · Git 계정 · 토큰 저장 확인(F-11). 항목마다 지금 코드가 어떤지와 정할 것을 함께 적음. 코드 변경 없음 | 아래 |
diff --git a/doc/진행상황.md b/doc/진행상황.md
index 5baee26..4ed4fe2 100644
--- a/doc/진행상황.md
+++ b/doc/진행상황.md
@@ -335,7 +335,7 @@
| 상태 | 항목 |
|---|---|
| **완료** | A-1 먼저 뽑아 보고 적는다 — 고치지 않음. 아홉 경우를 화면에서 뽑아 git 과 대조: 파일 고르기 · 바이트 보존(이진 · 한글 · 깊은 경로) · 범위 표시는 맞음. 확정된 문제 넷 — 지운 파일 · 이름 바뀐 옛 경로가 안 남음, 무엇과 무엇 사이인지 안 남음(병합은 첫 부모 · 거꾸로 범위도 말없이), 비어 있지 않은 폴더에 묻지 않고 덮어써 옛 파일이 섞임, 쓰다 막히면 반쯤 뽑힌 채 멈춤 |
-| 대기 | A-2 뽑은 폴더에 무엇을 뽑았는지 남기기 (범위 · 지워야 할 파일 · 옛 경로) — 형식 먼저 정함 |
+| **완료** | A-2 뽑은 폴더 **옆**에 안내 파일(`…_변경분안내.txt`) — 사용자 결정대로 폴더 밖에 두고 지울 파일은 목록으로만. 범위(커밋 제목 · 날짜) · 뽑은 파일(추가 · 수정 · 이름 바뀜은 옛 이름) · 장비에서 지워야 할 파일(지워짐 · 옛 경로). 화면에서 뽑아 git 과 대조 · 하네스 ReportTest +14(45건) |
| 대기 | A-3 이미 파일이 있는 폴더에 뽑을 때 — 묻기 · 새 폴더 · 그대로 중 정함 |
| 대기 | A-4 ZIP 으로 뽑기 — 할지 먼저 정함 |
diff --git a/src/MergeHub/Services/ExportReport.cs b/src/MergeHub/Services/ExportReport.cs
new file mode 100644
index 0000000..4bd63a0
--- /dev/null
+++ b/src/MergeHub/Services/ExportReport.cs
@@ -0,0 +1,125 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+
+namespace MergeHub.Services
+{
+ ///
+ /// 변경분 추출 안내 글(11차 A-2). 뽑은 파일 목록과 장비에서 지워야 할 파일을 적는다.
+ ///
+ /// 왜 필요한가 - 추출은 바뀐 파일만 폴더에 놓는다. 그런데 지운 파일과 이름이 바뀐 파일의 옛 이름은
+ /// 놓을 것이 없어서 폴더에 흔적이 남지 않는다. 그 폴더를 장비에 덮어 넣으면 지워야 할 파일이 그대로 남는다
+ /// (11차 A-1 에서 값으로 확인했다 - 알림은 "삭제되어 건너뜀 1개" 라고만 했다).
+ /// 어느 커밋에서 어느 커밋까지 뽑았는지도 폴더만 보고는 알 수 없었다.
+ ///
+ /// 글을 만드는 일만 여기서 한다(CompareReport 와 같은 규칙). 어디에 쓸지는 화면 쪽 일이다.
+ /// 사용자 결정(2026-09-18): 안내는 뽑은 폴더 밖(옆)에 두고, 지울 파일은 목록으로만 적는다
+ /// - 폴더를 장비에 통째로 넣어도 안내 파일이 소스 틈에 섞이지 않게.
+ ///
+ public static class ExportReport
+ {
+ private const string Rule = "----------------------------------------------------------------";
+
+ /// 추출 한 건. 는 이름이 바뀐 파일의 옛 경로다.
+ public sealed record Item(string Path, string? OldPath, ExportKind Kind);
+
+ /// 안내 글에 적을 갈래.
+ public enum ExportKind
+ {
+ /// 새로 생긴 파일.
+ Added,
+ /// 내용이 바뀐 파일.
+ Modified,
+ /// 이름(또는 자리)이 바뀐 파일. 새 경로로 뽑고 옛 경로는 지울 목록에 넣는다.
+ Renamed,
+ /// 지워진 파일. 뽑을 것이 없어 지울 목록에만 넣는다.
+ Deleted
+ }
+
+ ///
+ /// 뽑은 폴더 옆에 둘 안내 파일의 전체 경로.
+ ///
+ /// 폴더가 D:\나갈것\변경분 이면 D:\나갈것\변경분_변경분안내.txt 가 된다.
+ /// 드라이브 뿌리처럼 옆에 둘 자리가 없으면 폴더 안에 둔다 - 안내를 잃는 것보다 낫다.
+ ///
+ public static string PathFor(string targetDirectory)
+ {
+ var folder = (targetDirectory ?? string.Empty).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
+ var parent = Path.GetDirectoryName(folder);
+ var name = Path.GetFileName(folder);
+
+ if (string.IsNullOrEmpty(parent) || string.IsNullOrEmpty(name))
+ return Path.Combine(targetDirectory ?? string.Empty, "_변경분안내.txt");
+
+ return Path.Combine(parent, name + "_변경분안내.txt");
+ }
+
+ ///
+ /// 안내 글을 만든다.
+ ///
+ /// 저장소 경로.
+ /// 뽑아 놓은 폴더.
+ /// 견준 시작점(짧은 해시 · 제목처럼 사람이 읽을 한 줄).
+ /// 견준 끝(고른 커밋).
+ /// 추출한 것과 지워진 것.
+ /// 만든 때.
+ /// 만든 사람.
+ public static string Build(string repositoryPath, string targetDirectory,
+ string from, string to,
+ IReadOnlyList- items, DateTime when, string? author = null)
+ {
+ var list = items ?? Array.Empty
- ();
+ var text = new StringBuilder();
+
+ text.AppendLine("MergeHub 변경분 추출 안내");
+ text.AppendLine($"만든 때: {when:yyyy-MM-dd HH:mm:ss}");
+ if (!string.IsNullOrWhiteSpace(author)) text.AppendLine($"만든 사람: {author}");
+ text.AppendLine($"저장소 : {repositoryPath}");
+ text.AppendLine($"뽑은 곳: {targetDirectory}");
+ text.AppendLine($"범위 : {from}");
+ text.AppendLine($" → {to}");
+ text.AppendLine(Rule);
+
+ var put = list.Where(i => i.Kind != ExportKind.Deleted).ToList();
+ var remove = list.Where(i => i.Kind == ExportKind.Deleted || i.Kind == ExportKind.Renamed).ToList();
+
+ text.AppendLine($"요약: 뽑은 파일 {put.Count}개 · 장비에서 지워야 할 파일 {remove.Count}개");
+ text.AppendLine();
+
+ text.AppendLine($"[뽑은 파일 {put.Count}개] 이 폴더의 파일을 장비에 덮어 넣으세요.");
+ if (put.Count == 0) text.AppendLine(" (없음)");
+ foreach (var item in put.OrderBy(i => i.Path, StringComparer.OrdinalIgnoreCase))
+ {
+ var mark = item.Kind switch
+ {
+ ExportKind.Added => "추가",
+ ExportKind.Renamed => "이름 바뀜",
+ _ => "수정"
+ };
+ text.AppendLine(item.Kind == ExportKind.Renamed && !string.IsNullOrEmpty(item.OldPath)
+ ? $" {mark,-9} {item.Path} (옛 이름 {item.OldPath})"
+ : $" {mark,-9} {item.Path}");
+ }
+
+ text.AppendLine();
+ text.AppendLine($"[장비에서 지워야 할 파일 {remove.Count}개] 폴더에는 없습니다. 장비에서 직접 지우세요.");
+ if (remove.Count == 0) text.AppendLine(" (없음)");
+ foreach (var item in remove.OrderBy(i => i.Kind == ExportKind.Deleted ? i.Path : (i.OldPath ?? i.Path),
+ StringComparer.OrdinalIgnoreCase))
+ {
+ if (item.Kind == ExportKind.Deleted)
+ text.AppendLine($" 지워짐 {item.Path}");
+ else
+ text.AppendLine($" 옛 이름 {item.OldPath} (→ {item.Path} 로 바뀜)");
+ }
+
+ text.AppendLine();
+ text.AppendLine(Rule);
+ text.AppendLine("이 글은 MergeHub 가 만들었습니다. 파일 내용은 커밋에 있던 바이트 그대로입니다.");
+
+ return text.ToString();
+ }
+ }
+}
diff --git a/src/MergeHub/Services/GitService.cs b/src/MergeHub/Services/GitService.cs
index 701993a..9e5407d 100644
--- a/src/MergeHub/Services/GitService.cs
+++ b/src/MergeHub/Services/GitService.cs
@@ -1327,10 +1327,12 @@ namespace MergeHub.Services
/// 기획안 4p "변경분 파일 추출" - 장비에 소스를 통째로 넣지 못하는 현장에서
/// 바뀐 파일만 들고 가야 할 때 쓴다. 저장소 안의 경로 구조를 그대로 만든다.
///
- /// 지워진 파일은 뽑을 것이 없으므로 목록에만 남기고 파일은 만들지 않는다.
+ /// 지워진 파일은 뽑을 것이 없으므로 목록으로만 돌려주고 파일은 만들지 않는다 -
+ /// 그 목록이 안내 글의 "장비에서 지워야 할 파일" 이 된다(11차 A-2).
///
- /// (뽑은 파일 수, 지워져서 건너뛴 파일 수)
- public (int Written, int Skipped) ExportChangedFiles(string oldCommittish, string newCommittish, string targetDirectory)
+ /// (뽑은 파일 수, 지워져서 건너뛴 파일 수, 무엇을 어떻게 했는지 목록)
+ public (int Written, int Skipped, IReadOnlyList Items)
+ ExportChangedFiles(string oldCommittish, string newCommittish, string targetDirectory)
{
var newCommit = ResolveCommit(newCommittish)
?? throw new ArgumentException($"커밋을 찾을 수 없습니다: {newCommittish}");
@@ -1339,6 +1341,7 @@ namespace MergeHub.Services
var written = 0;
var skipped = 0;
+ var items = new List();
foreach (var diff in DiffCommits(oldCommittish, newCommittish))
{
@@ -1346,6 +1349,7 @@ namespace MergeHub.Services
if (newCommit[diff.Path]?.Target is not Blob blob)
{
skipped++;
+ items.Add(new ExportReport.Item(diff.Path, null, ExportReport.ExportKind.Deleted));
continue;
}
@@ -1361,10 +1365,25 @@ namespace MergeHub.Services
}
written++;
+
+ // git 의 상태 글자(A · M · R …)를 안내 글의 말로 옮긴다. 이름이 바뀐 파일은 옛 경로를 함께 들고 간다.
+ var kind = diff.ChangeKind switch
+ {
+ "Added" => ExportReport.ExportKind.Added,
+ "Renamed" => ExportReport.ExportKind.Renamed,
+ "Copied" => ExportReport.ExportKind.Added,
+ _ => ExportReport.ExportKind.Modified
+ };
+ var oldPath = kind == ExportReport.ExportKind.Renamed &&
+ !string.IsNullOrEmpty(diff.OldPath) && diff.OldPath != diff.Path
+ ? diff.OldPath
+ : null;
+
+ items.Add(new ExportReport.Item(diff.Path, oldPath, kind));
}
Report($"변경분 추출: {written}개 파일 → {targetDirectory} (삭제되어 건너뜀 {skipped}개)");
- return (written, skipped);
+ return (written, skipped, items);
}
public IReadOnlyList DiffCommitWithParent(string committish, params string[] paths)
diff --git a/src/MergeHub/Services/IGitService.cs b/src/MergeHub/Services/IGitService.cs
index a4fe11f..471f820 100644
--- a/src/MergeHub/Services/IGitService.cs
+++ b/src/MergeHub/Services/IGitService.cs
@@ -195,8 +195,12 @@ namespace MergeHub.Services
IReadOnlyList DiffCommits(string oldCommittish, string newCommittish, params string[] paths);
- /// 기획안 4p "변경분 파일 추출" - 두 커밋 사이에 바뀐 파일만 폴더로 뽑는다.
- (int Written, int Skipped) ExportChangedFiles(string oldCommittish, string newCommittish, string targetDirectory);
+ ///
+ /// 기획안 4p "변경분 파일 추출" - 두 커밋 사이에 바뀐 파일만 폴더로 뽑는다.
+ /// 지워진 파일과 이름이 바뀐 파일의 옛 경로는 목록으로 돌려준다 - 안내 글에 쓴다(11차 A-2).
+ ///
+ (int Written, int Skipped, IReadOnlyList Items)
+ ExportChangedFiles(string oldCommittish, string newCommittish, string targetDirectory);
IReadOnlyList DiffCommitWithParent(string committish, params string[] paths);
IReadOnlyList DiffWorkingDirectory(bool stagedOnly = false, params string[] paths);
diff --git a/src/MergeHub/ViewModels/GitViewModel.cs b/src/MergeHub/ViewModels/GitViewModel.cs
index e55cfce..ba9d427 100644
--- a/src/MergeHub/ViewModels/GitViewModel.cs
+++ b/src/MergeHub/ViewModels/GitViewModel.cs
@@ -730,17 +730,54 @@ namespace MergeHub.ViewModels
var to = SelectedCommit!;
var from = DiffFromCommit?.Sha ?? to.ParentShas.FirstOrDefault() ?? to.Sha;
+ // 안내 글에 적을 "무엇에서 무엇까지" - 이력에 있는 커밋이면 제목 · 날짜까지 적는다.
+ var fromLabel = RangeLabel(from, DiffFromCommit, isParentFallback: DiffFromCommit is null);
+ var toLabel = RangeLabel(to.Sha, to, isParentFallback: false);
+ var author = MergeNote.AuthorOf(_workspace.AuthorName);
+
return _workspace.RunAsync("변경분 추출", () =>
{
- var (written, skipped) = _workspace.Git.ExportChangedFiles(from, to.Sha, target);
+ var (written, skipped, items) = _workspace.Git.ExportChangedFiles(from, to.Sha, target);
+
+ // 뽑은 폴더 옆에 안내 글을 둔다(11차 A-2, 사용자 결정) - 폴더를 장비에 통째로 넣어도
+ // 소스 틈에 안내 파일이 섞이지 않는다. 지운 파일 · 이름 바뀐 옛 경로가 여기에만 남는다.
+ var notePath = ExportReport.PathFor(target);
+ var noteWritten = true;
+ try
+ {
+ TextFile.Write(notePath,
+ ExportReport.Build(_workspace.RepositoryPath, target, fromLabel, toLabel, items, DateTime.Now, author));
+ }
+ catch (Exception ex)
+ {
+ // 파일은 다 뽑았는데 안내만 못 쓴 것이라 추출 자체를 실패로 만들지 않는다.
+ noteWritten = false;
+ _workspace.AppendLog($"변경분 안내를 쓰지 못했습니다: {ex.Message}");
+ }
+
+ if (noteWritten) _workspace.AppendLog($"변경분 안내: {notePath}");
_workspace.OnUi(() => MessageUtil.ShowSuccessToast(
skipped > 0
- ? $"{written}개 파일 추출 (삭제되어 건너뜀 {skipped}개)"
- : $"{written}개 파일 추출"));
+ ? $"{written}개 파일 추출 (지워야 할 파일 {skipped}개는 안내에)"
+ : $"{written}개 파일 추출 · 안내 {Path.GetFileName(notePath)}"));
});
}
+ ///
+ /// 안내 글에 적을 커밋 한 줄. 이력에 있는 커밋이면 제목과 날짜까지 적고, 없으면 해시만 적는다.
+ /// 시작점을 안 잡았을 때는 "부모" 라는 것을 드러낸다 - 폴더만 보고 무엇과 견줬는지 알 수 있어야 한다.
+ ///
+ private string RangeLabel(string sha, CommitInfo? known, bool isParentFallback)
+ {
+ var info = known ?? _workspace.Commits.FirstOrDefault(c => c.Sha == sha);
+ var head = info is null
+ ? (sha.Length > 7 ? sha.Substring(0, 7) : sha)
+ : $"{info.ShortSha} {info.MessageShort} ({info.When:yyyy-MM-dd HH:mm})";
+
+ return isParentFallback ? head + " (고른 커밋의 부모)" : head;
+ }
+
/// 저장소가 놓인 폴더. 대화상자를 그 자리에서 열어 주려고 쓴다.
private static string ParentOf(string path)
{