@mikaelkaron/skills-cherry-pick-filter 0.5.1 → 0.6.0

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.
@@ -1,5 +1,5 @@
1
1
  import { Args, Command, Flags } from "@oclif/core";
2
- import { exec, tryExec } from "../lib/git.js";
2
+ import { checkout, cherryPick, diffTree, log, lsRemote, revParse, showRef, tryCheckout, } from "../lib/git.js";
3
3
  function out(sha) {
4
4
  if (!process.stdout.isTTY)
5
5
  process.stdout.write(sha + "\n");
@@ -49,23 +49,30 @@ All human-readable output goes to stderr. stdout emits one picked commit SHA per
49
49
  const { targetBranch } = args;
50
50
  const filters = flags.filter;
51
51
  const dryRun = flags["dry-run"];
52
- const currentBranch = exec("git rev-parse --abbrev-ref HEAD");
52
+ const currentBranch = revParse(["--abbrev-ref", "HEAD"]);
53
53
  if (currentBranch === targetBranch) {
54
54
  this.error(`already on '${targetBranch}'.`, { exit: 1 });
55
55
  }
56
- const localExists = tryExec(`git show-ref --verify --quiet refs/heads/${targetBranch}`).ok;
56
+ const localExists = showRef([
57
+ "--verify",
58
+ "--quiet",
59
+ `refs/heads/${targetBranch}`,
60
+ ]).ok;
57
61
  if (!localExists) {
58
- const remoteResult = tryExec(`git ls-remote --heads origin ${targetBranch}`);
62
+ const remoteResult = lsRemote(["--heads", "origin", targetBranch]);
59
63
  if (remoteResult.ok && remoteResult.output.length > 0) {
60
64
  this.logToStderr(`Checking out '${targetBranch}' from origin...`);
61
- const checkoutResult = tryExec(`git checkout --track origin/${targetBranch}`);
65
+ const checkoutResult = tryCheckout([
66
+ "--track",
67
+ `origin/${targetBranch}`,
68
+ ]);
62
69
  if (!checkoutResult.ok) {
63
70
  this.logToStderr(checkoutResult.output);
64
71
  this.error(`failed to checkout '${targetBranch}' from origin.`, {
65
72
  exit: 1,
66
73
  });
67
74
  }
68
- exec(`git checkout ${currentBranch}`);
75
+ checkout([currentBranch]);
69
76
  }
70
77
  else {
71
78
  this.error(`branch '${targetBranch}' not found locally or on origin.`, {
@@ -76,8 +83,14 @@ All human-readable output goes to stderr. stdout emits one picked commit SHA per
76
83
  const dryRunLabel = dryRun ? " (dry run)" : "";
77
84
  this.logToStderr(`\nSyncing ${currentBranch} → ${targetBranch}${dryRunLabel}`);
78
85
  this.logToStderr(`Filter prefixes: ${filters.join(", ")}\n`);
79
- const pathspecs = filters.map((f) => `:!${f}`).join(" ");
80
- const logOutput = tryExec(`git log ${targetBranch}..HEAD --reverse --format="%H %s" -- ${pathspecs}`);
86
+ const pathspecs = filters.map((f) => `:!${f}`);
87
+ const logOutput = log([
88
+ `${targetBranch}..HEAD`,
89
+ "--reverse",
90
+ "--format=%H %s",
91
+ "--",
92
+ ...pathspecs,
93
+ ]);
81
94
  if (!logOutput.ok || !logOutput.output) {
82
95
  this.logToStderr("Already up to date.");
83
96
  return;
@@ -85,14 +98,17 @@ All human-readable output goes to stderr. stdout emits one picked commit SHA per
85
98
  const candidates = logOutput.output
86
99
  .split("\n")
87
100
  .filter(Boolean)
88
- .map((line) => ({ sha: line.slice(0, 40), subject: line.slice(41) }));
101
+ .map((line) => ({
102
+ sha: line.slice(0, 40),
103
+ subject: line.slice(41),
104
+ }));
89
105
  if (candidates.length === 0) {
90
106
  this.logToStderr("Already up to date.");
91
107
  return;
92
108
  }
93
109
  const mixed = [];
94
110
  for (const { sha, subject } of candidates) {
95
- const files = exec(`git diff-tree --no-commit-id -r --name-only ${sha}`)
111
+ const files = diffTree(["--no-commit-id", "-r", "--name-only", sha])
96
112
  .split("\n")
97
113
  .filter(Boolean);
98
114
  const filteredFiles = files.filter((f) => filters.some((prefix) => f.startsWith(prefix)));
@@ -128,10 +144,10 @@ All human-readable output goes to stderr. stdout emits one picked commit SHA per
128
144
  this.logToStderr("Dry run complete. Run without --dry-run to apply.");
129
145
  return;
130
146
  }
131
- exec(`git checkout ${targetBranch}`);
147
+ checkout([targetBranch]);
132
148
  let picked = 0;
133
149
  for (const { sha, subject } of candidates) {
134
- const result = tryExec(`git cherry-pick ${sha}`);
150
+ const result = cherryPick([sha]);
135
151
  if (result.ok) {
136
152
  this.logToStderr(` pick ${sha.slice(0, 9)} ${subject}`);
137
153
  out(sha);
@@ -146,7 +162,7 @@ All human-readable output goes to stderr. stdout emits one picked commit SHA per
146
162
  });
147
163
  }
148
164
  }
149
- exec(`git checkout ${currentBranch}`);
165
+ checkout([currentBranch]);
150
166
  this.logToStderr(`\nDone: ${picked} picked`);
151
167
  }
152
168
  }
package/dist/lib/git.d.ts CHANGED
@@ -1,5 +1,23 @@
1
- export declare function exec(cmd: string): string;
2
- export declare function tryExec(cmd: string): {
1
+ export declare function revParse(args: string[], cmd?: string): string;
2
+ export declare function showRef(args: string[], cmd?: string): {
3
+ ok: boolean;
4
+ output: string;
5
+ };
6
+ export declare function lsRemote(args: string[], cmd?: string): {
7
+ ok: boolean;
8
+ output: string;
9
+ };
10
+ export declare function checkout(args: string[], cmd?: string): string;
11
+ export declare function tryCheckout(args: string[], cmd?: string): {
12
+ ok: boolean;
13
+ output: string;
14
+ };
15
+ export declare function log(args: string[], cmd?: string): {
16
+ ok: boolean;
17
+ output: string;
18
+ };
19
+ export declare function diffTree(args: string[], cmd?: string): string;
20
+ export declare function cherryPick(args: string[], cmd?: string): {
3
21
  ok: boolean;
4
22
  output: string;
5
23
  };
package/dist/lib/git.js CHANGED
@@ -1,16 +1,56 @@
1
- import { execSync } from "node:child_process";
2
- export function exec(cmd) {
3
- return execSync(cmd, {
1
+ import { spawnSync } from "node:child_process";
2
+ import which from "which";
3
+ function defaultCmd() {
4
+ return (process.env["GIT_CMD"] ?? which.sync("git", { nothrow: true }) ?? "git");
5
+ }
6
+ function run(args, cmd = defaultCmd()) {
7
+ const result = spawnSync(cmd, args, {
4
8
  encoding: "utf8",
5
9
  stdio: ["pipe", "pipe", "inherit"],
6
- }).trim();
10
+ });
11
+ if (result.error) {
12
+ throw new Error(result.error.message.includes("ENOENT")
13
+ ? "git not found. Install it from https://git-scm.com"
14
+ : result.error.message);
15
+ }
16
+ if (result.status !== 0) {
17
+ throw new Error(result.stderr?.trim() ||
18
+ `git ${args[0]} failed with exit code ${result.status}`);
19
+ }
20
+ return result.stdout.trim();
7
21
  }
8
- export function tryExec(cmd) {
22
+ function tryRun(args, cmd = defaultCmd()) {
9
23
  try {
10
- return { ok: true, output: exec(cmd) };
24
+ return { ok: true, output: run(args, cmd) };
11
25
  }
12
26
  catch (e) {
13
27
  const err = e;
14
- return { ok: false, output: err.stderr ?? err.message ?? "" };
28
+ if (err.message?.includes("git not found"))
29
+ throw e;
30
+ return { ok: false, output: err.message ?? "" };
15
31
  }
16
32
  }
33
+ export function revParse(args, cmd) {
34
+ return run(["rev-parse", ...args], cmd);
35
+ }
36
+ export function showRef(args, cmd) {
37
+ return tryRun(["show-ref", ...args], cmd);
38
+ }
39
+ export function lsRemote(args, cmd) {
40
+ return tryRun(["ls-remote", ...args], cmd);
41
+ }
42
+ export function checkout(args, cmd) {
43
+ return run(["checkout", ...args], cmd);
44
+ }
45
+ export function tryCheckout(args, cmd) {
46
+ return tryRun(["checkout", ...args], cmd);
47
+ }
48
+ export function log(args, cmd) {
49
+ return tryRun(["log", ...args], cmd);
50
+ }
51
+ export function diffTree(args, cmd) {
52
+ return run(["diff-tree", ...args], cmd);
53
+ }
54
+ export function cherryPick(args, cmd) {
55
+ return tryRun(["cherry-pick", ...args], cmd);
56
+ }
@@ -61,5 +61,5 @@
61
61
  ]
62
62
  }
63
63
  },
64
- "version": "0.5.1"
64
+ "version": "0.6.0"
65
65
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikaelkaron/skills-cherry-pick-filter",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "bin": {
5
5
  "mks-cherry-pick-filter": "bin/run.js"
6
6
  },
@@ -23,7 +23,8 @@
23
23
  "test:types": "tsc -p test/tsconfig.json"
24
24
  },
25
25
  "dependencies": {
26
- "@oclif/core": "^4"
26
+ "@oclif/core": "^4",
27
+ "which": "^7.0.0"
27
28
  },
28
29
  "devDependencies": {
29
30
  "@oclif/test": "^4.1.18",