cherrypick-interactive 1.4.3 → 1.5.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/cli.js +795 -720
- package/package.json +2 -8
- package/cli.backup.js +0 -193
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cherrypick-interactive",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Interactively cherry-pick commits that are in dev but not in main, using subject-based comparison.",
|
|
5
5
|
"main": "cli.js",
|
|
6
6
|
"bin": {
|
|
@@ -28,13 +28,7 @@
|
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@biomejs/biome": "^1.9.4"
|
|
30
30
|
},
|
|
31
|
-
"keywords": [
|
|
32
|
-
"git",
|
|
33
|
-
"cli",
|
|
34
|
-
"cherry-pick",
|
|
35
|
-
"interactive",
|
|
36
|
-
"devops"
|
|
37
|
-
],
|
|
31
|
+
"keywords": ["git", "cli", "cherry-pick", "interactive", "devops"],
|
|
38
32
|
"license": "MIT",
|
|
39
33
|
"packageManager": "yarn@4.6.0"
|
|
40
34
|
}
|
package/cli.backup.js
DELETED
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import chalk from "chalk";
|
|
4
|
-
import inquirer from "inquirer";
|
|
5
|
-
import simpleGit from "simple-git";
|
|
6
|
-
import yargs from "yargs";
|
|
7
|
-
|
|
8
|
-
import { hideBin } from "yargs/helpers";
|
|
9
|
-
|
|
10
|
-
const git = simpleGit();
|
|
11
|
-
|
|
12
|
-
const argv = yargs(hideBin(process.argv))
|
|
13
|
-
.scriptName("cherrypick-interactive")
|
|
14
|
-
.usage("$0 [options]")
|
|
15
|
-
.option("dev", {
|
|
16
|
-
type: "string",
|
|
17
|
-
default: "origin/dev",
|
|
18
|
-
describe: "Source branch (contains commits you want).",
|
|
19
|
-
})
|
|
20
|
-
.option("main", {
|
|
21
|
-
type: "string",
|
|
22
|
-
default: "origin/main",
|
|
23
|
-
describe: "Comparison branch (commits present here will be filtered out).",
|
|
24
|
-
})
|
|
25
|
-
.option("since", {
|
|
26
|
-
type: "string",
|
|
27
|
-
default: "1 week ago",
|
|
28
|
-
describe: 'Time window passed to git --since (e.g. "2 weeks ago", "1 month ago").',
|
|
29
|
-
})
|
|
30
|
-
.option("no-fetch", {
|
|
31
|
-
type: "boolean",
|
|
32
|
-
default: false,
|
|
33
|
-
describe: "Skip 'git fetch --prune'.",
|
|
34
|
-
})
|
|
35
|
-
.option("all-yes", {
|
|
36
|
-
type: "boolean",
|
|
37
|
-
default: false,
|
|
38
|
-
describe: "Non-interactive: cherry-pick ALL missing commits (oldest → newest).",
|
|
39
|
-
})
|
|
40
|
-
.option("dry-run", {
|
|
41
|
-
type: "boolean",
|
|
42
|
-
default: false,
|
|
43
|
-
describe: "Print what would be cherry-picked and exit.",
|
|
44
|
-
})
|
|
45
|
-
.help()
|
|
46
|
-
.alias("h", "help")
|
|
47
|
-
.alias("v", "version").argv;
|
|
48
|
-
|
|
49
|
-
const log = (...a) => console.log(...a);
|
|
50
|
-
const err = (...a) => console.error(...a);
|
|
51
|
-
|
|
52
|
-
async function gitRaw(args) {
|
|
53
|
-
const out = await git.raw(args);
|
|
54
|
-
return out.trim();
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
async function getSubjects(branch) {
|
|
58
|
-
const out = await gitRaw(["log", "--no-merges", "--pretty=%s", branch]);
|
|
59
|
-
if (!out) {
|
|
60
|
-
return new Set();
|
|
61
|
-
}
|
|
62
|
-
return new Set(out.split("\n").filter(Boolean));
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
async function getDevCommits(branch, since) {
|
|
66
|
-
const out = await gitRaw(["log", "--no-merges", "--since=" + since, "--pretty=%H %s", branch]);
|
|
67
|
-
|
|
68
|
-
if (!out) {
|
|
69
|
-
return [];
|
|
70
|
-
}
|
|
71
|
-
return out.split("\n").map((line) => {
|
|
72
|
-
const firstSpace = line.indexOf(" ");
|
|
73
|
-
const hash = line.slice(0, firstSpace);
|
|
74
|
-
const subject = line.slice(firstSpace + 1);
|
|
75
|
-
return { hash, subject };
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function filterMissing(devCommits, mainSubjects) {
|
|
80
|
-
return devCommits.filter(({ subject }) => !mainSubjects.has(subject));
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
async function selectCommitsInteractive(missing) {
|
|
84
|
-
const choices = [
|
|
85
|
-
new inquirer.Separator(chalk.gray("── Newest commits ──")),
|
|
86
|
-
...missing.map(({ hash, subject }, idx) => {
|
|
87
|
-
// display-only trim to avoid accidental leading spaces
|
|
88
|
-
const displaySubject = subject.replace(/^[\s\u00A0]+/, "");
|
|
89
|
-
return {
|
|
90
|
-
name: `${chalk.dim(`(${hash.slice(0, 7)})`)} ${displaySubject}`,
|
|
91
|
-
value: hash,
|
|
92
|
-
short: displaySubject,
|
|
93
|
-
idx, // we keep index for oldest→newest ordering later
|
|
94
|
-
};
|
|
95
|
-
}),
|
|
96
|
-
new inquirer.Separator(chalk.gray("── Oldest commits ──")),
|
|
97
|
-
];
|
|
98
|
-
|
|
99
|
-
const { selected } = await inquirer.prompt([
|
|
100
|
-
{
|
|
101
|
-
type: "checkbox",
|
|
102
|
-
name: "selected",
|
|
103
|
-
message: `Select commits to cherry-pick (${missing.length} missing):`,
|
|
104
|
-
choices,
|
|
105
|
-
pageSize: Math.min(20, Math.max(8, missing.length)),
|
|
106
|
-
},
|
|
107
|
-
]);
|
|
108
|
-
|
|
109
|
-
return selected;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
async function cherryPickSequential(hashes) {
|
|
113
|
-
for (const hash of hashes) {
|
|
114
|
-
try {
|
|
115
|
-
await gitRaw(["cherry-pick", hash]);
|
|
116
|
-
const subject = await gitRaw(["show", "--format=%s", "-s", hash]);
|
|
117
|
-
log(`${chalk.green("✓")} cherry-picked ${chalk.dim(`(${hash.slice(0, 7)})`)} ${subject}`);
|
|
118
|
-
} catch (e) {
|
|
119
|
-
err(chalk.red(`✖ Cherry-pick failed on ${hash}`));
|
|
120
|
-
err(chalk.yellow("Resolve conflicts, then run:"));
|
|
121
|
-
err(chalk.yellow(" git add -A && git cherry-pick --continue"));
|
|
122
|
-
err(chalk.yellow("Or abort:"));
|
|
123
|
-
err(chalk.yellow(" git cherry-pick --abort"));
|
|
124
|
-
throw e;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
async function main() {
|
|
130
|
-
try {
|
|
131
|
-
if (!argv["no-fetch"]) {
|
|
132
|
-
log(chalk.gray("Fetching remotes (git fetch --prune)..."));
|
|
133
|
-
await git.fetch(["--prune"]);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const currentBranch = (await gitRaw(["rev-parse", "--abbrev-ref", "HEAD"])) || "HEAD";
|
|
137
|
-
|
|
138
|
-
log(chalk.gray(`Comparing subjects since ${argv.since}`));
|
|
139
|
-
log(chalk.gray(`Dev: ${argv.dev}`));
|
|
140
|
-
log(chalk.gray(`Main: ${argv.main}`));
|
|
141
|
-
|
|
142
|
-
const [devCommits, mainSubjects] = await Promise.all([
|
|
143
|
-
getDevCommits(argv.dev, argv.since),
|
|
144
|
-
getSubjects(argv.main),
|
|
145
|
-
]);
|
|
146
|
-
|
|
147
|
-
const missing = filterMissing(devCommits, mainSubjects);
|
|
148
|
-
|
|
149
|
-
if (missing.length === 0) {
|
|
150
|
-
log(chalk.green("✅ No missing commits found in the selected window."));
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Prepare bottom→top ordering support
|
|
155
|
-
const indexByHash = new Map(missing.map((c, i) => [c.hash, i])); // 0=newest, larger=older
|
|
156
|
-
|
|
157
|
-
let selected;
|
|
158
|
-
if (argv.yes) {
|
|
159
|
-
selected = missing.map((m) => m.hash);
|
|
160
|
-
} else {
|
|
161
|
-
selected = await selectCommitsInteractive(missing);
|
|
162
|
-
if (!selected.length) {
|
|
163
|
-
log(chalk.yellow("No commits selected. Exiting."));
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Bottom → Top (oldest → newest)
|
|
169
|
-
const bottomToTop = [...selected].sort((a, b) => indexByHash.get(b) - indexByHash.get(a));
|
|
170
|
-
|
|
171
|
-
if (argv.dry_run || argv["dry-run"]) {
|
|
172
|
-
log(chalk.cyan("\n--dry-run: would cherry-pick (oldest → newest):"));
|
|
173
|
-
for (const h of bottomToTop) {
|
|
174
|
-
const subj = await gitRaw(["show", "--format=%s", "-s", h]);
|
|
175
|
-
log(`- ${chalk.dim(`(${h.slice(0, 7)})`)} ${subj}`);
|
|
176
|
-
}
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
log(
|
|
181
|
-
chalk.cyan(
|
|
182
|
-
`\nCherry-picking ${bottomToTop.length} commit(s) onto ${currentBranch} (oldest → newest)...\n`,
|
|
183
|
-
),
|
|
184
|
-
);
|
|
185
|
-
await cherryPickSequential(bottomToTop);
|
|
186
|
-
log(chalk.green(`\n✅ Done on ${currentBranch}`));
|
|
187
|
-
} catch (e) {
|
|
188
|
-
err(chalk.red(`\n❌ Error: ${e.message || e}`));
|
|
189
|
-
process.exit(1);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
main();
|