A-5: Git 저장소 최상위를 고를 수 없던 문제
두 가지가 겹쳐 있었다. 하나. Git 탭의 폴더 고르기가 WinForms FolderBrowserDialog 였다. net48 에서는 주소 칸이 없는 옛날 트리 창이라 경로를 붙여넣을 수 없고, 지난번에 골랐던 깊은 폴더에서 최상위까지 손으로 거슬러 올라가야 했다. 둘. .git 은 최상위에만 있는데 LooksLikeRepository 가 고른 폴더 바로 밑만 봤다. 사람은 지금 보고 있던 깊은 폴더를 고르기 마련이라 멀쩡한 저장소를 두고 "저장소가 아니다"가 됐다. - Services/FolderPicker.cs 신설. 폴더 고르기를 한 군데로 모았다(폴더 비교 왼쪽/오른쪽, Git 저장소 경로, 변경분 추출 대상 폴더). - PathUtil.FindRepositoryRoot 신설. 고른 자리에서 위로 올라가며 .git 을 찾는다. 워크트리/서브모듈은 .git 이 파일이라 둘 다 본다. 안쪽에 저장소가 또 있으면 가장 가까운 것을 잡는다. - Git 탭과 시작 시 자동 열기가 같은 규칙을 쓴다. 확인: src\MergeHub\Views\GitView.xaml (네 단계 아래)을 골랐더니 경로가 D:\Sorce\MergeTool\MergeHub 로 바로잡히고 저장소가 열렸다. PathTest 12건 추가(31 -> 43건). 전체 399건. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
af27800978
commit
93a507e3e8
32
doc/진행상황.md
32
doc/진행상황.md
@ -104,7 +104,7 @@ CommunityToolkit 커맨드는 CommandManager 를 구독하지 않는데 이 둘
|
||||
| **완료** | A-2 화면 이동 시 종료·최소화 | 아래 A-2~A-4 에 함께 적음 | 21회 연속 이동 (누름 60·250·600ms) |
|
||||
| **완료** | A-3 파일 비교에서 화면 이동이 원래 모니터로 복귀 | A-2 와 같은 원인 | 파일 비교 탭에서 6회 왕복 |
|
||||
| **완료** | A-4 화면 이동 시 잠금 팝업 | A-2 와 같은 원인 | 팝업 0회 · 잠금/바탕화면/종료 버튼은 그대로 동작 |
|
||||
| 대기 | A-5 Git 저장소 최상위를 고를 수 없음 | | |
|
||||
| **완료** | A-5 Git 저장소 최상위를 고를 수 없음 | 폴더 고르기 창을 바꾸고, 고른 자리에서 위로 올라가 최상위를 찾는다 | 4단계 아래 파일을 골라도 최상위가 열림 · PathTest 12건 추가 |
|
||||
|
||||
### B. 3-Way 화면
|
||||
|
||||
@ -136,6 +136,36 @@ CommunityToolkit 커맨드는 CommandManager 를 구독하지 않는데 이 둘
|
||||
|
||||
---
|
||||
|
||||
### A-5. Git 저장소 최상위를 고를 수 없던 문제 — **완료** (2026-09-09)
|
||||
|
||||
두 가지가 겹쳐 있었다.
|
||||
|
||||
**하나 — 폴더 고르는 창이 옛날 것이었다.** Git 탭의 `폴더...` 는 WinForms 의
|
||||
`FolderBrowserDialog` 를 썼는데, net48 에서는 <b>주소 칸이 없는 옛날 트리 창</b>이다.
|
||||
경로를 붙여넣을 수도 없고, 지난번에 골랐던 깊은 폴더가 기억돼 있으면 거기서부터
|
||||
저장소 최상위까지 트리를 손으로 거슬러 올라가야 했다.
|
||||
|
||||
**둘 — 고른 자리만 보고 판단했다.** `.git` 은 저장소 최상위에만 있는데
|
||||
`LooksLikeRepository` 는 고른 폴더 바로 밑만 봤다. 사람은 지금 보고 있던 깊은 폴더를
|
||||
고르기 마련이라, 멀쩡한 저장소를 두고 "저장소가 아니다"가 됐다.
|
||||
|
||||
**고친 것**
|
||||
|
||||
- `Services/FolderPicker.cs` 신설. 폴더 고르기를 한 군데로 모았다(폴더 비교 왼쪽·오른쪽,
|
||||
Git 저장소 경로, 변경분 추출 대상 폴더 — 네 자리가 같은 것을 쓴다).
|
||||
주소 칸이 있어 경로를 붙여넣을 수 있고, 파일 이름 칸을 그대로 둔 채 열기를 누르면
|
||||
<b>지금 보고 있는 폴더</b>가 선택된다. 최상위든 어디든 한 번에 갈 수 있다.
|
||||
- `PathUtil.FindRepositoryRoot` 신설. 고른 자리에서 <b>위로 거슬러 올라가며</b> `.git` 을 찾는다.
|
||||
워크트리·서브모듈은 `.git` 이 폴더가 아니라 <b>파일</b>이라 둘 다 본다.
|
||||
안쪽에 저장소가 또 있으면 가장 가까운 것을 잡는다.
|
||||
- Git 탭에서 폴더를 고르면 그 자리에서 최상위로 바로잡아 연다. 시작할 때의 자동 열기도
|
||||
같은 규칙을 쓰므로, 설정에 하위 폴더가 저장돼 있어도 이제 열린다.
|
||||
|
||||
**확인**: `src\MergeHub\Views\GitView.xaml`(최상위에서 네 단계 아래)을 골랐더니
|
||||
경로가 `D:\Sorce\MergeTool\MergeHub\` 로 바로잡히고 저장소가 열렸다
|
||||
(`HEAD master af27800 · Working Tree 6 files`). `PathTest` 에 12건 추가(31 → 43건). 전체 **399건**.
|
||||
---
|
||||
|
||||
### A-2 · A-3 · A-4. 화면 이동 결함 세 가지 — **완료** (2026-09-09)
|
||||
|
||||
세 가지가 아니라 <b>하나</b>였다.
|
||||
|
||||
55
src/MergeHub/Services/FolderPicker.cs
Normal file
55
src/MergeHub/Services/FolderPicker.cs
Normal file
@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace MergeHub.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// 폴더 하나를 고르는 대화상자. 세 군데(폴더 비교 왼쪽·오른쪽, Git 저장소 경로)가 같은 것을 쓴다.
|
||||
///
|
||||
/// net48 WPF 에는 폴더 전용 대화상자가 없다. WinForms 의 <c>FolderBrowserDialog</c> 는
|
||||
/// net48 에서 <b>옛날 트리 창</b>이라 주소 칸이 없고 경로를 붙여넣을 수도 없어서,
|
||||
/// 깊은 폴더가 기억돼 있으면 저장소 최상위까지 거슬러 올라가기가 번거롭다.
|
||||
/// 그래서 파일 열기 대화상자를 폴더 고르기로 쓴다 — 주소 칸이 있고, 경로를 붙여넣을 수 있고,
|
||||
/// 파일 이름 칸을 그대로 둔 채 열기를 누르면 <b>지금 보고 있는 폴더</b>가 선택된다.
|
||||
/// </summary>
|
||||
public static class FolderPicker
|
||||
{
|
||||
/// <summary>고른 폴더의 전체 경로. 취소하면 null.</summary>
|
||||
public static string? Pick(string title, string startFolder)
|
||||
{
|
||||
var dialog = new Microsoft.Win32.OpenFileDialog
|
||||
{
|
||||
Title = title + " (폴더 안에서 아무 파일이나 고르거나, 파일 이름 칸을 그대로 두고 열기)",
|
||||
CheckFileExists = false,
|
||||
CheckPathExists = true,
|
||||
FileName = "이 폴더 선택",
|
||||
Filter = "폴더 (*.*)|*.*"
|
||||
};
|
||||
|
||||
// 시작 폴더는 반드시 PathUtil 을 거친다. 슬래시가 섞인 경로("C:/a/b")를 그대로 넘기면
|
||||
// ShowDialog() 안쪽에서 ArgumentException 이 나고 프로그램이 죽는다. PathUtil.cs 참고.
|
||||
var start = PathUtil.FullFolderPath(startFolder);
|
||||
if (start.Length > 0) dialog.InitialDirectory = start;
|
||||
|
||||
if (!Show(dialog)) return null;
|
||||
|
||||
var picked = Path.GetDirectoryName(dialog.FileName);
|
||||
return string.IsNullOrEmpty(picked) ? null : picked;
|
||||
}
|
||||
|
||||
private static bool Show(Microsoft.Win32.OpenFileDialog dialog)
|
||||
{
|
||||
try
|
||||
{
|
||||
return dialog.ShowDialog() == true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// 정규화로 걸러지지 않는 시작 폴더가 남는다(끊긴 네트워크 드라이브 등).
|
||||
// 폴더 고르기 자체를 못 쓰게 되는 것보다 기본 위치에서 다시 여는 편이 낫다.
|
||||
dialog.InitialDirectory = string.Empty;
|
||||
return dialog.ShowDialog() == true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
@ -189,20 +189,26 @@ namespace MergeHub.Services
|
||||
/// 저장소가 아닌 폴더에 Open 을 걸면 실패 알림이 뜰 뿐 아니라, 그동안 작업 게이트를
|
||||
/// 쥐고 있어서 뒤이어 시작하는 "파일 두 개 비교"까지 통째로 취소된다.
|
||||
/// </summary>
|
||||
public bool LooksLikeRepository =>
|
||||
!string.IsNullOrWhiteSpace(RepositoryPath) &&
|
||||
(Directory.Exists(Path.Combine(RepositoryPath, ".git")) ||
|
||||
File.Exists(Path.Combine(RepositoryPath, ".git")));
|
||||
/// <remarks>
|
||||
/// <b>고른 자리에서 위로 거슬러 올라가 찾는다.</b> <c>.git</c> 은 최상위에만 있는데
|
||||
/// 사람은 지금 보고 있던 깊은 폴더를 고르기 마련이라, 그 자리만 보면
|
||||
/// 멀쩡한 저장소를 두고 "저장소가 아니다"라고 하게 된다.
|
||||
/// </remarks>
|
||||
public bool LooksLikeRepository => PathUtil.FindRepositoryRoot(RepositoryPath).Length > 0;
|
||||
|
||||
/// <summary>설정에 기본 저장소 경로가 있으면 시작하자마자 연다.</summary>
|
||||
public Task TryAutoOpenAsync()
|
||||
{
|
||||
if (IsRepositoryOpen || string.IsNullOrWhiteSpace(RepositoryPath) || !Directory.Exists(RepositoryPath))
|
||||
if (IsRepositoryOpen || string.IsNullOrWhiteSpace(RepositoryPath))
|
||||
return Task.CompletedTask;
|
||||
|
||||
if (!LooksLikeRepository) return Task.CompletedTask;
|
||||
var root = PathUtil.FindRepositoryRoot(RepositoryPath);
|
||||
if (root.Length == 0) return Task.CompletedTask;
|
||||
|
||||
return RunAndRefreshAsync("저장소 자동 열기", () => Git.Open(RepositoryPath));
|
||||
// 하위 폴더가 저장돼 있었으면 최상위로 바로잡아 둔다 - 화면의 경로 칸도 함께 맞는다.
|
||||
RepositoryPath = root;
|
||||
|
||||
return RunAndRefreshAsync("저장소 자동 열기", () => Git.Open(root));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace MergeHub.Services
|
||||
@ -36,6 +36,47 @@ namespace MergeHub.Services
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 주어진 폴더에서 <b>위로 올라가며</b> Git 저장소의 최상위(작업 폴더)를 찾는다. 없으면 빈 문자열.
|
||||
///
|
||||
/// 왜 필요한가 - <c>.git</c> 은 저장소 최상위에만 있다. 사람은 지금 보고 있던
|
||||
/// 깊은 폴더를 고르기 마련이라, 고른 자리에서 위로 거슬러 올라가 최상위를 찾아 줘야
|
||||
/// "저장소가 아니다"라는 말을 듣지 않는다.
|
||||
///
|
||||
/// 워크트리·서브모듈은 <c>.git</c> 이 폴더가 아니라 <b>파일</b>이라 둘 다 본다.
|
||||
/// </summary>
|
||||
public static string FindRepositoryRoot(string? startFolder)
|
||||
{
|
||||
var dir = FullFolderPath(startFolder);
|
||||
|
||||
while (dir.Length > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
var marker = Path.Combine(dir, ".git");
|
||||
if (Directory.Exists(marker) || File.Exists(marker)) return dir;
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var parent = Path.GetDirectoryName(dir);
|
||||
if (string.IsNullOrEmpty(parent) || string.Equals(parent, dir, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// 드라이브 뿌리(C:\)까지 왔다. 뿌리 자체도 한 번 보고 끝낸다.
|
||||
break;
|
||||
}
|
||||
|
||||
dir = parent!;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
/// <summary>파일 경로가 든 폴더를 정규화해서 돌려준다. 확인할 수 없으면 빈 문자열.</summary>
|
||||
public static string FolderOfFile(string? filePath)
|
||||
{
|
||||
|
||||
@ -217,38 +217,7 @@ namespace MergeHub.ViewModels
|
||||
}
|
||||
|
||||
private static string? AskForFolder(string title, string current)
|
||||
{
|
||||
// 파일 열기 대화상자를 폴더 고르기로 쓴다. net48 WPF 에는 폴더 전용 대화상자가 없고,
|
||||
// 셸 COM 을 직접 부르는 것보다 이 편이 유지보수가 쉽다.
|
||||
var dialog = new Microsoft.Win32.OpenFileDialog
|
||||
{
|
||||
Title = title + " (폴더 안에서 아무 파일이나 고르거나, 파일 이름 칸에 폴더명만 두고 열기)",
|
||||
CheckFileExists = false,
|
||||
CheckPathExists = true,
|
||||
FileName = "이 폴더 선택",
|
||||
Filter = "폴더 (*.*)|*.*"
|
||||
};
|
||||
|
||||
// 시작 폴더는 반드시 PathUtil 을 거친다. 슬래시가 섞인 경로("C:/a/b")를 그대로 넘기면
|
||||
// ShowDialog() 안쪽에서 ArgumentException 이 나고 프로그램이 죽는다. PathUtil.cs 참고.
|
||||
var start = PathUtil.FullFolderPath(current);
|
||||
if (start.Length > 0) dialog.InitialDirectory = start;
|
||||
|
||||
try
|
||||
{
|
||||
if (dialog.ShowDialog() != true) return null;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// 그래도 셸이 시작 폴더를 거부하는 경우(끊긴 네트워크 드라이브 등)가 남는다.
|
||||
// 폴더 고르기를 아예 못 쓰게 되는 것보다 기본 위치에서 다시 여는 편이 낫다.
|
||||
dialog.InitialDirectory = string.Empty;
|
||||
if (dialog.ShowDialog() != true) return null;
|
||||
}
|
||||
|
||||
var path = Path.GetDirectoryName(dialog.FileName);
|
||||
return string.IsNullOrEmpty(path) ? null : path;
|
||||
}
|
||||
=> FolderPicker.Pick(title, current);
|
||||
private bool CanCompare() => !IsBusy;
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanCompare))]
|
||||
|
||||
@ -408,18 +408,10 @@ namespace MergeHub.ViewModels
|
||||
[RelayCommand(CanExecute = nameof(CanExportChanges))]
|
||||
private Task ExportChangesAsync()
|
||||
{
|
||||
using var dialog = new System.Windows.Forms.FolderBrowserDialog
|
||||
{
|
||||
Description = "변경분 파일을 뽑아 놓을 폴더",
|
||||
// 저장소가 있는 자리에서 시작한다 - 뽑아 놓을 자리는 보통 그 근처다.
|
||||
var target = FolderPicker.Pick("변경분 파일을 뽑아 놓을 폴더", ParentOf(_workspace.RepositoryPath));
|
||||
if (target is null) return Task.CompletedTask;
|
||||
|
||||
// 저장소가 있는 자리에서 시작한다. 이 대화상자는 경로를 칠 칸이 없어
|
||||
// 매번 트리를 처음부터 파고 내려가야 하는데, 뽑아 놓을 자리는 보통 그 근처다.
|
||||
SelectedPath = ParentOf(_workspace.RepositoryPath)
|
||||
};
|
||||
|
||||
if (dialog.ShowDialog() != System.Windows.Forms.DialogResult.OK) return Task.CompletedTask;
|
||||
|
||||
var target = dialog.SelectedPath;
|
||||
var to = SelectedCommit!;
|
||||
var from = DiffFromCommit?.Sha ?? to.ParentShas.FirstOrDefault() ?? to.Sha;
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
using System.Windows;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using MergeHub.ViewModels;
|
||||
|
||||
@ -21,19 +21,17 @@ namespace MergeHub.Views
|
||||
var vm = ViewModel;
|
||||
if (vm is null) return;
|
||||
|
||||
// net48 WPF 에는 OpenFolderDialog 가 없어 WinForms 대화상자를 쓴다.
|
||||
using var dialog = new System.Windows.Forms.FolderBrowserDialog
|
||||
{
|
||||
Description = "Git 저장소 폴더 선택",
|
||||
ShowNewFolderButton = false
|
||||
};
|
||||
// 예전에는 WinForms FolderBrowserDialog 를 썼다. net48 에서는 옛날 트리 창이라
|
||||
// 주소 칸이 없고 경로를 붙여넣을 수도 없어서, 깊은 폴더가 기억돼 있으면
|
||||
// 저장소 최상위까지 거슬러 올라가기가 번거로웠다. FolderPicker.cs 참고.
|
||||
var picked = MergeHub.Services.FolderPicker.Pick("Git 저장소 폴더 고르기", vm.Workspace.RepositoryPath);
|
||||
if (picked is null) return;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(vm.Workspace.RepositoryPath))
|
||||
dialog.SelectedPath = vm.Workspace.RepositoryPath;
|
||||
// .git 은 최상위에만 있다. 하위 폴더를 골랐어도 위로 거슬러 올라가 최상위를 연다 -
|
||||
// 사람은 지금 보고 있던 깊은 폴더를 고르기 마련이다.
|
||||
var root = MergeHub.Services.PathUtil.FindRepositoryRoot(picked);
|
||||
vm.Workspace.RepositoryPath = root.Length > 0 ? root : picked;
|
||||
|
||||
if (dialog.ShowDialog() != System.Windows.Forms.DialogResult.OK) return;
|
||||
|
||||
vm.Workspace.RepositoryPath = dialog.SelectedPath;
|
||||
if (vm.OpenCommand.CanExecute(null)) vm.OpenCommand.Execute(null);
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user