my-durable 0.0.23 → 0.0.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -167,6 +167,7 @@ export async function gitStatus(path: string): Promise<GitStatus> {
167
167
  export type GitOp =
168
168
  | "diff"
169
169
  | "log"
170
+ | "log-list"
170
171
  | "fetch"
171
172
  | "pull"
172
173
  | "switch"
@@ -174,7 +175,13 @@ export type GitOp =
174
175
  | "commit"
175
176
  | "worktree-list"
176
177
  | "worktree-add"
177
- | "worktree-remove";
178
+ | "worktree-remove"
179
+ | "push"
180
+ | "stage"
181
+ | "discard"
182
+ | "diff-file"
183
+ | "stash-push"
184
+ | "stash-pop";
178
185
 
179
186
  /** Plain string an arg may never contain — we build argv ourselves anyway. */
180
187
  const safeRef = (s: string) => /^[\w./@+-]+$/.test(s) && !s.includes("..");
@@ -183,14 +190,39 @@ const safeRef = (s: string) => /^[\w./@+-]+$/.test(s) && !s.includes("..");
183
190
  export async function gitOp(
184
191
  path: string,
185
192
  op: GitOp,
186
- opts: { ref?: string; message?: string; target?: string; branch?: string; files?: string[] } = {},
187
- ): Promise<{ text: string }> {
188
- const { ref, message, target, branch, files } = opts;
193
+ opts: {
194
+ ref?: string;
195
+ message?: string;
196
+ target?: string;
197
+ branch?: string;
198
+ files?: string[];
199
+ create?: boolean;
200
+ staged?: boolean;
201
+ } = {},
202
+ ): Promise<{ text: string; commits?: { hash: string; refs: string; author: string; date: string; subject: string }[] }> {
203
+ const { ref, message, target, branch, files, create, staged } = opts;
189
204
  switch (op) {
190
205
  case "diff":
191
206
  return { text: await git(path, ["--no-pager", "diff", ...(ref && safeRef(ref) ? [ref] : [])]) };
192
207
  case "log":
193
208
  return { text: (await git(path, ["--no-pager", "log", "--oneline", "-n", "30", ...(ref && safeRef(ref) ? [ref] : [])])).trimEnd() };
209
+ case "log-list": {
210
+ // IntelliJ's log window: hash, refs, author, date, subject — structured.
211
+ const raw = await git(
212
+ path,
213
+ ["--no-pager", "log", "-n", "50", "--date=short", "--pretty=format:%h%x1f%D%x1f%an%x1f%ad%x1f%s"],
214
+ );
215
+ return {
216
+ text: "",
217
+ commits: raw
218
+ .split("\n")
219
+ .filter(Boolean)
220
+ .map((l) => {
221
+ const [hash, refs, author, date, subject] = l.split("\x1f");
222
+ return { hash: hash ?? "", refs: refs ?? "", author: author ?? "", date: date ?? "", subject: subject ?? "" };
223
+ }),
224
+ };
225
+ }
194
226
  case "fetch":
195
227
  return { text: (await git(path, ["fetch", "--all", "--prune"])).trim() || "fetched" };
196
228
  case "pull":
@@ -199,8 +231,28 @@ export async function gitOp(
199
231
  return { text: (await git(path, ["--no-pager", "branch", "-vv"])).trimEnd() };
200
232
  case "switch": {
201
233
  if (!ref || !safeRef(ref)) throw Error("switch needs a branch name (letters, digits, ./-_)");
202
- return { text: (await git(path, ["switch", ref])).trim() };
234
+ // create:true = git switch -c — IntelliJ's "new branch" action.
235
+ return { text: (await git(path, ["switch", ...(create ? ["-c"] : []), ref])).trim() };
236
+ }
237
+ case "push":
238
+ return { text: (await git(path, ["push"], GIT_SLOW_TIMEOUT)).trim() || "pushed (or nothing to push)" };
239
+ case "stage": {
240
+ if (!files?.length) throw Error("stage needs files");
241
+ return { text: (await git(path, ["add", "--", ...files])).trim() || `staged ${files.length} file(s)` };
242
+ }
243
+ case "discard": {
244
+ // IntelliJ's Rollback: index AND worktree back to HEAD for these paths.
245
+ if (!files?.length) throw Error("discard needs files");
246
+ return { text: (await git(path, ["checkout", "HEAD", "--", ...files])).trim() || `discarded ${files.length} file(s)` };
247
+ }
248
+ case "diff-file": {
249
+ if (!target) throw Error("diff-file needs a file path");
250
+ return { text: (await git(path, ["--no-pager", "diff", ...(staged ? ["--cached"] : []), "HEAD", "--", target])).trim() || "no diff" };
203
251
  }
252
+ case "stash-push":
253
+ return { text: (await git(path, ["stash", "push", ...(message ? ["-m", message] : [])])).trim() || "stashed" };
254
+ case "stash-pop":
255
+ return { text: (await git(path, ["stash", "pop"])).trim() || "stash popped" };
204
256
  case "commit": {
205
257
  const msg = (message ?? "").trim();
206
258
  if (!msg) throw Error("commit needs a message");
@@ -11,7 +11,7 @@ import type { DevicePairing } from "./pairing.js";
11
11
  type Async = (req: Request, res: Response) => Promise<unknown>;
12
12
  const route = (fn: Async) => (req: Request, res: Response, next: NextFunction) => fn(req, res).catch(next);
13
13
  import { gitOp, gitStatus, gitStatusBatch, type GitOp } from "./git.ts";
14
- const GIT_OPS: GitOp[] = ["diff", "log", "fetch", "pull", "switch", "branch", "commit", "worktree-list", "worktree-add", "worktree-remove"];
14
+ const GIT_OPS: GitOp[] = ["diff", "log", "log-list", "fetch", "pull", "push", "switch", "branch", "commit", "stage", "discard", "diff-file", "stash-push", "stash-pop", "worktree-list", "worktree-add", "worktree-remove"];
15
15
 
16
16
  interface Locals {
17
17
  row: SessionRow;