Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,9 @@ Git Log 在视口接近底部时自动读取下一批较早提交,不再把“
视口的后台 80 条预热,避免迟到预热把已追加的长列表覆盖成短列表。第一次
续载用包含首屏的有界请求打开游标,后续每次只读 50 条,仍受 Core 5,000 条
上限约束。失败保留已有行,暂停自动请求,显式重试重新打开游标。
正常浏览不再保留底部“加载更多”按钮或固定高度页脚,原生列表直接撑满
剩余空间。加载指示和失败重试覆盖在列表底部,不缩小视口;不能为了显示
分页状态重新加入常驻空白带。
状态扫描和历史追加分别合并当前结果,不能把新收到的变更或提交覆盖成旧快照。
搜索只筛选已加载历史,有搜索条件时不自动遍历整个仓库;切换工作树、关闭页面、
移除工作树或重置仓库时取消在途续载并关闭游标,迟到结果只清理自身资源。
Expand All @@ -257,6 +260,35 @@ Diff、打开文件、定位与复制路径。空格切换当前项暂存,Retu
点击复选框不能顺带打开文件,扩展选区也不能打断当前 Diff。
批量操作优先作用于选区;右键未选中的文件只操作该文件。

更改页采用可拖动的左右分栏:左边显示按本地变更列表(ChangeList)分组的
文件状态,右边复用 `CommitAreaView` 的提交说明、AI、修订上次提交和提交/推送
操作。窄详情列改为上下排列,路径与分支标题区保持紧凑,文件区不再额外套一圈
12pt 留白。分组计算放在尺寸读取之外,拖动不重建原生列表或逐行计算分组。

共享提交区不能直接绑定主工作区模型,否则从另一个工作树点击提交会带入主仓库
或嵌套仓库。`commitFeature(for:)` 复用既有提交计划和 ChangeList 存储,但固定
到当前工作树的唯一根目录,提交前仍由 Core 校验真实根目录、HEAD、索引及范围。
例如,选中 linked 工作树时只传入 linked 这一项仓库绑定;不要把工作区发现的
所有仓库原样交给这个提交区。缺失、可清理或裸仓库不允许从该入口提交。

状态扫描的排除集合与提交绑定集合必须分开。首次检查、手动刷新、工作树列表
自动刷新及独立提交模型刷新,都在所选 checkout 内发现仓库,把发现结果交给
既有 Core 状态过滤;linked 路径在打开的工作区之外时也一样。提交模型的
`availableRepositoryRoots` 仍只有所选根,目录展开同样携带状态排除集合。
例如,父根下独立的 `nested/` 仓库不应成为父根的未跟踪行;不要为了单根提交
把状态扫描也缩成 `[root]`,否则全部暂存会意外生成没有 `.gitmodules` 的
gitlink(Git 索引中 mode-160000 的仓库引用)。已跟踪的 gitlink 仍由 Core
原有规则保留,不由界面猜测子模块语义。代价是状态读取前需要一次仓库发现。

提交说明草稿按工作树和工作区会话分别保留,切换详情标签或工作树不会串草稿。
认证、取消和模块保活仍由父 Git 模型持有;主提交区与工作树提交互斥,同一根的
ChangeList 值与存储故障状态同步,linked 根单独存储。工作区重置同时使旧提交模型
失效,旧编辑器任务不能重新打开原路径。代价是在当前工作区会话内缓存访问过的
工作树提交模型和草稿;不增加新的 Git 提交实现或运行时资源目录。
互斥必须在模型写入口检查,既覆盖复选框和批量暂存,也覆盖 Diff 的直接文件与
区块暂存/取消暂存。工作树提交在准备计划时已经持锁,不能只看父模型自身的
`isCommitting`;否则仍打开的 Diff 会在计划已读取后修改同一个索引。

