дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 19:25:54 +02:00
Removing a worktree could delete the folder the window had open: only the main worktree was blocked, so a linked worktree opened as the project, or one containing the project, went after one confirmation, and Force Remove discarded its changes. `vcs_remove_worktree` now compares canonical paths and refuses a target that is the project root or an ancestor of it, with or without force, before running git. Worktrees below the project root stay removable, including a project opened on a parent folder. Sibling covered: the Worktrees tab disables Remove for such a worktree and says why.
70 рядки
2.9 KiB
TypeScript
70 рядки
2.9 KiB
TypeScript
import type { Worktree } from "@/bindings";
|
|
|
|
/**
|
|
* Comparison key of a path: `/` separators, no trailing slash, drive letter lowercased
|
|
* (Git prints `C:/…` with the drive in either case).
|
|
*/
|
|
function pathKey(p: string): string {
|
|
const s = p.replace(/\\/g, "/").replace(/(.)\/+$/, "$1");
|
|
return /^[A-Za-z]:\//.test(s) ? s.toLowerCase() : s;
|
|
}
|
|
|
|
/**
|
|
* Whether worktree `path` (as `git worktree list` prints it) is the repository root `root`
|
|
* this window works in. A trailing slash doesn't count.
|
|
*/
|
|
export function isCurrentWorktree(path: string, root: string | null | undefined): boolean {
|
|
return !!root && pathKey(path) === pathKey(root);
|
|
}
|
|
|
|
/** Whether deleting this worktree would also delete the window's project folder. */
|
|
export function worktreeContainsProject(path: string, projectRoot: string | null | undefined): boolean {
|
|
if (!projectRoot) return false;
|
|
const worktree = pathKey(path);
|
|
const project = pathKey(projectRoot);
|
|
return project === worktree || project.startsWith(worktree.endsWith("/") ? worktree : worktree + "/");
|
|
}
|
|
|
|
/** The worktrees of one repository, loaded from one of its roots. */
|
|
export interface RepoWorktrees {
|
|
/** A VCS root of the repository the query ran from. */
|
|
root: string;
|
|
worktrees: Worktree[];
|
|
/** Why the query failed; the repository is still listed. */
|
|
error?: string;
|
|
}
|
|
|
|
/**
|
|
* One group per repository. A project holds a linked worktree as a root of its own, and
|
|
* every root of a repository lists all of its worktrees: their duplicate lists are
|
|
* merged, the first root (in root order) staying the group's.
|
|
*/
|
|
export function groupWorktrees(lists: readonly RepoWorktrees[]): RepoWorktrees[] {
|
|
// Loaded lists first: a failed query knows only its own root, which a loaded list
|
|
// can claim as one of the repository's worktrees.
|
|
const ordered = [...lists].sort((a, b) => Number(b.worktrees.length > 0) - Number(a.worktrees.length > 0));
|
|
const groups: RepoWorktrees[] = [];
|
|
const byMain = new Map<string, RepoWorktrees>();
|
|
const byWorktree = new Map<string, RepoWorktrees>();
|
|
for (const list of ordered) {
|
|
const main = list.worktrees.find((w) => w.main);
|
|
const group = (main && byMain.get(pathKey(main.path))) || byWorktree.get(pathKey(list.root));
|
|
if (group) {
|
|
// Name the group after the repository's main folder when its root is one of them.
|
|
if (main && pathKey(main.path) === pathKey(list.root)) group.root = list.root;
|
|
if (!group.worktrees.length && list.worktrees.length) {
|
|
group.worktrees = list.worktrees;
|
|
group.error = list.error;
|
|
if (main) byMain.set(pathKey(main.path), group);
|
|
}
|
|
for (const w of list.worktrees) byWorktree.set(pathKey(w.path), group);
|
|
continue;
|
|
}
|
|
const created = { ...list };
|
|
groups.push(created);
|
|
if (main) byMain.set(pathKey(main.path), created);
|
|
for (const w of created.worktrees) byWorktree.set(pathKey(w.path), created);
|
|
}
|
|
return groups;
|
|
}
|