git-stack-cli 2.9.3 → 2.9.5
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/js/index.js +77 -77
- package/package.json +1 -1
- package/src/app/DebugOutput.tsx +40 -0
- package/src/app/GithubApiError.tsx +31 -28
- package/src/app/ManualRebase.tsx +6 -1
- package/src/app/Output.tsx +8 -17
- package/src/app/Store.tsx +35 -71
- package/src/app/SyncGithub.tsx +22 -58
- package/src/app/VerboseDebugInfo.tsx +8 -0
- package/src/core/CommitMetadata.ts +88 -58
- package/src/core/cache_message.tsx +36 -0
- package/src/core/cli.ts +12 -5
- package/src/core/git.tsx +193 -0
- package/src/core/github.tsx +136 -76
- package/src/index.tsx +0 -5
- package/src/types/global.d.ts +1 -0
- package/src/app/LogTimestamp.tsx +0 -8
- package/src/core/git.ts +0 -83
package/src/core/github.tsx
CHANGED
|
@@ -10,6 +10,7 @@ import { Brackets } from "~/app/Brackets";
|
|
|
10
10
|
import { FormatText } from "~/app/FormatText";
|
|
11
11
|
import { Store } from "~/app/Store";
|
|
12
12
|
import { Timer } from "~/core/Timer";
|
|
13
|
+
import { cache_message } from "~/core/cache_message";
|
|
13
14
|
import { cli } from "~/core/cli";
|
|
14
15
|
import { colors } from "~/core/colors";
|
|
15
16
|
import { get_tmp_dir } from "~/core/get_tmp_dir";
|
|
@@ -40,7 +41,6 @@ export async function pr_list(): Promise<Array<PullRequest>> {
|
|
|
40
41
|
if (actions.isDebug()) {
|
|
41
42
|
actions.output(
|
|
42
43
|
<FormatText
|
|
43
|
-
wrapper={<Ink.Text dimColor />}
|
|
44
44
|
message="Github cache {count} open PRs from {repo_path} authored by {username}"
|
|
45
45
|
values={{
|
|
46
46
|
count: (
|
|
@@ -101,7 +101,8 @@ export async function pr_status(branch: string): Promise<null | PullRequest> {
|
|
|
101
101
|
);
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
const
|
|
104
|
+
const commmand = `pr view ${branch} --repo ${repo_path} ${JSON_FIELDS}`;
|
|
105
|
+
const pr = await gh_json<PullRequest>(commmand, { branch });
|
|
105
106
|
|
|
106
107
|
if (pr instanceof Error) {
|
|
107
108
|
return null;
|
|
@@ -241,48 +242,37 @@ export async function pr_draft(args: DraftPullRequestArgs) {
|
|
|
241
242
|
}
|
|
242
243
|
}
|
|
243
244
|
|
|
244
|
-
export async function pr_diff(
|
|
245
|
-
|
|
246
|
-
const
|
|
245
|
+
export async function pr_diff(branch: string) {
|
|
246
|
+
// https://cli.github.com/manual/gh_pr_diff
|
|
247
|
+
const result = await gh(`pr diff --color=never ${branch}`, { branch });
|
|
247
248
|
|
|
248
|
-
|
|
249
|
+
if (result instanceof Error) {
|
|
250
|
+
handle_error(result.message);
|
|
251
|
+
}
|
|
249
252
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
actions.debug(
|
|
253
|
-
cache_message({
|
|
254
|
-
hit: true,
|
|
255
|
-
message: "Github pr_diff cache",
|
|
256
|
-
extra: number,
|
|
257
|
-
}),
|
|
258
|
-
);
|
|
259
|
-
}
|
|
253
|
+
return result;
|
|
254
|
+
}
|
|
260
255
|
|
|
261
|
-
|
|
262
|
-
|
|
256
|
+
export async function pr_compare(branch: string) {
|
|
257
|
+
const state = Store.getState();
|
|
258
|
+
const master_branch = state.master_branch;
|
|
259
|
+
const repo_path = state.repo_path;
|
|
260
|
+
invariant(master_branch, "master_branch must exist");
|
|
261
|
+
invariant(repo_path, "repo_path must exist");
|
|
263
262
|
|
|
264
|
-
|
|
265
|
-
actions.debug(
|
|
266
|
-
cache_message({
|
|
267
|
-
hit: false,
|
|
268
|
-
message: "Github pr_diff cache",
|
|
269
|
-
extra: number,
|
|
270
|
-
}),
|
|
271
|
-
);
|
|
272
|
-
}
|
|
263
|
+
const master_branch_name = master_branch.replace(/^origin\//, "");
|
|
273
264
|
|
|
274
|
-
//
|
|
275
|
-
const
|
|
265
|
+
// gh api repos/openai/openai/compare/master...chrome/publish/vine-1211---4h2xmw0o3ndvnt
|
|
266
|
+
const result = await gh_json<BranchCompare>(
|
|
267
|
+
`api repos/${repo_path}/compare/${master_branch_name}...${branch}`,
|
|
268
|
+
{ branch },
|
|
269
|
+
);
|
|
276
270
|
|
|
277
|
-
if (
|
|
278
|
-
handle_error(
|
|
271
|
+
if (result instanceof Error) {
|
|
272
|
+
handle_error(result.message);
|
|
279
273
|
}
|
|
280
274
|
|
|
281
|
-
|
|
282
|
-
state.cache_pr_diff[number] = cli_result.output;
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
return cli_result.stdout;
|
|
275
|
+
return result;
|
|
286
276
|
}
|
|
287
277
|
|
|
288
278
|
// pull request JSON fields
|
|
@@ -290,29 +280,99 @@ export async function pr_diff(number: number) {
|
|
|
290
280
|
// prettier-ignore
|
|
291
281
|
const JSON_FIELDS = "--json id,number,state,baseRefName,headRefName,commits,title,body,url,isDraft";
|
|
292
282
|
|
|
283
|
+
type GhCmdOptions = {
|
|
284
|
+
branch?: string;
|
|
285
|
+
};
|
|
286
|
+
|
|
293
287
|
// consistent handle gh cli commands returning json
|
|
294
288
|
// redirect to tmp file to avoid scrollback overflow causing scrollback to be cleared
|
|
295
|
-
async function gh_json<T>(command: string): Promise<T | Error> {
|
|
289
|
+
async function gh_json<T>(command: string, gh_options?: GhCmdOptions): Promise<T | Error> {
|
|
290
|
+
const gh_result = await gh(command, gh_options);
|
|
291
|
+
|
|
292
|
+
if (gh_result instanceof Error) {
|
|
293
|
+
return gh_result;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
const json = JSON.parse(gh_result);
|
|
298
|
+
return json as T;
|
|
299
|
+
} catch (error) {
|
|
300
|
+
return new Error(`gh_json JSON.parse: ${error}`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// consistent handle gh cli commands
|
|
305
|
+
// redirect to tmp file to avoid scrollback overflow causing scrollback to be cleared
|
|
306
|
+
async function gh(command: string, gh_options?: GhCmdOptions): Promise<string | Error> {
|
|
307
|
+
const state = Store.getState();
|
|
308
|
+
const actions = state.actions;
|
|
309
|
+
|
|
310
|
+
if (gh_options?.branch) {
|
|
311
|
+
const branch = gh_options.branch;
|
|
312
|
+
|
|
313
|
+
type CacheEntryByHeadRefName = (typeof state.cache_gh_cli_by_branch)[string][string];
|
|
314
|
+
|
|
315
|
+
let cache: undefined | CacheEntryByHeadRefName = undefined;
|
|
316
|
+
|
|
317
|
+
if (branch) {
|
|
318
|
+
if (state.cache_gh_cli_by_branch[branch]) {
|
|
319
|
+
cache = state.cache_gh_cli_by_branch[branch][command];
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (cache) {
|
|
324
|
+
if (actions.isDebug()) {
|
|
325
|
+
actions.debug(
|
|
326
|
+
cache_message({
|
|
327
|
+
hit: true,
|
|
328
|
+
message: "gh cache",
|
|
329
|
+
extra: command,
|
|
330
|
+
}),
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return cache;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (actions.isDebug()) {
|
|
338
|
+
actions.debug(
|
|
339
|
+
cache_message({
|
|
340
|
+
hit: false,
|
|
341
|
+
message: "gh cache",
|
|
342
|
+
extra: command,
|
|
343
|
+
}),
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
296
348
|
// hash command for unique short string
|
|
297
349
|
let hash = crypto.createHash("md5").update(command).digest("hex");
|
|
298
|
-
let tmp_filename = safe_filename(`
|
|
299
|
-
const
|
|
350
|
+
let tmp_filename = safe_filename(`gh-${hash}`);
|
|
351
|
+
const tmp_filepath = path.join(await get_tmp_dir(), `${tmp_filename}`);
|
|
300
352
|
|
|
301
353
|
const options = { ignoreExitCode: true };
|
|
302
|
-
const cli_result = await cli(`gh ${command} > ${
|
|
354
|
+
const cli_result = await cli(`gh ${command} > ${tmp_filepath}`, options);
|
|
303
355
|
|
|
304
356
|
if (cli_result.code !== 0) {
|
|
305
357
|
return new Error(cli_result.output);
|
|
306
358
|
}
|
|
307
359
|
|
|
308
360
|
// read from file
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
361
|
+
let content = String(await fs.readFile(tmp_filepath));
|
|
362
|
+
content = content.trim();
|
|
363
|
+
|
|
364
|
+
if (gh_options?.branch) {
|
|
365
|
+
const branch = gh_options.branch;
|
|
366
|
+
|
|
367
|
+
actions.set((state) => {
|
|
368
|
+
if (!state.cache_gh_cli_by_branch[branch]) {
|
|
369
|
+
state.cache_gh_cli_by_branch[branch] = {};
|
|
370
|
+
}
|
|
371
|
+
state.cache_gh_cli_by_branch[branch][command] = content;
|
|
372
|
+
});
|
|
315
373
|
}
|
|
374
|
+
|
|
375
|
+
return content;
|
|
316
376
|
}
|
|
317
377
|
|
|
318
378
|
function handle_error(output: string): never {
|
|
@@ -349,36 +409,6 @@ function safe_filename(value: string): string {
|
|
|
349
409
|
return value.replace(RE.non_alphanumeric_dash, "-");
|
|
350
410
|
}
|
|
351
411
|
|
|
352
|
-
type CacheMessageArgs = {
|
|
353
|
-
hit: boolean;
|
|
354
|
-
message: React.ReactNode;
|
|
355
|
-
extra: React.ReactNode;
|
|
356
|
-
};
|
|
357
|
-
|
|
358
|
-
function cache_message(args: CacheMessageArgs) {
|
|
359
|
-
const status = args.hit ? (
|
|
360
|
-
<Ink.Text bold color={colors.green}>
|
|
361
|
-
HIT
|
|
362
|
-
</Ink.Text>
|
|
363
|
-
) : (
|
|
364
|
-
<Ink.Text bold color={colors.red}>
|
|
365
|
-
MISS
|
|
366
|
-
</Ink.Text>
|
|
367
|
-
);
|
|
368
|
-
|
|
369
|
-
return (
|
|
370
|
-
<FormatText
|
|
371
|
-
wrapper={<Ink.Text dimColor />}
|
|
372
|
-
message="{message} {status} {extra}"
|
|
373
|
-
values={{
|
|
374
|
-
message: args.message,
|
|
375
|
-
status,
|
|
376
|
-
extra: args.extra,
|
|
377
|
-
}}
|
|
378
|
-
/>
|
|
379
|
-
);
|
|
380
|
-
}
|
|
381
|
-
|
|
382
412
|
type Commit = {
|
|
383
413
|
authoredDate: string; // "2023-10-22T23:13:35Z"
|
|
384
414
|
authors: [
|
|
@@ -408,6 +438,36 @@ export type PullRequest = {
|
|
|
408
438
|
isDraft: boolean;
|
|
409
439
|
};
|
|
410
440
|
|
|
441
|
+
type MergeBaseCommit = {
|
|
442
|
+
author: unknown;
|
|
443
|
+
comments_url: string;
|
|
444
|
+
commit: unknown;
|
|
445
|
+
committer: unknown;
|
|
446
|
+
html_url: string;
|
|
447
|
+
node_id: string;
|
|
448
|
+
parents: unknown;
|
|
449
|
+
sha: string;
|
|
450
|
+
url: string;
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
export type BranchCompare = {
|
|
454
|
+
ahead_by: number;
|
|
455
|
+
base_commit: unknown;
|
|
456
|
+
behind_by: number;
|
|
457
|
+
commits: unknown;
|
|
458
|
+
diff_url: string;
|
|
459
|
+
|
|
460
|
+
files: unknown;
|
|
461
|
+
html_url: string;
|
|
462
|
+
|
|
463
|
+
merge_base_commit: MergeBaseCommit;
|
|
464
|
+
patch_url: string;
|
|
465
|
+
permalink_url: string;
|
|
466
|
+
status: unknown;
|
|
467
|
+
total_commits: number;
|
|
468
|
+
url: string;
|
|
469
|
+
};
|
|
470
|
+
|
|
411
471
|
const RE = {
|
|
412
472
|
non_alphanumeric_dash: /[^a-zA-Z0-9_-]+/g,
|
|
413
473
|
};
|
package/src/index.tsx
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
import * as React from "react";
|
|
6
6
|
|
|
7
7
|
import fs from "node:fs/promises";
|
|
8
|
-
import path from "node:path";
|
|
9
8
|
|
|
10
9
|
import * as Ink from "ink-cjs";
|
|
11
10
|
|
|
@@ -61,10 +60,6 @@ import { pretty_json } from "~/core/pretty_json";
|
|
|
61
60
|
|
|
62
61
|
actions.debug(pretty_json(argv as any));
|
|
63
62
|
|
|
64
|
-
const PATH = process.env["PATH"];
|
|
65
|
-
const PATH_LIST = pretty_json(PATH.split(path.delimiter));
|
|
66
|
-
actions.debug(`process.env.PATH ${PATH_LIST}`);
|
|
67
|
-
|
|
68
63
|
await ink.waitUntilExit();
|
|
69
64
|
|
|
70
65
|
function maybe_verbose_help() {
|
package/src/types/global.d.ts
CHANGED
package/src/app/LogTimestamp.tsx
DELETED
package/src/core/git.ts
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { Store } from "~/app/Store";
|
|
2
|
-
import * as Metadata from "~/core/Metadata";
|
|
3
|
-
import { cli } from "~/core/cli";
|
|
4
|
-
import { invariant } from "~/core/invariant";
|
|
5
|
-
|
|
6
|
-
type CommitList = Awaited<ReturnType<typeof get_commits>>;
|
|
7
|
-
|
|
8
|
-
export type Commit = CommitList[0];
|
|
9
|
-
|
|
10
|
-
export async function get_commits(dot_range: string) {
|
|
11
|
-
const log_result = await cli(`git log ${dot_range} --format=${FORMAT} --color=never`);
|
|
12
|
-
|
|
13
|
-
if (!log_result.stdout) {
|
|
14
|
-
return [];
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const commit_list = [];
|
|
18
|
-
|
|
19
|
-
for (let record of log_result.stdout.split(SEP.record)) {
|
|
20
|
-
record = record.replace(/^\n/, "");
|
|
21
|
-
record = record.replace(/\n$/, "");
|
|
22
|
-
|
|
23
|
-
if (!record) continue;
|
|
24
|
-
|
|
25
|
-
const [sha, full_message] = record.split(SEP.field);
|
|
26
|
-
|
|
27
|
-
// ensure sha is a hex string, otherwise we should throw an error
|
|
28
|
-
if (!RE.git_sha.test(sha)) {
|
|
29
|
-
const actions = Store.getState().actions;
|
|
30
|
-
const sep_values = JSON.stringify(Object.values(SEP));
|
|
31
|
-
const message = `unable to parse git commits, maybe commit message contained ${sep_values}`;
|
|
32
|
-
actions.error(message);
|
|
33
|
-
actions.exit(19);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const metadata = Metadata.read(full_message);
|
|
37
|
-
const branch_id = metadata.id;
|
|
38
|
-
const subject_line = metadata.subject || "";
|
|
39
|
-
const title = metadata.title;
|
|
40
|
-
const master_base = metadata.base === "master";
|
|
41
|
-
|
|
42
|
-
const commit = {
|
|
43
|
-
sha,
|
|
44
|
-
full_message,
|
|
45
|
-
subject_line,
|
|
46
|
-
branch_id,
|
|
47
|
-
title,
|
|
48
|
-
master_base,
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
commit_list.push(commit);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
commit_list.reverse();
|
|
55
|
-
|
|
56
|
-
return commit_list;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export async function get_diff(commit_list: CommitList) {
|
|
60
|
-
invariant(commit_list.length, "commit_list must exist");
|
|
61
|
-
const first_commit = commit_list[0];
|
|
62
|
-
const last_commit = commit_list[commit_list.length - 1];
|
|
63
|
-
const sha_range = `${first_commit.sha}~1..${last_commit.sha}`;
|
|
64
|
-
const diff_result = await cli(`git --no-pager diff --color=never ${sha_range}`);
|
|
65
|
-
return diff_result.stdout;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Why these separators?
|
|
69
|
-
// - Rare in human written text
|
|
70
|
-
// - Supported in git %xNN to write bytes
|
|
71
|
-
// - Supported in javascript \xNN to write bytes
|
|
72
|
-
// - Used historically as separators in unicode
|
|
73
|
-
// https://en.wikipedia.org/wiki/C0_and_C1_control_codes#Field_separators
|
|
74
|
-
const SEP = {
|
|
75
|
-
record: "\x1e",
|
|
76
|
-
field: "\x1f",
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
const FORMAT = `%H${SEP.field}%B${SEP.record}`;
|
|
80
|
-
|
|
81
|
-
const RE = {
|
|
82
|
-
git_sha: /^[0-9a-fA-F]{40}$/,
|
|
83
|
-
};
|