@evanpurkhiser/tooling-personal 1.51.0 → 1.53.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/cherry-pick.js +50 -13
- package/dist/editor.js +1 -1
- package/package.json +1 -1
package/dist/cherry-pick.js
CHANGED
|
@@ -5,6 +5,41 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.cherryPickOnto = cherryPickOnto;
|
|
7
7
|
const simple_git_1 = __importDefault(require("simple-git"));
|
|
8
|
+
/**
|
|
9
|
+
* Produce an actionable error for a failed `merge-tree` cherry-pick.
|
|
10
|
+
*
|
|
11
|
+
* The common failure mode for this workflow is picking a commit that depends
|
|
12
|
+
* on earlier local-only commits not yet on the base — the merge then conflicts
|
|
13
|
+
* with content the base hasn't seen. Detect that case explicitly so the caller
|
|
14
|
+
* (often an LLM) gets a clear next step instead of a generic "conflict".
|
|
15
|
+
*/
|
|
16
|
+
async function buildPickError(git, sha, base, baseSha, parentSha, conflictLines) {
|
|
17
|
+
const conflictFiles = [
|
|
18
|
+
...new Set(conflictLines.map(line => line.split('\t').slice(1).join('\t'))),
|
|
19
|
+
];
|
|
20
|
+
// Local commits between `base` and the picked commit's parent that touched
|
|
21
|
+
// any conflicting file are candidate blockers — the actual conflict is
|
|
22
|
+
// caused by at least one of them. We don't narrow further (would require
|
|
23
|
+
// blaming the conflicting regions specifically), but in this workflow
|
|
24
|
+
// history is linear so any narrower set would still need to be on the
|
|
25
|
+
// remote alongside its ancestors.
|
|
26
|
+
const candidateLog = (await git.raw([
|
|
27
|
+
'log',
|
|
28
|
+
'--reverse',
|
|
29
|
+
'--format=%h %s',
|
|
30
|
+
`${baseSha}..${parentSha}`,
|
|
31
|
+
'--',
|
|
32
|
+
...conflictFiles,
|
|
33
|
+
])).trim();
|
|
34
|
+
const fileList = conflictFiles.map(file => `- ${file}`).join('\n');
|
|
35
|
+
const header = `Cannot cherry-pick ${sha.slice(0, 8)} onto tip of ${base}, merge conflicts in:\n${fileList}`;
|
|
36
|
+
if (!candidateLog) {
|
|
37
|
+
return new Error(header);
|
|
38
|
+
}
|
|
39
|
+
const candidates = candidateLog.split('\n');
|
|
40
|
+
const candidateList = candidates.map(line => `- ${line}`).join('\n');
|
|
41
|
+
return new Error(`${header}\n\n${candidates.length} earlier local commit(s) changed those file(s):\n${candidateList}`);
|
|
42
|
+
}
|
|
8
43
|
/**
|
|
9
44
|
* Build a commit object equivalent to cherry-picking `sha` onto `base`, without
|
|
10
45
|
* touching the working tree or any ref.
|
|
@@ -21,19 +56,21 @@ async function cherryPickOnto(sha, base) {
|
|
|
21
56
|
const git = (0, simple_git_1.default)();
|
|
22
57
|
const parentSha = (await git.revparse([`${sha}^`])).trim();
|
|
23
58
|
const baseSha = (await git.revparse([base])).trim();
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
59
|
+
// `git merge-tree --write-tree` exits non-zero on conflicts, but simple-git's
|
|
60
|
+
// .raw does not surface that — it just returns stdout. On success stdout is
|
|
61
|
+
// a single tree OID; on conflict it's the tree OID followed by lines of
|
|
62
|
+
// `<mode> <oid> <stage>\t<path>`. Detect the conflict case explicitly.
|
|
63
|
+
const mergeOutput = (await git.raw([
|
|
64
|
+
'merge-tree',
|
|
65
|
+
'--write-tree',
|
|
66
|
+
'--no-messages',
|
|
67
|
+
`--merge-base=${parentSha}`,
|
|
68
|
+
baseSha,
|
|
69
|
+
sha,
|
|
70
|
+
])).trim();
|
|
71
|
+
const [treeOid, ...conflictLines] = mergeOutput.split('\n');
|
|
72
|
+
if (conflictLines.length > 0) {
|
|
73
|
+
throw await buildPickError(git, sha, base, baseSha, parentSha, conflictLines);
|
|
37
74
|
}
|
|
38
75
|
const info = await git.raw(['show', '-s', '--format=%an%n%ae%n%aI%n%B', sha]);
|
|
39
76
|
const lines = info.split('\n');
|
package/dist/editor.js
CHANGED
|
@@ -14,7 +14,7 @@ async function editPullRequest(commit) {
|
|
|
14
14
|
const prTemplate = `${commit.message}${split}${messageBody}`;
|
|
15
15
|
const pullEditFile = node_path_1.default.join(await (0, utils_1.getRepoPath)(), '.git', 'PULLREQ_EDITMSG');
|
|
16
16
|
await promises_1.default.writeFile(pullEditFile, prTemplate);
|
|
17
|
-
const quotedFile = `'${pullEditFile.
|
|
17
|
+
const quotedFile = `'${pullEditFile.replaceAll(`'`, `'\\''`)}'`;
|
|
18
18
|
const editor = (0, node_child_process_1.spawn)(`${process.env.EDITOR ?? 'vim'} ${quotedFile}`, {
|
|
19
19
|
shell: true,
|
|
20
20
|
stdio: 'inherit',
|
package/package.json
CHANGED