a1069211faa5136f7038b3c31e715503fcecd413
- Author
- TheEdgeOfRage <git@theedgeofrage.com>
- Committer
- TheEdgeOfRage <git@theedgeofrage.com>
- Date
Message
Diff
This diff is truncated to protect this page.
1diff --git a/README.md b/README.md
2index f827688dcd2a63183994ec9c5975ff0e8c1e0376..32c0c88dca476c7ae20efe50c3253a8803b64c7c 100644
3--- a/README.md
4+++ b/README.md
5@@ -1,6 +1,6 @@
6 # Pi Tree Pruning
7
8-Pi extension that removes inactive branches from an on-disk session tree. It creates a backup before it changes the session. Use `/tree-prune undo` to restore a backup.
9+Pi extension that permanently removes inactive branches from an on-disk session tree.
10
11 ## Install
12
13diff --git a/index.ts b/index.ts
14index ad3aff71dd5fa0e23b191e6b8716c387aac0e5db..e3c24944cbf7beefa2d7939881813b3c87bf30da 100644
15--- a/index.ts
16+++ b/index.ts
17@@ -1,4 +1,3 @@
18-import { basename } from "node:path";
19 import type {
20 ExtensionAPI,
21 ExtensionCommandContext,
22@@ -8,10 +7,7 @@ import {
23 captureSessionFile,
24 countPruneEntries,
25 getSessionTreeEntries,
26- createBackupPath,
27- listSessionBackups,
28 pruneSessionFile,
29- restoreSessionBackup,
30 toSessionFilePath,
31 type SessionFilePath,
32 } from "./session-file.ts";
33@@ -32,7 +28,7 @@ const COMMAND_NAME = "tree-prune";
34
35 export default function registerTreePruning(pi: ExtensionAPI): void {
36 pi.registerCommand(COMMAND_NAME, {
37- description: "Delete inactive session-tree branches with a backup; use /tree-prune undo to restore",
38+ description: "Delete inactive session-tree branches",
39 handler: runTreePruneCommand,
40 });
41 }
42@@ -53,12 +49,7 @@ export async function runTreePruneCommand(
43 return;
44 }
45
46- if (mode === "undo") {
47- await runUndo(ctx);
48- return;
49- }
50-
51- ctx.ui.notify("Usage: /tree-prune [undo]", "error");
52+ ctx.ui.notify("Usage: /tree-prune", "error");
53 }
54
55 async function runPrune(ctx: ExtensionCommandContext): Promise<void> {
56@@ -96,30 +87,28 @@ async function runPrune(ctx: ExtensionCommandContext): Promise<void> {
57 const item = treeItems.find((treeItem) => treeItem.candidate.rootId === candidate.rootId);
58 return `• ${item ? formatPruneTreeItem(item) : candidate.preview}`;
59 });
60- const backupPath = createBackupPath(sessionFile);
61 const confirmed = await ctx.ui.confirm(
62 "Delete inactive branches?",
63 [
64 "Branches to delete:",
65 ...branchesToPrune,
66 `Entries to delete: ${formatEntryCount(removedEntryCount)}`,
67- `Backup: ${backupPath}`,
68- "The branches will be removed from this session file.",
69+ "The branches will be permanently removed from this session file.",
70 ].join("\n"),
71 );
72 if (!confirmed) return;
73
74- const result = await pruneSessionFile(snapshot, candidatesToPrune, backupPath);
75+ await pruneSessionFile(snapshot, candidatesToPrune);
76 const switchResult = await reloadSession(
77 ctx,
78 sessionFile,
79 activeLeafId,
80- `Tree Prune removed ${formatEntryCount(result.removedEntryCount)}. Backup: ${result.backupPath}`,
81+ `Tree Prune removed ${formatEntryCount(removedEntryCount)}.`,
82 );
83
84 if (switchResult.cancelled) {
85 ctx.ui.notify(
86- `Tree Prune changed the session, but reload was cancelled. Resume ${sessionFile} to load it. Backup: ${result.backupPath}`,
87+ `Tree Prune changed the session, but reload was cancelled. Resume ${sessionFile} to load it.`,
88 "error",
89 );
90 }
91@@ -128,67 +117,6 @@ async function runPrune(ctx: ExtensionCommandContext): Promise<void> {
92 }
93 }
94
95-async function runUndo(ctx: ExtensionCommandContext): Promise<void> {
96- try {
97- await ctx.waitForIdle();
98-
99- const sessionFile = requireSessionFile(ctx);
100- const currentLeafId = ctx.sessionManager.getLeafId();
101- const activeLeafId = currentLeafId ? toEntryId(currentLeafId) : undefined;
102- const currentSnapshot = await captureSessionFile(sessionFile);
103- const backups = await listSessionBackups(sessionFile);
104- if (backups.length === 0) {
105- ctx.ui.notify("Tree Prune found no backups for this session", "info");
106- return;
107- }
108-
109- const selectedBackupPath = await ctx.ui.select(
110- "Restore Tree Prune backup:",
111- backups.map((backup) => basename(backup)),
112- );
113- if (!selectedBackupPath) return;
114-
115- const selectedBackup = backups.find((backup) => basename(backup) === selectedBackupPath);
116- if (!selectedBackup) {
117diff --git a/session-file.ts b/session-file.ts
118index e665d251c1823e08cdffdf57b5b84ced6ec7bbe4..99d33c85fe79fa4a3f8234ee4a852aa23e432b29 100644
119--- a/session-file.ts
120+++ b/session-file.ts
121@@ -1,5 +1,5 @@
122 import { randomUUID } from "node:crypto";
123-import { chmod, link, lstat, open, readdir, readFile, rename, unlink } from "node:fs/promises";
124+import { chmod, lstat, open, readFile, rename, unlink } from "node:fs/promises";
125 import { basename, dirname, join, resolve } from "node:path";
126 import {
127 toEntryId,
128@@ -117,27 +117,6 @@ export function buildPrunedSessionContent(
129 .join("");
130 }
131
132-export function createBackupPath(sessionFile: SessionFilePath): SessionFilePath {
133- const path = toSessionFilePath(sessionFile);
134- const timestamp = new Date().toISOString().replaceAll(":", "-").replaceAll(".", "-");
135- return toSessionFilePath(
136- join(dirname(path), `${basename(path)}.tree-prune-backup-${timestamp}-${randomUUID()}`),
137- );
138-}
139-
140-export async function createSessionBackup(
141- snapshot: SessionFileSnapshot,
142- backupPath = createBackupPath(snapshot.path),
143-): Promise<SessionFilePath> {
144- const path = toSessionFilePath(backupPath);
145- if (dirname(path) !== dirname(snapshot.path)) {
146- throw new Error("Backup must be in the session file directory");
147- }
148-
149- await writeNewFileAtomically(path, snapshot.content, snapshot.identity.mode);
150- return path;
151-}
152-
153 export function countPruneEntries(candidates: readonly BranchCandidate[]): number {
154 return collectPruneEntries(candidates).size;
155 }
156@@ -145,44 +124,9 @@ export function countPruneEntries(candidates: readonly BranchCandidate[]): numbe
157 export async function pruneSessionFile(
158 snapshot: SessionFileSnapshot,
159 candidates: readonly BranchCandidate[],
160- backupPath = createBackupPath(snapshot.path),
161-): Promise<{ backupPath: SessionFilePath; removedEntryCount: number }> {
162- const removedEntryCount = countPruneEntries(candidates);
163+): Promise<void> {
164 const prunedContent = buildPrunedSessionContent(snapshot, candidates);
165- await assertSessionFileUnchanged(snapshot);
166- const createdBackupPath = await createSessionBackup(snapshot, backupPath);
167 await replaceSessionFile(snapshot, prunedContent);
168-
169- return { backupPath: createdBackupPath, removedEntryCount };
170-}
171-
172-export async function restoreSessionBackup(
173- currentSnapshot: SessionFileSnapshot,
174- selectedBackup: SessionFileSnapshot,
175- backupPath = createBackupPath(currentSnapshot.path),
176-): Promise<SessionFilePath> {
177- if (dirname(selectedBackup.path) !== dirname(currentSnapshot.path)) {
178- throw new Error("Backup must be in the session file directory");
179- }
180-
181- await assertSessionFileUnchanged(currentSnapshot);
182- await assertSessionFileUnchanged(selectedBackup);
183- const currentBackupPath = await createSessionBackup(currentSnapshot, backupPath);
184- await replaceSessionFile(currentSnapshot, selectedBackup.content);
185-
186- return currentBackupPath;
187-}
188-
189-export async function listSessionBackups(sessionFile: SessionFilePath): Promise<SessionFilePath[]> {
190- const path = toSessionFilePath(sessionFile);
191- const directory = dirname(path);
192- const prefix = `${basename(path)}.tree-prune-backup-`;
193- const directoryEntries = await readdir(directory, { withFileTypes: true });
194-
195- return directoryEntries
196- .filter((entry) => entry.isFile() && entry.name.startsWith(prefix))
197- .map((entry) => toSessionFilePath(join(directory, entry.name)))
198- .sort((left, right) => right.localeCompare(left));
199 }
200
201 async function replaceSessionFile(snapshot: SessionFileSnapshot, content: string): Promise<void> {
202@@ -198,17 +142,6 @@ async function replaceSessionFile(snapshot: SessionFileSnapshot, content: string
203 }
204 }
205
206-async function writeNewFileAtomically(path: string, content: string, mode: number): Promise<void> {
207- const temporaryPath = temporaryPathFor(path);
208-
209- try {
210- await writeFileAndSync(temporaryPath, content, mode);
211- await link(temporaryPath, path);
212- } finally {
213- await removeIfPresent(temporaryPath);
214- }
215-}
216-
217 async function writeFileAndSync(path: string, content: string, mode: number): Promise<void> {
218 const file = await open(path, "wx", mode);
219 try {