所有写操作由 `GitFeatureModel` 调用已有 Git service,沿用提交页的暂存、
取消暂存和回滚语义,不另外拼命令,也不隐式切换活动仓库。执行前核对
工作树仍在列表、路径可用且快照已加载,再将捕获的文件与最新状态逐项匹配。
Expand All @@ -268,6 +300,10 @@ Diff、打开文件、定位与复制路径。空格切换当前项暂存,Retu
变更批次在途期间禁止重复提交,首个失败停止后续文件,保留错误供用户处理。
取消或重置后的迟到结果不向新上下文发通知。操作后只重新扫描该工作树状态,
合并到当前详情,保留已有历史与游标;扫描失败保留旧行并显示错误。
主工作区刷新已确认文件状态后,同步更新正在显示的主工作区详情;刷新工作树
记录时,对已选且已完成初次扫描的工作树补读文件状态。文件编辑不会改变
工作树路径或 HEAD,不能只依赖这两个字段重启详情任务。状态未变时保留
详情版本,不重复投影历史;正在初次加载或执行文件操作时不启动重复扫描。
已打开的该工作树 Diff 跟随新状态刷新,文件变干净后关闭。其他工作树或仓库
的新选择不能被旧扫描覆盖。Windows 当前只有管理弹窗,没有这组更改详情,
本次没有新增 Windows 对等入口。
Expand Down Expand Up @@ -493,6 +529,18 @@ Lithe 在工作台视图中持有 `WorkbenchToolContentStore`,本次用于 Git
批量目标、禁用状态、共享工具栏和深浅窄宽布局,以及路径整行自适应与行内展开;
`GitModuleTests` 覆盖根目录隔离、部分暂存、过期目标拒绝、首错停止、扫描失败
保留、Diff 刷新/关闭、历史保留和取消/重置后迟到错误不发布。
- 更改页组件测试另外覆盖分组展开和勾选互不混淆、分组行不干扰文件键盘导航、
左右/上下布局切换保留选择及草稿、同轴调整不重建原生文档;模型测试检查
提交范围只包含选中根、ChangeList 独立与主根同步、存储故障和重置隔离。
`GitStatusObservationTests.realGitWorktreeComposerCommitsOnlySelectedCheckout` 使用
真实 Git 与 Core 提交 linked 改动,断言主仓库 HEAD 和暂存文件不变。
`realGitWorktreeRefreshAndStageAllExcludeIndependentNestedRepositories` 分别在主根
与工作区外 linked 根放置独立嵌套仓库,检查首次读取、显式/自动刷新、独立模型
刷新、全部暂存及提交不产生意外 gitlink,且嵌套仓库 HEAD/索引不变。
`worktreeCommitPreparationBlocksDirectDiffIndexMutations` 用有界 gate 暂停准备好的
提交计划,验证父 Diff 的文件与区块暂存/取消暂存不调用 service,释放后提交成功,
写入口恢复可用。
这些测试不替代完整工作台中拖动、确认弹窗及真实推送的人工验收。
- `DebugModuleTests` 另覆盖上下两个方向的缩页、单条邻居失败后的边缘验证、
无效补位指令、真实错误保留和验证期间失效;本机 `lldb-dap` 21 的独立 C
程序实验复现映像首地址的负偏移失败及零偏移成功,不代替完整工作台验收。
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ macOS 与 Windows 都提供原生列表界面,提交范围的检查由同一

