Skip to content

git.discard 在「会话 cwd 位于仓库子目录 + 同名相对路径存在」时会还原错误的文件 #765

Description

@nanami-0713

问题

resolveGitPath(src/index.ts 附近 151-161 行)对相对路径的解析优先级是:会话 cwd 相对路径若存在,则优先于仓库根相对路径:

const sessionPath = requireAbsolute(join(cwd, raw))
if (await stat(sessionPath).then(() => true).catch(() => false)) return sessionPath
const root = await git.repoRoot(cwd, selected).catch(() => cwd)
return requireAbsolute(join(root, raw))

但 Git status/diff 行的路径是仓库根相对的。当会话 cwd 是仓库的子目录、且子目录下恰好存在同名相对路径时,存在性检查命中,解析结果指向子目录下的同名文件而不是状态行所指的是文件。

危害集中在前瞻性 destructive 的 git.discard

  • git.stage/git.unstage 不受影响(它们把原始相对路径直接交给 git -C root)
  • 但 git.discard(src/index.ts 410 行)先 resolveGitPath 再 git checkout -- <绝对路径>:命中误解析时还原的是另一个文件,状态行所指文件的改动原封不动

具体场景:会话 cwd /repo/pkg,状态行 "README.md"(实际是 /repo/README.md 被修改),而 /repo/pkg/README.md 也存在 → 点 discard(确认后)还原的是 /repo/pkg/README.md;若后者也有未提交改动,这些改动被销毁,而用户以为处理的是状态行那一个文件。若 pkg 下副本无改动,git checkout -- 静默无操作,用户会误以为状态行改动已还原。

同一误解析也影响 fs.read/git.diff 对状态行的预览(展示的是子目录同名文件的内容/diff)。

注释表明「cwd 相对优先」是为嵌套会话的读取安全边界刻意设计的——这个意图合理,但它同时被用于构造破坏性 pathspec,两者冲突。

建议方向

  • 对 git.discard(以及状态行驱动的 diff 预览)改为始终按仓库根解析,或
  • 解析后校验该路径确被 git 跟踪(git ls-files 命中)再执行 checkout,未命中时回退仓库根解析,或
  • 至少在 discard 确认对话框里显示最终解析出的绝对路径,让用户确认的对象无歧义

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2中:一般功能/优化area/gitGit panel: status, branches, commit, worktree, multi-repobugSomething isn't working

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions