Repository navigation
feat: add a project tree for folders containing multiple repositories / 新增“工程树”,支持包含多个 git 仓库的上级目录 - #2887
Open
ncnnnnn wants to merge 2 commits into
Open
feat: add a project tree for folders containing multiple repositories / 新增“工程树”,支持包含多个 git 仓库的上级目录#2887ncnnnnn wants to merge 2 commits into
ncnnnnn wants to merge 2 commits into
Conversation
Projects are user-defined containers of directories. The tree scans each directory for git repositories (same pruning rules as ScanRepositories but without the side effect of registering nodes into Preferences), keeps only the directory chains that actually lead to a repository, and opens a repository in a regular tab on double click - opening an already opened repository activates its tab. The panel is mounted as a new column of the existing page area in Launcher.axaml; Preferences and the repository pages are untouched. Data is persisted to a separate projects.json so the upstream Preferences class keeps its diff-free state.
Scanning a large folder such as C:\docs (5275 directories) used to take about 12 minutes, because the upstream scanner spawns one `git rev-parse` process per subdirectory, and the tree was only built after the whole scan finished. Fixing the performance alone did not make repositories visible: ProjectTree never set its DataContext, so every binding resolved against Launcher's view model and the tree rendered empty no matter what the model contained. - RepositoryScanner: recognise bare repositories by layout (HEAD + objects/ + refs/) instead of one git process per folder; verify real candidates with 4-way concurrency and read results back in index order; report every hit through IProgress so the caller can render it immediately - Projects: add the directory node before scanning and insert repositories as they are found, reconcile against the full result list afterwards, restore projects sequentially (concurrent rebuilds used to cancel each other), reuse an existing project when the chosen directories are identical, and name the top-level directory node by its full path so it no longer collides with the project name - ProjectTree: set DataContext to Projects.Instance; show three-state icons (folder for a project, open folder for an intermediate directory, repository stack for a git repository) taken from Resources/Icons.axaml; trim long names - ProjectTreeNode: IsDirectory plus change notification for icon selection - ScanLog + `--scan-log <dir>` + SOURCEGIT_SCAN_LOG: headless scanner trace, disabled unless explicitly requested
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Today the launcher tree is a list of repositories. It cannot represent the common
case of one parent folder that contains many git repositories — the folder itself
is not a repository, so opening it only offers "initialize here" or "trust".
TryOpenRepositoryFromPathhas exactly two branches (it is a repository / showInit or TrustRepository popup), because
LauncherPage.Nodeis always aRepositoryNode; there is no "directory container" dimension.Upstream's
ScanRepositoriescan recurse, but it is a one-shot modal import: itleaves no container behind and registers every found repository into the global
Preferences.RepositoryNodes, so the result is indistinguishable from manuallyimported repositories. Related requests exist in the tracker (#2712, #2296,
#2537, #2542, #602, #713) and were not implemented, so this PR proposes one
concrete shape and keeps it isolated.
Solution
Add a resizable "project" panel on the left of the launcher page:
OpenRepositoryInTab, so tabs, workspace restore and everything else behaveexactly as with a repository opened from the welcome page.
repository and expands the directories on the way to it; a welcome tab clears
the selection.
projects.json, not inPreferences.RepositoryNodes.Why the scanner is not
ScanRepositoriesReusing it would have made scanning a normal folder take minutes: it runs one
git rev-parseprocess per directory without a.gitentry. Measured onC:\docs(5275 directories, 9 repositories within depth 5):git rev-parseper directoryHEAD+objects/+refs/)End-to-end: 12 min → 3.5 s on the folder above, with all 9 repositories listed.
Changes
New files (no upstream behaviour modified):
Models/Project.cs— project model and the save model forprojects.jsonModels/RepositoryScanner.cs— directory walk and repository detectionModels/ScanLog.cs— opt-in trace, used to diagnose scan problemsViewModels/ProjectTreeNode.cs— tree node (project / directory / repository)ViewModels/Projects.cs— project list, scanning, incremental tree buildingViews/ProjectTree.axaml(.cs)— the panel, context menu, dialogsViews/ProjectRename.axaml(.cs)— rename dialog, following the existingPushToNewBranchstyle (v:ChromelessWindow,v:CaptionButtons)Two upstream files touched, both minimally:
Views/Launcher.axaml— the page area becomesGrid ColumnDefinitions="240,1,*"with the tree and aGridSplitter; theoriginal
ContentControlis moved unchanged into the third column.App.JsonCodeGen.cs— one line registeringProjectsSaveModel, requiredbecause
projects.jsonis trimmed away underPublishAot/PublishTrimmed.Testing
dotnet build -c Debug— 0 warnings, 0 errors.C:\docs(18 top-level folders, 5275 directories, 9 repositories atdepth 5): all 9 repositories appear, double-click opens a normal tab, tab
switching keeps the tree in sync.
linux-x64publish succeeds; the binary is a valid ELFapphost with
libcoreclr.so/libSkiaSharp.so/libHarfBuzzSharp.sopresent. Not launched on a Linux desktop yet.
Screenshots available on request.
问题(中文)
现在的启动页树本质是“仓库列表”,无法表达“一个上级目录里装着多个 git 仓库”这种常见情况:
这个目录本身不是仓库,打开它只会走“在此初始化”或“信任仓库”两条分支。
TryOpenRepositoryFromPath只有两支(是仓库 / 弹 Init 或 TrustRepository),根因是
LauncherPage.Node恒为RepositoryNode,缺“目录容器”这一维。上游
ScanRepositories虽有递归能力,但它是一次性模态导入:不留容器概念,且会把找到的每个仓库都注册进全局
Preferences.RepositoryNodes,结果与手工导入的仓库无法区分。仓库里的同类诉求(#2712、#2296、#2537、#2542、#602、#713)
均未实现,因此本 PR 提出一种具体形态,并把改动完全隔离。
方案
在启动页左侧新增可拖宽的“工程”面板:
OpenRepositoryInTab,标签页、工作区恢复等行为与从欢迎页打开完全一致;projects.json,不写入Preferences.RepositoryNodes。为什么不复用
ScanRepositories复用它会让普通目录的扫描变成分钟级:它对每个“没有
.git的目录”都起一次git rev-parse进程。在
C:\docs(5275 个目录、深度 5 内 9 个仓库)实测:git rev-parseHEAD+objects/+refs/)端到端:同一目录 12 分钟 → 3.5 秒,9 个仓库全部列出。
改动
新增文件(不改上游既有行为):
Models/Project.cs— 工程模型与projects.json的保存模型Models/RepositoryScanner.cs— 目录遍历与仓库识别Models/ScanLog.cs— 可选开关的追踪日志,用于诊断扫描问题ViewModels/ProjectTreeNode.cs— 树节点(工程 / 目录 / 仓库)ViewModels/Projects.cs— 工程列表、扫描、增量建树Views/ProjectTree.axaml(.cs)— 面板、右键菜单、对话框Views/ProjectRename.axaml(.cs)— 重命名对话框,沿用既有PushToNewBranch的样式(
v:ChromelessWindow、v:CaptionButtons)只改 2 个上游文件,且都是最小改动:
Views/Launcher.axaml— 页面区改成Grid ColumnDefinitions="240,1,*",放入树和GridSplitter,原
ContentControl原样移到第三列;App.JsonCodeGen.cs— 加一行注册ProjectsSaveModel。因为projects.json在PublishAot/PublishTrimmed下会被裁掉,必须靠源生成器。测试
dotnet build -c Debug— 0 警告 0 错误;C:\docs(18 个顶层目录、深度 5 内 5275 个目录、9 个仓库):9 个仓库全部显示,双击可打开与原生一致的标签页,切换标签时树保持同步;
linux-x64自包含发布成功,产物是合法 ELF apphost,libcoreclr.so/libSkiaSharp.so/libHarfBuzzSharp.so齐备;尚未在 Linux 桌面实跑。需要截图可以补。