- 双端的普通批量暂存与提交范围说明放在 ChangeList 选择器的悬停提示中,不常驻占用文件列表高度;Windows 配置不可用等实际错误仍以 `alert` 显示。缩减说明只改变呈现,不改变暂存与提交范围校验。
- 列表名称、当前选择和文件归属属于本地界面状态;Windows 沿用 Zustand 状态库与 WebView 存储适配器,macOS 通过 Git 模块的存储接口注入偏好存储适配器。两端仅将用户分组投影成允许或排除路径,Core 独占提交是否安全的判断。按工作区路径隔离列表;文件归属同时包含所属仓库根和仓库相对路径,工作树和同名文件不串组。
- macOS Worktrees 的更改页复用同一列表管理及范围检查,但模型固定在选中工作树的唯一根目录,不发现同级或嵌套仓库。主工作区根也出现在 Worktrees 时,两处共享该存储键的列表值和故障状态;linked 工作树使用独立键。主提交与工作树提交互斥,提交在途不允许另一处修改同根列表或启动批量暂存;详情标签切换保留各自草稿,工作区重置使旧模型失效。
- Windows 每个工作区使用独立的持久化键;每次动作重新读取并只写目标工作区,禁止用窗口内存中的整个工作区表覆盖共享存储。旧版整表按工作区读取,第一次保存写入新键,旧键保留为迁移备份;新键损坏不能回退到旧快照。存储事件和窗口重新获得焦点时刷新当前工作区,刷新监听由视图创建并在卸载时移除。
- 默认列表接收未分配的新路径。用户选择的当前列表限定本次批量暂存和提交范围;新建列表不会自动搬入已有文件。支持创建、重命名、删除确认,以及右键将多选文件移动到其它列表,Windows 的目录菜单同时支持移动目录内文件。macOS 按仓库、列表分组展示文件,原未跟踪文件保留各行的新增状态。
- 勾选仍是真实 Git index(暂存区)状态。单文件和分组勾选可以操作对应文件;仓库级和顶部批量按钮只影响当前列表。提交说明生成和文件数量只使用当前列表的已暂存文件。macOS 在读取差异前保存分组选择版本和当前暂存路径集合,读取完成和 AI 返回时都重新核对;切换分组(包括切走再切回)或修改文件归属后丢弃旧结果。
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,19 @@ extension GitFeatureModel: CommitWorkflowGit {
@MainActor
enum CommitWorkflowComposition {
static func make(model: AppModel) -> CommitWorkflowCoordinator {
make(model: model, draft: model.commitDraftFeature,
activateGit: { [weak model] in await model?.activateGitModule() })
}

static func make(model: AppModel, draft: CommitDraftFeatureModel,
activateGit: @escaping () async -> (any CommitWorkflowGit)?) -> CommitWorkflowCoordinator {
let runtime = model.services.moduleRuntime
let settings = model.settings
let workspace = model.workspaceFeature
let notification = model.notificationFeature
return CommitWorkflowCoordinator(
draft: model.commitDraftFeature,
activateGit: { [weak model] in await model?.activateGitModule() },
draft: draft,
activateGit: activateGit,
workspaceGeneration: { workspace.workspaceGeneration },
generate: { [weak model] input in
model?.refreshAIConfigurations()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import Foundation
import LitheGitModule

/// Retains each checkout's editor when switching worktrees or detail tabs.
@MainActor
final class WorktreeCommitSession {
let feature: GitFeatureModel
let draft: CommitDraftFeatureModel
let workflow: CommitWorkflowCoordinator

init(feature: GitFeatureModel, draft: CommitDraftFeatureModel, workflow: CommitWorkflowCoordinator) {
self.feature = feature
self.draft = draft
self.workflow = workflow
}
}

@MainActor
final class WorktreeCommitSessions {
private var generation: Int?
private var sessions: [URL: WorktreeCommitSession] = [:]

func session(for feature: GitFeatureModel, generation: Int,
make: () -> WorktreeCommitSession) -> WorktreeCommitSession {
if self.generation != generation {
sessions = [:]
self.generation = generation
}
guard let root = feature.gitRepositoryRoot else { return make() }
if let existing = sessions[root], existing.feature === feature { return existing }
let session = make()
sessions[root] = session
return session
}
}
10 changes: 10 additions & 0 deletions macos/Sources/Lithe/Models/AppModel/AppModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,16 @@ final class AppModel: ObservableObject, Identifiable, UnsavedDocumentHandling {
)
var commitDraftFeature: CommitDraftFeatureModel { featureGraph.commitDraft }
lazy var commitWorkflow = CommitWorkflowComposition.make(model: self)
let worktreeCommitSessions = WorktreeCommitSessions()

func worktreeCommitSession(feature: GitFeatureModel, worktree: GitWorktree) -> WorktreeCommitSession {
let scoped = feature.commitFeature(for: worktree)
return worktreeCommitSessions.session(for: scoped, generation: workspaceFeature.workspaceGeneration) {
let draft = CommitDraftFeatureModel()
return WorktreeCommitSession(feature: scoped, draft: draft,
workflow: CommitWorkflowComposition.make(model: self, draft: draft, activateGit: { [weak scoped] in scoped }))
}
}
var commitMessage: String {
get { commitDraftFeature.message }
set { commitDraftFeature.message = newValue }
Expand Down
2 changes: 1 addition & 1 deletion macos/Sources/Lithe/Views/Git/CommitAreaView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ struct CommitAreaView: View {
private var canCommit: Bool {
feature.changelistCommitError == nil && !feature.changelistEditingDisabled && !stagedChanges.isEmpty &&
!draft.message.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty &&
!feature.isCommitting && !feature.isStagingChanges && feature.pendingSubmoduleCommitPlan == nil && !feature.canRetryWorkspaceCommit
!feature.isCommitting && !feature.isStagingChanges && !feature.isWorktreeCommitBlocked && feature.pendingSubmoduleCommitPlan == nil && !feature.canRetryWorkspaceCommit
}

}
Expand Down
9 changes: 6 additions & 3 deletions macos/Sources/Lithe/Views/Git/GitChangelistBar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import LitheGitModule

struct GitChangelistBar: View {
@ObservedObject var feature: GitFeatureModel
var showsParentReferences = true
@Environment(\.locale) private var locale
@State private var editing = false
@State private var editingID: String?
Expand Down Expand Up @@ -52,9 +53,11 @@ struct GitChangelistBar: View {
.accessibilityLabel("Delete ChangeList")
}
.disabled(feature.changelistEditingDisabled)
Toggle("Update parent repository references", isOn: $feature.includeChangelistParentReferences)
.toggleStyle(.checkbox).font(.caption)
.disabled(feature.changelistEditingDisabled)
if showsParentReferences {
Toggle("Update parent repository references", isOn: $feature.includeChangelistParentReferences)
.toggleStyle(.checkbox).font(.caption)
.disabled(feature.changelistEditingDisabled)
}
if let error = feature.changelistCommitError {
Text(LocalizedStringKey(error)).font(.caption).foregroundStyle(LitheTheme.warning)
}
Expand Down
2 changes: 2 additions & 0 deletions macos/Sources/Lithe/Views/Git/GitWorktreeActions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ struct GitWorktreeActions {
var temporaryDirectory: (() -> URL)? = nil
var showChange: ((GitChange) -> Void)? = nil
var openFile: ((URL, String) -> Void)? = nil
var commitSession: ((GitWorktree) -> WorktreeCommitSession)? = nil
var showCommitSettings: () -> Void = {}
}
Loading
Loading