Repository navigation
feat: add a project tree for folders containing multiple repositories / 新增“工程树”,支持包含多个 git 仓库的上级目录 - #2887
Open
ncnnnnn wants to merge 5 commits into
Open
feat: add a project tree for folders containing multiple repositories / 新增“工程树”,支持包含多个 git 仓库的上级目录#2887ncnnnnn wants to merge 5 commits into
ncnnnnn wants to merge 5 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
跟随当前标签页: - 切换标签时在树中自动选中对应仓库,并展开通往它的目录;欢迎页(其节点 Id 是 生成的 guid)则清空选中 - 路径从 ActivePage.Node.Id 取,与 RepositoryScanner.Key() 用同一套规范化后比对, 避免大小写或斜杠差异导致匹配不上 - TreeView 在 Avalonia 里没有 ScrollIntoView,改为找到节点的容器后 BringIntoView, 放在 Background 优先级等待布局完成 - 扫描结果尚未恢复时目标为 null,此时清空选中而不是保留旧选中 移除按钮按选中层级执行: - 工程节点 -> 移除整个工程(写入 projects.json) - 一级目录 -> 从工程中移除该目录(写入 projects.json) - 更深的目录/仓库 -> 只从当前树中摘除,不持久化,下次扫描会重新出现; 由此变空的中间目录一并剪枝,不留死枝 - 按钮文字随选中项变为「移除工程 / 移除目录 / 移除仓库」,避免再次出现 选中仓库却删掉整个工程的歧义
顶部标签条实际是 ListBox 而非 TabItem,之前活动页的唯一表现是把内容 Opacity 从 0.5 提到 1,背景固定 Transparent,选中与未选中几乎看不出差别。 - 活动页:强调色 16% 透明底色块 + 底部 2px 实心强调条(颜色取自 SystemAccentColor,跟随主题,不硬编码色值) - 活动页 + 悬停:底色加深到 22% - 未选中:Opacity 提到 0.6,与活动页拉开差距 - 活动页标题文字加粗并转为红色(新增 tab_title 类选择器,因为标签条是 ListBox,只能通过伪类 + 类选择器命中,不能改 TabItem:selected)
- 仓库节点:圆点沿用标签页配色(灰=有本地修改,蓝=有待推送或拉取), 并显示「N 处改动」文字;若有待推送/拉取追加「待推拉」 - 目录与工程节点:显示「N 个仓库有改动」的聚合文字,折叠状态下同样可见 - 已打开的仓库直接镜像所属标签页的 DirtyState(上游每次 git 操作都会调 ChangeDirtyState),因此提交、暂存、拉取后树上状态立即更新,无需轮询 - 未打开的仓库由 RepositoryStatusScanner 后台并发查询(并发 4、可取消, 单仓库失败不影响整轮),命令为 status -uno:刻意不计未跟踪文件, 否则 node_modules 与构建产物会让整棵树全变脏 - 刷新时机:建树完成后一次;主窗口重新获得焦点一次(3 秒节流); 标签页开关触发一次全树重查;不做定时轮询 - 节点名宽度由 176 收到 132,为右侧状态文字让位
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 桌面实跑。需要截图可以补。