fix(ui): reparse-check repo-scan root folder and move scan off UI thread

RepoScanner.Scan() only guarded reparse points on subdirectories found
during recursion, so a configured import root that is itself a junction
walked straight through onto other drives. Apply the same
FileAttributes.ReparsePoint check to the root before scanning.

RepoImportModalViewModel.ScanAndAdd ran the full 5-level recursive scan
synchronously on the UI thread; pointing the folder picker at a broad
directory froze the app. Offload RepoScanner.Scan to Task.Run per folder
and apply results back on the UI thread.

Add a RepoScannerTests case that creates a real junction (mklink /J) at
the scan root and asserts Scan returns empty.
This commit is contained in:
mika kuns
2026-08-06 14:27:39 +02:00
parent bac8387069
commit b97f55bfb6
3 changed files with 37 additions and 4 deletions
@@ -75,7 +75,7 @@ public sealed partial class RepoImportModalViewModel : ViewModelBase
ErrorReported?.Invoke(Loc.T("vm.repoImport.loadFailed", ex.Message));
}
ScanAndAdd(_folders);
await ScanAndAddAsync(_folders);
OnPropertyChanged(nameof(HasFolders));
NotifyCreateState();
}
@@ -88,20 +88,21 @@ public sealed partial class RepoImportModalViewModel : ViewModelBase
if (added.Count == 0) return;
ScanAndAdd(added);
await ScanAndAddAsync(added);
OnPropertyChanged(nameof(HasFolders));
NotifyCreateState();
await SaveFoldersAsync();
}
private void ScanAndAdd(IEnumerable<string> folders)
private async Task ScanAndAddAsync(IEnumerable<string> folders)
{
var current = new HashSet<string>(
Repos.Select(r => r.FullPath), StringComparer.OrdinalIgnoreCase);
foreach (var folder in folders)
{
foreach (var item in BuildCandidates(RepoScanner.Scan(folder), current, _existingDirs))
var found = await Task.Run(() => RepoScanner.Scan(folder));
foreach (var item in BuildCandidates(found, current, _existingDirs))
{
item.PropertyChanged += OnItemChanged;
Repos.Add(item);