@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.
- package/dist/commands/cherry-pick-filter.js +29 -13
- package/dist/lib/git.d.ts +20 -2
- package/dist/lib/git.js +47 -7
- package/oclif.manifest.json +1 -1
- package/package.json +3 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Args, Command, Flags } from "@oclif/core";
|
|
2
|
-
import {
|
|
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 =
|
|
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 =
|
|
56
|
+
const localExists = showRef([
|
|
57
|
+
"--verify",
|
|
58
|
+
"--quiet",
|
|
59
|
+
`refs/heads/${targetBranch}`,
|
|
60
|
+
]).ok;
|
|
57
61
|
if (!localExists) {
|
|
58
|
-
const remoteResult =
|
|
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 =
|
|
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
|
-
|
|
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}`)
|
|
80
|
-
const logOutput =
|
|
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) => ({
|
|
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 =
|
|
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
|
-
|
|
147
|
+
checkout([targetBranch]);
|
|
132
148
|
let picked = 0;
|
|
133
149
|
for (const { sha, subject } of candidates) {
|
|
134
|
-
const result =
|
|
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
|
-
|
|
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
|
|
2
|
-
export declare function
|
|
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 {
|
|
2
|
-
|
|
3
|
-
|
|
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
|
-
})
|
|
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
|
-
|
|
22
|
+
function tryRun(args, cmd = defaultCmd()) {
|
|
9
23
|
try {
|
|
10
|
-
return { ok: true, output:
|
|
24
|
+
return { ok: true, output: run(args, cmd) };
|
|
11
25
|
}
|
|
12
26
|
catch (e) {
|
|
13
27
|
const err = e;
|
|
14
|
-
|
|
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
|
+
}
|
package/oclif.manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikaelkaron/skills-cherry-pick-filter",
|
|
3
|
-
"version": "0.
|
|
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",
|