git-stack-cli 2.7.1 → 2.7.3
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 +118 -101
- package/package.json +1 -1
- package/scripts/bun-build.ts +2 -1
- package/scripts/core/get_local_iso.ts +20 -0
- package/src/app/AutoUpdate.tsx +3 -12
- package/src/app/SelectCommitRanges.tsx +14 -16
- package/src/command.ts +13 -8
- package/src/commands/Rebase.tsx +4 -0
- package/src/core/CommitMetadata.ts +8 -60
- package/src/core/Metadata.test.ts +3 -0
- package/src/core/Metadata.ts +11 -1
- package/src/core/__snapshots__/git.test.ts.snap +95 -0
- package/src/core/get_timeout_fn.ts +11 -0
- package/src/core/git.test.ts +54 -0
- package/src/core/git.ts +55 -0
- package/src/core/github.tsx +6 -0
package/dist/js/index.js
CHANGED
|
@@ -37414,6 +37414,17 @@ async function fetch_json(url) {
|
|
|
37414
37414
|
});
|
|
37415
37415
|
}
|
|
37416
37416
|
|
|
37417
|
+
// src/core/get_timeout_fn.ts
|
|
37418
|
+
function get_timeout_fn(ms, message) {
|
|
37419
|
+
return function timeout(promise) {
|
|
37420
|
+
let id;
|
|
37421
|
+
const timeout = new Promise((_resolve, reject) => {
|
|
37422
|
+
id = setTimeout(() => reject(new Error(message)), ms);
|
|
37423
|
+
});
|
|
37424
|
+
return Promise.race([promise, timeout]).finally(() => clearTimeout(id));
|
|
37425
|
+
};
|
|
37426
|
+
}
|
|
37427
|
+
|
|
37417
37428
|
// src/core/is_finite_value.ts
|
|
37418
37429
|
function is_finite_value(value) {
|
|
37419
37430
|
return typeof value === "number" && Number.isFinite(value);
|
|
@@ -37522,19 +37533,15 @@ function AutoUpdate(props) {
|
|
|
37522
37533
|
async function init_state() {
|
|
37523
37534
|
if (state.latest_version !== null)
|
|
37524
37535
|
return;
|
|
37525
|
-
const local_version = "2.7.
|
|
37536
|
+
const local_version = "2.7.3";
|
|
37526
37537
|
const latest_version = await get_latest_version();
|
|
37527
37538
|
const is_brew_bun_standalone = get_is_brew_bun_standalone();
|
|
37528
37539
|
patch({ local_version, latest_version, is_brew_bun_standalone });
|
|
37529
37540
|
}
|
|
37530
37541
|
async function get_latest_version() {
|
|
37531
37542
|
const timeout_ms = is_finite_value(props.timeoutMs) ? props.timeoutMs : 2 * 1000;
|
|
37532
|
-
const
|
|
37533
|
-
|
|
37534
|
-
sleep(timeout_ms).then(() => {
|
|
37535
|
-
abort(new Error("AutoUpdate timeout"));
|
|
37536
|
-
})
|
|
37537
|
-
]);
|
|
37543
|
+
const timeout = get_timeout_fn(timeout_ms, "AutoUpdate timeout");
|
|
37544
|
+
const npm_json = await timeout(fetch_json(`https://registry.npmjs.org/${props.name}`));
|
|
37538
37545
|
const maybe_version = npm_json?.["dist-tags"]?.latest;
|
|
37539
37546
|
if (typeof maybe_version === "string") {
|
|
37540
37547
|
return maybe_version;
|
|
@@ -38078,7 +38085,12 @@ function write(message, values) {
|
|
|
38078
38085
|
return new_message;
|
|
38079
38086
|
}
|
|
38080
38087
|
function read(message) {
|
|
38081
|
-
const values = { id: null, title: null };
|
|
38088
|
+
const values = { subject: null, id: null, title: null };
|
|
38089
|
+
const match_subject = message.match(RE2.subject_line);
|
|
38090
|
+
if (match_subject?.groups) {
|
|
38091
|
+
values.subject = match_subject.groups["subject"];
|
|
38092
|
+
invariant(values.subject, "subject must exist");
|
|
38093
|
+
}
|
|
38082
38094
|
const match_id = message.match(RE2.stack_id);
|
|
38083
38095
|
if (match_id?.groups) {
|
|
38084
38096
|
values.id = match_id.groups["id"];
|
|
@@ -38106,10 +38118,46 @@ var TEMPLATE = {
|
|
|
38106
38118
|
}
|
|
38107
38119
|
};
|
|
38108
38120
|
var RE2 = {
|
|
38121
|
+
subject_line: /^(?<subject>[^\n]*)/,
|
|
38109
38122
|
stack_id: new RegExp(`${TEMPLATE.stack_id("(?<id>[^\\s]+)")}`, "i"),
|
|
38110
38123
|
group_title: new RegExp(TEMPLATE.group_title("(?<title>[^\\n^\\r]+)"), "i")
|
|
38111
38124
|
};
|
|
38112
38125
|
|
|
38126
|
+
// src/core/git.ts
|
|
38127
|
+
async function get_commits(dot_range) {
|
|
38128
|
+
const log_result = await cli(`git log ${dot_range} --format=${FORMAT} --color=never`);
|
|
38129
|
+
if (!log_result.stdout) {
|
|
38130
|
+
return [];
|
|
38131
|
+
}
|
|
38132
|
+
const commit_list = [];
|
|
38133
|
+
for (let record of log_result.stdout.split(SEP2.record)) {
|
|
38134
|
+
record = record.replace(/^\n/, "");
|
|
38135
|
+
record = record.replace(/\n$/, "");
|
|
38136
|
+
if (!record)
|
|
38137
|
+
continue;
|
|
38138
|
+
const [sha, full_message] = record.split(SEP2.field);
|
|
38139
|
+
const metadata = read(full_message);
|
|
38140
|
+
const branch_id = metadata.id;
|
|
38141
|
+
const subject_line = metadata.subject || "";
|
|
38142
|
+
const title = metadata.title;
|
|
38143
|
+
const commit = {
|
|
38144
|
+
sha,
|
|
38145
|
+
full_message,
|
|
38146
|
+
subject_line,
|
|
38147
|
+
branch_id,
|
|
38148
|
+
title
|
|
38149
|
+
};
|
|
38150
|
+
commit_list.push(commit);
|
|
38151
|
+
}
|
|
38152
|
+
commit_list.reverse();
|
|
38153
|
+
return commit_list;
|
|
38154
|
+
}
|
|
38155
|
+
var SEP2 = {
|
|
38156
|
+
record: "\x1E",
|
|
38157
|
+
field: "\x1F"
|
|
38158
|
+
};
|
|
38159
|
+
var FORMAT = `%H${SEP2.field}%B${SEP2.record}`;
|
|
38160
|
+
|
|
38113
38161
|
// src/core/github.tsx
|
|
38114
38162
|
var React24 = __toESM(require_react(), 1);
|
|
38115
38163
|
import crypto from "node:crypto";
|
|
@@ -38144,6 +38192,8 @@ var RE3 = {
|
|
|
38144
38192
|
async function pr_list() {
|
|
38145
38193
|
const state = Store.getState();
|
|
38146
38194
|
const actions = state.actions;
|
|
38195
|
+
const timer = Timer();
|
|
38196
|
+
actions.debug("start github.pr_list");
|
|
38147
38197
|
const username = state.username;
|
|
38148
38198
|
const repo_path = state.repo_path;
|
|
38149
38199
|
invariant(username, "username must exist");
|
|
@@ -38165,6 +38215,8 @@ async function pr_list() {
|
|
|
38165
38215
|
state2.pr[pr.headRefName] = pr;
|
|
38166
38216
|
}
|
|
38167
38217
|
});
|
|
38218
|
+
const duration = timer.duration();
|
|
38219
|
+
actions.debug(`end github.pr_list (duration=${duration})`);
|
|
38168
38220
|
return result_pr_list;
|
|
38169
38221
|
}
|
|
38170
38222
|
async function pr_status(branch) {
|
|
@@ -38287,11 +38339,12 @@ var RE4 = {
|
|
|
38287
38339
|
|
|
38288
38340
|
// src/core/CommitMetadata.ts
|
|
38289
38341
|
async function range(commit_group_map) {
|
|
38290
|
-
const master_branch = Store.getState().master_branch;
|
|
38291
38342
|
await pr_list();
|
|
38292
|
-
const
|
|
38343
|
+
const master_branch = Store.getState().master_branch;
|
|
38344
|
+
const commit_list = await get_commits(`${master_branch}..HEAD`);
|
|
38293
38345
|
const pr_lookup = {};
|
|
38294
38346
|
let invalid = false;
|
|
38347
|
+
let last_group_id = null;
|
|
38295
38348
|
const group_map = new Map;
|
|
38296
38349
|
for (const commit of commit_list) {
|
|
38297
38350
|
let id = commit.branch_id;
|
|
@@ -38307,9 +38360,7 @@ async function range(commit_group_map) {
|
|
|
38307
38360
|
invalid = true;
|
|
38308
38361
|
}
|
|
38309
38362
|
if (id) {
|
|
38310
|
-
|
|
38311
|
-
const last_key = group_key_list[group_key_list.length - 1];
|
|
38312
|
-
if (group_map.has(id) && last_key !== id) {
|
|
38363
|
+
if (group_map.has(id) && last_group_id !== id) {
|
|
38313
38364
|
invalid = true;
|
|
38314
38365
|
}
|
|
38315
38366
|
} else {
|
|
@@ -38329,6 +38380,7 @@ async function range(commit_group_map) {
|
|
|
38329
38380
|
};
|
|
38330
38381
|
group.commits.push(commit);
|
|
38331
38382
|
group_map.set(id, group);
|
|
38383
|
+
last_group_id = id;
|
|
38332
38384
|
}
|
|
38333
38385
|
const group_value_list = Array.from(group_map.values());
|
|
38334
38386
|
const group_list = [];
|
|
@@ -38394,44 +38446,6 @@ async function range(commit_group_map) {
|
|
|
38394
38446
|
}
|
|
38395
38447
|
return { invalid, group_list, commit_list, pr_lookup, UNASSIGNED };
|
|
38396
38448
|
}
|
|
38397
|
-
async function get_commit_list() {
|
|
38398
|
-
const master_branch = Store.getState().master_branch;
|
|
38399
|
-
const log_result = await cli(`git log ${master_branch}..HEAD --oneline --format=%H --color=never`);
|
|
38400
|
-
if (!log_result.stdout) {
|
|
38401
|
-
return [];
|
|
38402
|
-
}
|
|
38403
|
-
const sha_list = lines(log_result.stdout).reverse();
|
|
38404
|
-
const commit_metadata_list = [];
|
|
38405
|
-
for (let i2 = 0;i2 < sha_list.length; i2++) {
|
|
38406
|
-
const sha = sha_list[i2];
|
|
38407
|
-
const commit_metadata = await commit(sha);
|
|
38408
|
-
commit_metadata_list.push(commit_metadata);
|
|
38409
|
-
}
|
|
38410
|
-
return commit_metadata_list;
|
|
38411
|
-
}
|
|
38412
|
-
async function commit(sha) {
|
|
38413
|
-
const full_message = (await cli(`git show -s --format=%B ${sha}`)).stdout;
|
|
38414
|
-
const metadata = await read(full_message);
|
|
38415
|
-
const branch_id = metadata?.id;
|
|
38416
|
-
const subject_line = get_subject_line(full_message);
|
|
38417
|
-
const title = metadata?.title;
|
|
38418
|
-
return {
|
|
38419
|
-
sha,
|
|
38420
|
-
full_message,
|
|
38421
|
-
subject_line,
|
|
38422
|
-
branch_id,
|
|
38423
|
-
title
|
|
38424
|
-
};
|
|
38425
|
-
}
|
|
38426
|
-
function get_subject_line(message) {
|
|
38427
|
-
const line_list = lines(message);
|
|
38428
|
-
const first_line = line_list[0];
|
|
38429
|
-
return remove(first_line);
|
|
38430
|
-
}
|
|
38431
|
-
function lines(value) {
|
|
38432
|
-
return value.split(`
|
|
38433
|
-
`);
|
|
38434
|
-
}
|
|
38435
38449
|
var UNASSIGNED = "unassigned";
|
|
38436
38450
|
|
|
38437
38451
|
// src/core/GitReviseTodo.ts
|
|
@@ -38442,8 +38456,8 @@ function GitReviseTodo(args) {
|
|
|
38442
38456
|
const group_list = args.commit_range.group_list;
|
|
38443
38457
|
for (let i2 = args.rebase_group_index;i2 < group_list.length; i2++) {
|
|
38444
38458
|
const group = group_list[i2];
|
|
38445
|
-
for (const
|
|
38446
|
-
commit_list.push(
|
|
38459
|
+
for (const commit of group.commits) {
|
|
38460
|
+
commit_list.push(commit);
|
|
38447
38461
|
}
|
|
38448
38462
|
}
|
|
38449
38463
|
const todo = GitReviseTodo.todo({ commit_list });
|
|
@@ -38451,17 +38465,17 @@ function GitReviseTodo(args) {
|
|
|
38451
38465
|
}
|
|
38452
38466
|
GitReviseTodo.todo = function todo(args) {
|
|
38453
38467
|
const entry_list = [];
|
|
38454
|
-
for (const
|
|
38455
|
-
const sha =
|
|
38468
|
+
for (const commit of args.commit_list) {
|
|
38469
|
+
const sha = commit.sha.slice(0, 12);
|
|
38456
38470
|
const entry_lines = [`++ pick ${sha}`];
|
|
38457
|
-
const id =
|
|
38471
|
+
const id = commit.branch_id;
|
|
38458
38472
|
if (id == null || id === UNASSIGNED) {
|
|
38459
|
-
entry_lines.push(
|
|
38473
|
+
entry_lines.push(commit.full_message);
|
|
38460
38474
|
} else {
|
|
38461
|
-
const title =
|
|
38475
|
+
const title = commit.title;
|
|
38462
38476
|
invariant(title, "commit.title must exist");
|
|
38463
38477
|
const metadata = { id, title };
|
|
38464
|
-
const unsafe_message_with_id = write(
|
|
38478
|
+
const unsafe_message_with_id = write(commit.full_message, metadata);
|
|
38465
38479
|
const message_with_id = unsafe_message_with_id;
|
|
38466
38480
|
entry_lines.push(message_with_id);
|
|
38467
38481
|
}
|
|
@@ -38629,8 +38643,8 @@ function DetectInitialPR(props) {
|
|
|
38629
38643
|
invariant(commit_range, "commit_range must exist");
|
|
38630
38644
|
try {
|
|
38631
38645
|
let has_existing_metadata = false;
|
|
38632
|
-
for (const
|
|
38633
|
-
if (
|
|
38646
|
+
for (const commit of commit_range.commit_list) {
|
|
38647
|
+
if (commit.branch_id) {
|
|
38634
38648
|
has_existing_metadata = true;
|
|
38635
38649
|
break;
|
|
38636
38650
|
}
|
|
@@ -38659,9 +38673,9 @@ function DetectInitialPR(props) {
|
|
|
38659
38673
|
for (const group of commit_range.group_list) {
|
|
38660
38674
|
group.id = branch_name2;
|
|
38661
38675
|
group.title = state.pr?.title || "-";
|
|
38662
|
-
for (const
|
|
38663
|
-
|
|
38664
|
-
|
|
38676
|
+
for (const commit of commit_range.commit_list) {
|
|
38677
|
+
commit.branch_id = group.id;
|
|
38678
|
+
commit.title = group.title;
|
|
38665
38679
|
}
|
|
38666
38680
|
}
|
|
38667
38681
|
const rebase_group_index = 0;
|
|
@@ -39290,7 +39304,9 @@ Rebase.run = async function run5(props) {
|
|
|
39290
39304
|
}
|
|
39291
39305
|
actions.exit(20);
|
|
39292
39306
|
}
|
|
39307
|
+
actions.debug("start CommitMetadata.range");
|
|
39293
39308
|
const next_commit_range = await range();
|
|
39309
|
+
actions.debug("end CommitMetadata.range");
|
|
39294
39310
|
actions.output(/* @__PURE__ */ React34.createElement(FormatText, {
|
|
39295
39311
|
wrapper: /* @__PURE__ */ React34.createElement(Text, {
|
|
39296
39312
|
color: colors.green
|
|
@@ -39320,12 +39336,12 @@ Rebase.run = async function run5(props) {
|
|
|
39320
39336
|
await cli(`git checkout -b ${temp_branch_name} ${rebase_merge_base}`);
|
|
39321
39337
|
const picked_commit_list = [];
|
|
39322
39338
|
for (let i2 = 0;i2 < commit_range.commit_list.length; i2++) {
|
|
39323
|
-
const
|
|
39324
|
-
const commit_pr = commit_range.pr_lookup[
|
|
39339
|
+
const commit = commit_range.commit_list[i2];
|
|
39340
|
+
const commit_pr = commit_range.pr_lookup[commit.branch_id || ""];
|
|
39325
39341
|
const merged_pr = commit_pr?.state === "MERGED";
|
|
39326
39342
|
const commit_message = /* @__PURE__ */ React34.createElement(Text, {
|
|
39327
39343
|
color: colors.blue
|
|
39328
|
-
},
|
|
39344
|
+
}, commit.subject_line);
|
|
39329
39345
|
if (merged_pr) {
|
|
39330
39346
|
actions.output(/* @__PURE__ */ React34.createElement(FormatText, {
|
|
39331
39347
|
wrapper: /* @__PURE__ */ React34.createElement(Text, {
|
|
@@ -39352,15 +39368,17 @@ Rebase.run = async function run5(props) {
|
|
|
39352
39368
|
commit_message
|
|
39353
39369
|
}
|
|
39354
39370
|
}));
|
|
39355
|
-
picked_commit_list.push(
|
|
39371
|
+
picked_commit_list.push(commit);
|
|
39356
39372
|
}
|
|
39357
39373
|
if (picked_commit_list.length > 0) {
|
|
39358
39374
|
await cli(`git clean -fd`);
|
|
39359
|
-
const sha_list = picked_commit_list.map((
|
|
39375
|
+
const sha_list = picked_commit_list.map((commit) => commit.sha).join(" ");
|
|
39360
39376
|
await cli(`git cherry-pick --keep-redundant-commits ${sha_list}`);
|
|
39361
39377
|
}
|
|
39362
39378
|
await cli(`git branch -f ${branch_name} ${temp_branch_name}`);
|
|
39379
|
+
actions.debug("start restore_git()");
|
|
39363
39380
|
restore_git();
|
|
39381
|
+
actions.debug("end restore_git()");
|
|
39364
39382
|
}
|
|
39365
39383
|
function restore_git() {
|
|
39366
39384
|
const spawn_options = { ignoreExitCode: true };
|
|
@@ -39431,10 +39449,10 @@ async function run6() {
|
|
|
39431
39449
|
const merge_base = (await cli(`git merge-base HEAD ${master_branch}`)).stdout;
|
|
39432
39450
|
let commit_range = await range(commit_map);
|
|
39433
39451
|
commit_range.group_list.reverse();
|
|
39434
|
-
for (const
|
|
39435
|
-
const group_from_map = commit_map[
|
|
39436
|
-
|
|
39437
|
-
|
|
39452
|
+
for (const commit of commit_range.commit_list) {
|
|
39453
|
+
const group_from_map = commit_map[commit.sha];
|
|
39454
|
+
commit.branch_id = group_from_map.id;
|
|
39455
|
+
commit.title = group_from_map.title;
|
|
39438
39456
|
}
|
|
39439
39457
|
await GitReviseTodo.execute({
|
|
39440
39458
|
rebase_group_index: 0,
|
|
@@ -39898,8 +39916,8 @@ function SelectCommitRangesInternal(props) {
|
|
|
39898
39916
|
map.set(args.key, args.value);
|
|
39899
39917
|
return new Map(map);
|
|
39900
39918
|
}, new Map, (map) => {
|
|
39901
|
-
for (const
|
|
39902
|
-
map.set(
|
|
39919
|
+
for (const commit of props.commit_range.commit_list) {
|
|
39920
|
+
map.set(commit.sha, commit.branch_id);
|
|
39903
39921
|
}
|
|
39904
39922
|
return new Map(map);
|
|
39905
39923
|
});
|
|
@@ -39989,8 +40007,8 @@ function SelectCommitRangesInternal(props) {
|
|
|
39989
40007
|
const max_width = 80;
|
|
39990
40008
|
const [focused, set_focused] = React42.useState("");
|
|
39991
40009
|
const has_groups = group.id !== props.commit_range.UNASSIGNED;
|
|
39992
|
-
const items = props.commit_range.commit_list.map((
|
|
39993
|
-
const commit_metadata_id = commit_map.get(
|
|
40010
|
+
const items = props.commit_range.commit_list.map((commit) => {
|
|
40011
|
+
const commit_metadata_id = commit_map.get(commit.sha);
|
|
39994
40012
|
const selected = commit_metadata_id !== null;
|
|
39995
40013
|
let disabled;
|
|
39996
40014
|
if (group_input) {
|
|
@@ -40001,8 +40019,8 @@ function SelectCommitRangesInternal(props) {
|
|
|
40001
40019
|
disabled = Boolean(selected && commit_metadata_id !== group.id);
|
|
40002
40020
|
}
|
|
40003
40021
|
return {
|
|
40004
|
-
label:
|
|
40005
|
-
value:
|
|
40022
|
+
label: commit.subject_line,
|
|
40023
|
+
value: commit,
|
|
40006
40024
|
selected,
|
|
40007
40025
|
disabled
|
|
40008
40026
|
};
|
|
@@ -40133,7 +40151,7 @@ function SelectCommitRangesInternal(props) {
|
|
|
40133
40151
|
wrapper: /* @__PURE__ */ React42.createElement(Text, {
|
|
40134
40152
|
color: colors.gray
|
|
40135
40153
|
}),
|
|
40136
|
-
message: "Press {s} to {sync} the {count} assigned commits to Github",
|
|
40154
|
+
message: argv.sync ? "Press {s} to {sync} the {count} assigned commits to Github" : "Press {s} to {sync} the {count} assigned commits locally",
|
|
40137
40155
|
values: {
|
|
40138
40156
|
...S_TO_SYNC_VALUES,
|
|
40139
40157
|
count: /* @__PURE__ */ React42.createElement(Text, {
|
|
@@ -40141,15 +40159,11 @@ function SelectCommitRangesInternal(props) {
|
|
|
40141
40159
|
bold: true
|
|
40142
40160
|
}, assigned_count)
|
|
40143
40161
|
}
|
|
40144
|
-
})) : /* @__PURE__ */ React42.createElement(
|
|
40145
|
-
wrapper: /* @__PURE__ */ React42.createElement(Text, null),
|
|
40146
|
-
message: "\uD83C\uDF89 Done! Press {s} to {sync} the commits to Github",
|
|
40147
|
-
values: S_TO_SYNC_VALUES
|
|
40148
|
-
}) : /* @__PURE__ */ React42.createElement(FormatText, {
|
|
40162
|
+
})) : /* @__PURE__ */ React42.createElement(FormatText, {
|
|
40149
40163
|
wrapper: /* @__PURE__ */ React42.createElement(Text, null),
|
|
40150
|
-
message: "\uD83C\uDF89 Done! Press {s} to {
|
|
40164
|
+
message: argv.sync ? "\uD83C\uDF89 Done! Press {s} to {sync} the PRs to Github" : "\uD83C\uDF89 Done! Press {s} to {sync} the PRs locally",
|
|
40151
40165
|
values: S_TO_SYNC_VALUES
|
|
40152
|
-
})
|
|
40166
|
+
}), /* @__PURE__ */ React42.createElement(Box_default, null, /* @__PURE__ */ React42.createElement(FormatText, {
|
|
40153
40167
|
wrapper: /* @__PURE__ */ React42.createElement(Text, {
|
|
40154
40168
|
color: colors.gray
|
|
40155
40169
|
}),
|
|
@@ -40204,8 +40218,8 @@ function SelectCommitRangesInternal(props) {
|
|
|
40204
40218
|
}
|
|
40205
40219
|
let allow_unassigned_sync = null;
|
|
40206
40220
|
for (let i2 = 0;i2 < props.commit_range.commit_list.length; i2++) {
|
|
40207
|
-
const
|
|
40208
|
-
const group_id = commit_map.get(
|
|
40221
|
+
const commit = props.commit_range.commit_list[i2];
|
|
40222
|
+
const group_id = commit_map.get(commit.sha);
|
|
40209
40223
|
if (allow_unassigned_sync === null) {
|
|
40210
40224
|
if (group_id === null) {
|
|
40211
40225
|
allow_unassigned_sync = true;
|
|
@@ -40770,14 +40784,14 @@ class UI {
|
|
|
40770
40784
|
return [0, noAnsi.match(/\s*$/)[0].length, 0, noAnsi.match(/^\s*/)[0].length];
|
|
40771
40785
|
}
|
|
40772
40786
|
toString() {
|
|
40773
|
-
const
|
|
40787
|
+
const lines = [];
|
|
40774
40788
|
this.rows.forEach((row) => {
|
|
40775
|
-
this.rowToString(row,
|
|
40789
|
+
this.rowToString(row, lines);
|
|
40776
40790
|
});
|
|
40777
|
-
return
|
|
40791
|
+
return lines.filter((line) => !line.hidden).map((line) => line.text).join(`
|
|
40778
40792
|
`);
|
|
40779
40793
|
}
|
|
40780
|
-
rowToString(row,
|
|
40794
|
+
rowToString(row, lines) {
|
|
40781
40795
|
this.rasterize(row).forEach((rrow, r3) => {
|
|
40782
40796
|
let str = "";
|
|
40783
40797
|
rrow.forEach((col, c2) => {
|
|
@@ -40804,16 +40818,16 @@ class UI {
|
|
|
40804
40818
|
if (padding[right]) {
|
|
40805
40819
|
str += " ".repeat(padding[right]);
|
|
40806
40820
|
}
|
|
40807
|
-
if (r3 === 0 &&
|
|
40808
|
-
str = this.renderInline(str,
|
|
40821
|
+
if (r3 === 0 && lines.length > 0) {
|
|
40822
|
+
str = this.renderInline(str, lines[lines.length - 1]);
|
|
40809
40823
|
}
|
|
40810
40824
|
});
|
|
40811
|
-
|
|
40825
|
+
lines.push({
|
|
40812
40826
|
text: str.replace(/ +$/, ""),
|
|
40813
40827
|
span: row.span
|
|
40814
40828
|
});
|
|
40815
40829
|
});
|
|
40816
|
-
return
|
|
40830
|
+
return lines;
|
|
40817
40831
|
}
|
|
40818
40832
|
renderInline(source, previousLine) {
|
|
40819
40833
|
const match2 = source.match(/^ */);
|
|
@@ -45559,10 +45573,13 @@ async function command2(argv, options = {}) {
|
|
|
45559
45573
|
if (options.parserConfiguration) {
|
|
45560
45574
|
builder = builder.parserConfiguration(options.parserConfiguration);
|
|
45561
45575
|
}
|
|
45562
|
-
|
|
45563
|
-
|
|
45564
|
-
|
|
45565
|
-
|
|
45576
|
+
const parsed = await builder.scriptName("git stack").usage("Usage: git stack [command] [options]").command("$0", "Sync commit ranges to Github", (yargs) => {
|
|
45577
|
+
let builder2 = yargs.options(DefaultOptions);
|
|
45578
|
+
if (options.env_config) {
|
|
45579
|
+
builder2 = builder2.config(options.env_config);
|
|
45580
|
+
}
|
|
45581
|
+
return builder2;
|
|
45582
|
+
}).command("fixup [commit]", "Amend staged changes to a specific commit in history", (yargs) => yargs.positional("commit", FixupOptions.commit)).command("log [args...]", "Print an abbreviated log with numbered commits, useful for git stack fixup", (yargs) => yargs.strict(false)).command("rebase", "Update local branch via rebase with latest changes from origin master branch", (yargs) => yargs).command(["update", "upgrade"], "Check and install the latest version of git stack", (yargs) => yargs).command("config", "Generate a one-time configuration json based on the passed arguments", (yargs) => yargs.options(DefaultOptions)).option("verbose", GlobalOptions.verbose).wrap(123).strict().version("2.7.3").showHidden("show-hidden", "Show hidden options via `git stack help --show-hidden`").help("help", "Show usage via `git stack help`");
|
|
45566
45583
|
const result = parsed.argv;
|
|
45567
45584
|
return result;
|
|
45568
45585
|
}
|
package/package.json
CHANGED
package/scripts/bun-build.ts
CHANGED
|
@@ -3,6 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import * as util from "util";
|
|
4
4
|
|
|
5
5
|
import * as file from "~/core/file";
|
|
6
|
+
import { get_local_iso } from "~/core/get_local_iso";
|
|
6
7
|
import { spawn } from "~/core/spawn";
|
|
7
8
|
|
|
8
9
|
const parsed_args = util.parseArgs({
|
|
@@ -25,7 +26,7 @@ const WATCH = parsed_args.values.watch;
|
|
|
25
26
|
const VERBOSE = parsed_args.values.verbose;
|
|
26
27
|
|
|
27
28
|
function log(...args: any[]) {
|
|
28
|
-
const timestamp = new Date()
|
|
29
|
+
const timestamp = get_local_iso(new Date());
|
|
29
30
|
console.debug(`[${timestamp}]`, ...args);
|
|
30
31
|
}
|
|
31
32
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function get_local_iso(date: Date) {
|
|
2
|
+
const d: Record<string, string> = {};
|
|
3
|
+
for (const part of FORMATTER.formatToParts(date)) {
|
|
4
|
+
d[part.type] = part.value;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const ms = String(date.getMilliseconds()).padStart(3, "0");
|
|
8
|
+
const timestamp = `${d.year}-${d.month}-${d.day}T${d.hour}:${d.minute}:${d.second}.${ms}Z`;
|
|
9
|
+
return timestamp;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const FORMATTER = new Intl.DateTimeFormat("en-CA", {
|
|
13
|
+
year: "numeric",
|
|
14
|
+
month: "2-digit",
|
|
15
|
+
day: "2-digit",
|
|
16
|
+
hour: "2-digit",
|
|
17
|
+
minute: "2-digit",
|
|
18
|
+
second: "2-digit",
|
|
19
|
+
hour12: false,
|
|
20
|
+
});
|
package/src/app/AutoUpdate.tsx
CHANGED
|
@@ -11,9 +11,9 @@ import { assertNever } from "~/core/assertNever";
|
|
|
11
11
|
import { cli } from "~/core/cli";
|
|
12
12
|
import { colors } from "~/core/colors";
|
|
13
13
|
import { fetch_json } from "~/core/fetch_json";
|
|
14
|
+
import { get_timeout_fn } from "~/core/get_timeout_fn";
|
|
14
15
|
import { is_finite_value } from "~/core/is_finite_value";
|
|
15
16
|
import { semver_compare } from "~/core/semver_compare";
|
|
16
|
-
import { sleep } from "~/core/sleep";
|
|
17
17
|
|
|
18
18
|
type Props = {
|
|
19
19
|
name: string;
|
|
@@ -162,21 +162,12 @@ export function AutoUpdate(props: Props) {
|
|
|
162
162
|
|
|
163
163
|
async function get_latest_version() {
|
|
164
164
|
const timeout_ms = is_finite_value(props.timeoutMs) ? props.timeoutMs : 2 * 1000;
|
|
165
|
-
|
|
166
|
-
const npm_json = await
|
|
167
|
-
fetch_json(`https://registry.npmjs.org/${props.name}`),
|
|
168
|
-
|
|
169
|
-
sleep(timeout_ms).then(() => {
|
|
170
|
-
abort(new Error("AutoUpdate timeout"));
|
|
171
|
-
}),
|
|
172
|
-
]);
|
|
173
|
-
|
|
165
|
+
const timeout = get_timeout_fn(timeout_ms, "AutoUpdate timeout");
|
|
166
|
+
const npm_json = await timeout(fetch_json(`https://registry.npmjs.org/${props.name}`));
|
|
174
167
|
const maybe_version = npm_json?.["dist-tags"]?.latest;
|
|
175
|
-
|
|
176
168
|
if (typeof maybe_version === "string") {
|
|
177
169
|
return maybe_version;
|
|
178
170
|
}
|
|
179
|
-
|
|
180
171
|
throw new Error("Unable to retrieve latest version from npm");
|
|
181
172
|
}
|
|
182
173
|
|
|
@@ -380,7 +380,11 @@ function SelectCommitRangesInternal(props: Props) {
|
|
|
380
380
|
{sync_status !== "allow_unassigned" ? null : (
|
|
381
381
|
<FormatText
|
|
382
382
|
wrapper={<Ink.Text color={colors.gray} />}
|
|
383
|
-
message=
|
|
383
|
+
message={
|
|
384
|
+
argv.sync
|
|
385
|
+
? "Press {s} to {sync} the {count} assigned commits to Github"
|
|
386
|
+
: "Press {s} to {sync} the {count} assigned commits locally"
|
|
387
|
+
}
|
|
384
388
|
values={{
|
|
385
389
|
...S_TO_SYNC_VALUES,
|
|
386
390
|
count: (
|
|
@@ -393,21 +397,15 @@ function SelectCommitRangesInternal(props: Props) {
|
|
|
393
397
|
)}
|
|
394
398
|
</React.Fragment>
|
|
395
399
|
) : (
|
|
396
|
-
<
|
|
397
|
-
{
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
wrapper={<Ink.Text />}
|
|
406
|
-
message="🎉 Done! Press {s} to {save} the commits locally"
|
|
407
|
-
values={S_TO_SYNC_VALUES}
|
|
408
|
-
/>
|
|
409
|
-
)}
|
|
410
|
-
</React.Fragment>
|
|
400
|
+
<FormatText
|
|
401
|
+
wrapper={<Ink.Text />}
|
|
402
|
+
message={
|
|
403
|
+
argv.sync
|
|
404
|
+
? "🎉 Done! Press {s} to {sync} the PRs to Github"
|
|
405
|
+
: "🎉 Done! Press {s} to {sync} the PRs locally"
|
|
406
|
+
}
|
|
407
|
+
values={S_TO_SYNC_VALUES}
|
|
408
|
+
/>
|
|
411
409
|
)}
|
|
412
410
|
|
|
413
411
|
<Ink.Box>
|
package/src/command.ts
CHANGED
|
@@ -18,18 +18,22 @@ export async function command(argv: string[], options: CommandOptions = {}) {
|
|
|
18
18
|
builder = builder.parserConfiguration(options.parserConfiguration);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
// apply overrides from config
|
|
22
|
-
// higher precedence than defaults, but lower precendence than cli flags
|
|
23
|
-
// perfect since that's what we want, prefer config only if not explicitly set on cli
|
|
24
|
-
if (options.env_config) {
|
|
25
|
-
builder = builder.config(options.env_config);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
21
|
const parsed = await builder
|
|
29
22
|
.scriptName("git stack")
|
|
30
23
|
.usage("Usage: git stack [command] [options]")
|
|
31
24
|
|
|
32
|
-
.command("$0", "Sync commit ranges to Github", (yargs) =>
|
|
25
|
+
.command("$0", "Sync commit ranges to Github", (yargs) => {
|
|
26
|
+
let builder = yargs.options(DefaultOptions);
|
|
27
|
+
|
|
28
|
+
// apply overrides from config
|
|
29
|
+
// higher precedence than defaults, but lower precendence than cli flags
|
|
30
|
+
// perfect since that's what we want, prefer config only if not explicitly set on cli
|
|
31
|
+
if (options.env_config) {
|
|
32
|
+
builder = builder.config(options.env_config);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return builder;
|
|
36
|
+
})
|
|
33
37
|
|
|
34
38
|
.command("fixup [commit]", "Amend staged changes to a specific commit in history", (yargs) =>
|
|
35
39
|
yargs.positional("commit", FixupOptions.commit),
|
|
@@ -52,6 +56,7 @@ export async function command(argv: string[], options: CommandOptions = {}) {
|
|
|
52
56
|
"Check and install the latest version of git stack",
|
|
53
57
|
(yargs) => yargs,
|
|
54
58
|
)
|
|
59
|
+
|
|
55
60
|
.command(
|
|
56
61
|
"config",
|
|
57
62
|
"Generate a one-time configuration json based on the passed arguments",
|
package/src/commands/Rebase.tsx
CHANGED
|
@@ -78,7 +78,9 @@ Rebase.run = async function run(props: Props) {
|
|
|
78
78
|
actions.exit(20);
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
actions.debug("start CommitMetadata.range");
|
|
81
82
|
const next_commit_range = await CommitMetadata.range();
|
|
83
|
+
actions.debug("end CommitMetadata.range");
|
|
82
84
|
|
|
83
85
|
actions.output(
|
|
84
86
|
<FormatText
|
|
@@ -167,7 +169,9 @@ Rebase.run = async function run(props: Props) {
|
|
|
167
169
|
// of original branch to the newly created temporary branch
|
|
168
170
|
await cli(`git branch -f ${branch_name} ${temp_branch_name}`);
|
|
169
171
|
|
|
172
|
+
actions.debug("start restore_git()");
|
|
170
173
|
restore_git();
|
|
174
|
+
actions.debug("end restore_git()");
|
|
171
175
|
}
|
|
172
176
|
|
|
173
177
|
// cleanup git operations if cancelled during manual rebase
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { Store } from "~/app/Store";
|
|
2
|
-
import * as
|
|
3
|
-
import { cli } from "~/core/cli";
|
|
2
|
+
import * as git from "~/core/git";
|
|
4
3
|
import * as github from "~/core/github";
|
|
5
4
|
|
|
6
|
-
export type CommitMetadata = Awaited<ReturnType<typeof commit>>;
|
|
7
5
|
export type CommitRange = Awaited<ReturnType<typeof range>>;
|
|
8
6
|
|
|
9
7
|
type GithubPRStatus = ReturnType<typeof github.pr_status>;
|
|
@@ -15,24 +13,25 @@ type CommitGroup = {
|
|
|
15
13
|
pr: null | PullRequest;
|
|
16
14
|
base: null | string;
|
|
17
15
|
dirty: boolean;
|
|
18
|
-
commits: Array<
|
|
16
|
+
commits: Array<git.Commit>;
|
|
19
17
|
};
|
|
20
18
|
|
|
21
19
|
export type SimpleGroup = { id: string; title: string };
|
|
22
20
|
type CommitGroupMap = { [sha: string]: SimpleGroup };
|
|
23
21
|
|
|
24
22
|
export async function range(commit_group_map?: CommitGroupMap) {
|
|
25
|
-
const master_branch = Store.getState().master_branch;
|
|
26
|
-
|
|
27
23
|
// gather all open prs in repo first
|
|
28
24
|
// cheaper query to populate cache
|
|
29
25
|
await github.pr_list();
|
|
30
26
|
|
|
31
|
-
const
|
|
27
|
+
const master_branch = Store.getState().master_branch;
|
|
28
|
+
const commit_list = await git.get_commits(`${master_branch}..HEAD`);
|
|
32
29
|
|
|
33
30
|
const pr_lookup: Record<string, void | PullRequest> = {};
|
|
34
31
|
|
|
35
32
|
let invalid = false;
|
|
33
|
+
let last_group_id: null | string = null;
|
|
34
|
+
|
|
36
35
|
const group_map = new Map<string, CommitGroup>();
|
|
37
36
|
|
|
38
37
|
for (const commit of commit_list) {
|
|
@@ -57,10 +56,7 @@ export async function range(commit_group_map?: CommitGroupMap) {
|
|
|
57
56
|
}
|
|
58
57
|
|
|
59
58
|
if (id) {
|
|
60
|
-
|
|
61
|
-
const last_key = group_key_list[group_key_list.length - 1];
|
|
62
|
-
|
|
63
|
-
if (group_map.has(id) && last_key !== id) {
|
|
59
|
+
if (group_map.has(id) && last_group_id !== id) {
|
|
64
60
|
// if we've seen this id before and it's not
|
|
65
61
|
// the last added key then we are out of order
|
|
66
62
|
// console.debug("INVALID", "OUT OF ORDER");
|
|
@@ -87,6 +83,7 @@ export async function range(commit_group_map?: CommitGroupMap) {
|
|
|
87
83
|
|
|
88
84
|
group.commits.push(commit);
|
|
89
85
|
group_map.set(id, group);
|
|
86
|
+
last_group_id = id;
|
|
90
87
|
}
|
|
91
88
|
|
|
92
89
|
// check each group for dirty state and base
|
|
@@ -183,53 +180,4 @@ export async function range(commit_group_map?: CommitGroupMap) {
|
|
|
183
180
|
return { invalid, group_list, commit_list, pr_lookup, UNASSIGNED };
|
|
184
181
|
}
|
|
185
182
|
|
|
186
|
-
async function get_commit_list() {
|
|
187
|
-
const master_branch = Store.getState().master_branch;
|
|
188
|
-
const log_result = await cli(
|
|
189
|
-
`git log ${master_branch}..HEAD --oneline --format=%H --color=never`,
|
|
190
|
-
);
|
|
191
|
-
|
|
192
|
-
if (!log_result.stdout) {
|
|
193
|
-
return [];
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
const sha_list = lines(log_result.stdout).reverse();
|
|
197
|
-
|
|
198
|
-
const commit_metadata_list = [];
|
|
199
|
-
|
|
200
|
-
for (let i = 0; i < sha_list.length; i++) {
|
|
201
|
-
const sha = sha_list[i];
|
|
202
|
-
const commit_metadata = await commit(sha);
|
|
203
|
-
commit_metadata_list.push(commit_metadata);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return commit_metadata_list;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
export async function commit(sha: string) {
|
|
210
|
-
const full_message = (await cli(`git show -s --format=%B ${sha}`)).stdout;
|
|
211
|
-
const metadata = await Metadata.read(full_message);
|
|
212
|
-
const branch_id = metadata?.id;
|
|
213
|
-
const subject_line = get_subject_line(full_message);
|
|
214
|
-
const title = metadata?.title;
|
|
215
|
-
|
|
216
|
-
return {
|
|
217
|
-
sha,
|
|
218
|
-
full_message,
|
|
219
|
-
subject_line,
|
|
220
|
-
branch_id,
|
|
221
|
-
title,
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
function get_subject_line(message: string) {
|
|
226
|
-
const line_list = lines(message);
|
|
227
|
-
const first_line = line_list[0];
|
|
228
|
-
return Metadata.remove(first_line);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
function lines(value: string) {
|
|
232
|
-
return value.split("\n");
|
|
233
|
-
}
|
|
234
|
-
|
|
235
183
|
export const UNASSIGNED = "unassigned";
|
|
@@ -17,6 +17,7 @@ test("read handles bulleted lists", () => {
|
|
|
17
17
|
const metadata = Metadata.read(body);
|
|
18
18
|
|
|
19
19
|
expect(metadata).toEqual({
|
|
20
|
+
subject: "[feat] implement various features",
|
|
20
21
|
id: "DdKIFyufW",
|
|
21
22
|
title: "saved group title",
|
|
22
23
|
});
|
|
@@ -63,6 +64,7 @@ test("read handles slashes in branch name", () => {
|
|
|
63
64
|
const metadata = Metadata.read(body);
|
|
64
65
|
|
|
65
66
|
expect(metadata).toEqual({
|
|
67
|
+
subject: "[fix] slash in branch name",
|
|
66
68
|
id: "dev/noah/fix-slash-branch",
|
|
67
69
|
title: "fix slash branch",
|
|
68
70
|
});
|
|
@@ -109,6 +111,7 @@ test("read handles double quotes", () => {
|
|
|
109
111
|
const metadata = Metadata.read(body);
|
|
110
112
|
|
|
111
113
|
expect(metadata).toEqual({
|
|
114
|
+
subject: 'Revert "[abc / 123] subject (#1234)"',
|
|
112
115
|
id: "dev/noah/fix-slash-branch",
|
|
113
116
|
title: 'Revert \\"[abc / 123] subject (#1234)\\"',
|
|
114
117
|
});
|
package/src/core/Metadata.ts
CHANGED
|
@@ -6,6 +6,7 @@ type InputMetadataValues = {
|
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
type OutputMetadataValues = {
|
|
9
|
+
subject: null | string;
|
|
9
10
|
id: null | string;
|
|
10
11
|
title: null | string;
|
|
11
12
|
};
|
|
@@ -28,7 +29,14 @@ export function write(message: string, values: InputMetadataValues) {
|
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
export function read(message: string): OutputMetadataValues {
|
|
31
|
-
const values: OutputMetadataValues = { id: null, title: null };
|
|
32
|
+
const values: OutputMetadataValues = { subject: null, id: null, title: null };
|
|
33
|
+
|
|
34
|
+
const match_subject = message.match(RE.subject_line);
|
|
35
|
+
|
|
36
|
+
if (match_subject?.groups) {
|
|
37
|
+
values.subject = match_subject.groups["subject"];
|
|
38
|
+
invariant(values.subject, "subject must exist");
|
|
39
|
+
}
|
|
32
40
|
|
|
33
41
|
const match_id = message.match(RE.stack_id);
|
|
34
42
|
|
|
@@ -69,6 +77,8 @@ const TEMPLATE = {
|
|
|
69
77
|
};
|
|
70
78
|
|
|
71
79
|
const RE = {
|
|
80
|
+
// https://regex101.com/r/pOrChS/1
|
|
81
|
+
subject_line: /^(?<subject>[^\n]*)/,
|
|
72
82
|
// https://regex101.com/r/wLmGVq/1
|
|
73
83
|
stack_id: new RegExp(`${TEMPLATE.stack_id("(?<id>[^\\s]+)")}`, "i"),
|
|
74
84
|
group_title: new RegExp(TEMPLATE.group_title("(?<title>[^\\n^\\r]+)"), "i"),
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// Bun Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`get_commits ABC..DEF:
|
|
4
|
+
[
|
|
5
|
+
{
|
|
6
|
+
"branch_id": null,
|
|
7
|
+
"full_message": "homebrew-git-stack 2.7.1",
|
|
8
|
+
"sha": "ba067f8ad641dda7e65e3c2acb2421c955843829",
|
|
9
|
+
"subject_line": "homebrew-git-stack 2.7.1",
|
|
10
|
+
"title": null,
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"branch_id": "noah/paint-test---4gwpqhd033n6y5",
|
|
14
|
+
"full_message":
|
|
15
|
+
"Rebase: debug logs
|
|
16
|
+
|
|
17
|
+
git-stack-id: noah/paint-test---4gwpqhd033n6y5
|
|
18
|
+
git-stack-title: Rebase: debug logs"
|
|
19
|
+
,
|
|
20
|
+
"sha": "7c8da9f5fe681fc7459a0e737241df631983cd3c",
|
|
21
|
+
"subject_line": "Rebase: debug logs",
|
|
22
|
+
"title": "Rebase: debug logs",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"branch_id": "noah/paint-test---4gwpqirewoudxa",
|
|
26
|
+
"full_message":
|
|
27
|
+
"CommitMetadata: track last group id
|
|
28
|
+
|
|
29
|
+
git-stack-id: noah/paint-test---4gwpqirewoudxa
|
|
30
|
+
git-stack-title: CommitMetadata: track last group id"
|
|
31
|
+
,
|
|
32
|
+
"sha": "47a69d37f8c5cc796884a91fa9e93fc1db5297dd",
|
|
33
|
+
"subject_line": "CommitMetadata: track last group id",
|
|
34
|
+
"title": "CommitMetadata: track last group id",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"branch_id": "noah/paint-test---4gwpqjcpu7-isv",
|
|
38
|
+
"full_message":
|
|
39
|
+
"Github: pr_list duration timer
|
|
40
|
+
|
|
41
|
+
git-stack-id: noah/paint-test---4gwpqjcpu7-isv
|
|
42
|
+
git-stack-title: Github: pr_list duration timer"
|
|
43
|
+
,
|
|
44
|
+
"sha": "8ccf42a7c72aa194fafc7c326f5874f5a0a009c6",
|
|
45
|
+
"subject_line": "Github: pr_list duration timer",
|
|
46
|
+
"title": "Github: pr_list duration timer",
|
|
47
|
+
},
|
|
48
|
+
]
|
|
49
|
+
1`] = `
|
|
50
|
+
[
|
|
51
|
+
{
|
|
52
|
+
"branch_id": null,
|
|
53
|
+
"full_message": "homebrew-git-stack 2.7.1",
|
|
54
|
+
"sha": "ba067f8ad641dda7e65e3c2acb2421c955843829",
|
|
55
|
+
"subject_line": "homebrew-git-stack 2.7.1",
|
|
56
|
+
"title": null,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"branch_id": "noah/paint-test---4gwpqhd033n6y5",
|
|
60
|
+
"full_message":
|
|
61
|
+
"Rebase: debug logs
|
|
62
|
+
|
|
63
|
+
git-stack-id: noah/paint-test---4gwpqhd033n6y5
|
|
64
|
+
git-stack-title: Rebase: debug logs"
|
|
65
|
+
,
|
|
66
|
+
"sha": "7c8da9f5fe681fc7459a0e737241df631983cd3c",
|
|
67
|
+
"subject_line": "Rebase: debug logs",
|
|
68
|
+
"title": "Rebase: debug logs",
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"branch_id": "noah/paint-test---4gwpqirewoudxa",
|
|
72
|
+
"full_message":
|
|
73
|
+
"CommitMetadata: track last group id
|
|
74
|
+
|
|
75
|
+
git-stack-id: noah/paint-test---4gwpqirewoudxa
|
|
76
|
+
git-stack-title: CommitMetadata: track last group id"
|
|
77
|
+
,
|
|
78
|
+
"sha": "47a69d37f8c5cc796884a91fa9e93fc1db5297dd",
|
|
79
|
+
"subject_line": "CommitMetadata: track last group id",
|
|
80
|
+
"title": "CommitMetadata: track last group id",
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"branch_id": "noah/paint-test---4gwpqjcpu7-isv",
|
|
84
|
+
"full_message":
|
|
85
|
+
"Github: pr_list duration timer
|
|
86
|
+
|
|
87
|
+
git-stack-id: noah/paint-test---4gwpqjcpu7-isv
|
|
88
|
+
git-stack-title: Github: pr_list duration timer"
|
|
89
|
+
,
|
|
90
|
+
"sha": "8ccf42a7c72aa194fafc7c326f5874f5a0a009c6",
|
|
91
|
+
"subject_line": "Github: pr_list duration timer",
|
|
92
|
+
"title": "Github: pr_list duration timer",
|
|
93
|
+
},
|
|
94
|
+
]
|
|
95
|
+
`;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function get_timeout_fn(ms: number, message: string) {
|
|
2
|
+
return function timeout<T>(promise: Promise<T>) {
|
|
3
|
+
let id: ReturnType<typeof setTimeout>;
|
|
4
|
+
|
|
5
|
+
const timeout = new Promise<never>((_resolve, reject) => {
|
|
6
|
+
id = setTimeout(() => reject(new Error(message)), ms);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
return Promise.race([promise, timeout]).finally(() => clearTimeout(id));
|
|
10
|
+
};
|
|
11
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { test, expect } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import * as git from "~/core/git";
|
|
4
|
+
|
|
5
|
+
test("get_commits ABC..DEF", async () => {
|
|
6
|
+
const commits = await git.get_commits("e781ede..8ccf42a");
|
|
7
|
+
expect(commits).toMatchSnapshot(`
|
|
8
|
+
[
|
|
9
|
+
{
|
|
10
|
+
"branch_id": null,
|
|
11
|
+
"full_message": "homebrew-git-stack 2.7.1",
|
|
12
|
+
"sha": "ba067f8ad641dda7e65e3c2acb2421c955843829",
|
|
13
|
+
"subject_line": "homebrew-git-stack 2.7.1",
|
|
14
|
+
"title": null,
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"branch_id": "noah/paint-test---4gwpqhd033n6y5",
|
|
18
|
+
"full_message":
|
|
19
|
+
"Rebase: debug logs
|
|
20
|
+
|
|
21
|
+
git-stack-id: noah/paint-test---4gwpqhd033n6y5
|
|
22
|
+
git-stack-title: Rebase: debug logs"
|
|
23
|
+
,
|
|
24
|
+
"sha": "7c8da9f5fe681fc7459a0e737241df631983cd3c",
|
|
25
|
+
"subject_line": "Rebase: debug logs",
|
|
26
|
+
"title": "Rebase: debug logs",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"branch_id": "noah/paint-test---4gwpqirewoudxa",
|
|
30
|
+
"full_message":
|
|
31
|
+
"CommitMetadata: track last group id
|
|
32
|
+
|
|
33
|
+
git-stack-id: noah/paint-test---4gwpqirewoudxa
|
|
34
|
+
git-stack-title: CommitMetadata: track last group id"
|
|
35
|
+
,
|
|
36
|
+
"sha": "47a69d37f8c5cc796884a91fa9e93fc1db5297dd",
|
|
37
|
+
"subject_line": "CommitMetadata: track last group id",
|
|
38
|
+
"title": "CommitMetadata: track last group id",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"branch_id": "noah/paint-test---4gwpqjcpu7-isv",
|
|
42
|
+
"full_message":
|
|
43
|
+
"Github: pr_list duration timer
|
|
44
|
+
|
|
45
|
+
git-stack-id: noah/paint-test---4gwpqjcpu7-isv
|
|
46
|
+
git-stack-title: Github: pr_list duration timer"
|
|
47
|
+
,
|
|
48
|
+
"sha": "8ccf42a7c72aa194fafc7c326f5874f5a0a009c6",
|
|
49
|
+
"subject_line": "Github: pr_list duration timer",
|
|
50
|
+
"title": "Github: pr_list duration timer",
|
|
51
|
+
},
|
|
52
|
+
]
|
|
53
|
+
`);
|
|
54
|
+
});
|
package/src/core/git.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import * as Metadata from "~/core/Metadata";
|
|
2
|
+
import { cli } from "~/core/cli";
|
|
3
|
+
|
|
4
|
+
export type Commit = Awaited<ReturnType<typeof get_commits>>[0];
|
|
5
|
+
|
|
6
|
+
export async function get_commits(dot_range: string) {
|
|
7
|
+
const log_result = await cli(`git log ${dot_range} --format=${FORMAT} --color=never`);
|
|
8
|
+
|
|
9
|
+
if (!log_result.stdout) {
|
|
10
|
+
return [];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const commit_list = [];
|
|
14
|
+
|
|
15
|
+
for (let record of log_result.stdout.split(SEP.record)) {
|
|
16
|
+
record = record.replace(/^\n/, "");
|
|
17
|
+
record = record.replace(/\n$/, "");
|
|
18
|
+
|
|
19
|
+
if (!record) continue;
|
|
20
|
+
|
|
21
|
+
const [sha, full_message] = record.split(SEP.field);
|
|
22
|
+
|
|
23
|
+
const metadata = Metadata.read(full_message);
|
|
24
|
+
const branch_id = metadata.id;
|
|
25
|
+
const subject_line = metadata.subject || "";
|
|
26
|
+
const title = metadata.title;
|
|
27
|
+
|
|
28
|
+
const commit = {
|
|
29
|
+
sha,
|
|
30
|
+
full_message,
|
|
31
|
+
subject_line,
|
|
32
|
+
branch_id,
|
|
33
|
+
title,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
commit_list.push(commit);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
commit_list.reverse();
|
|
40
|
+
|
|
41
|
+
return commit_list;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Why these separators?
|
|
45
|
+
// - Rare in human written text
|
|
46
|
+
// - Supported in git %xNN to write bytes
|
|
47
|
+
// - Supported in javascript \xNN to write bytes
|
|
48
|
+
// - Used historically as separators in unicode
|
|
49
|
+
// https://en.wikipedia.org/wiki/C0_and_C1_control_codes#Field_separators
|
|
50
|
+
const SEP = {
|
|
51
|
+
record: "\x1e",
|
|
52
|
+
field: "\x1f",
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const FORMAT = `%H${SEP.field}%B${SEP.record}`;
|
package/src/core/github.tsx
CHANGED
|
@@ -8,6 +8,7 @@ import * as Ink from "ink-cjs";
|
|
|
8
8
|
|
|
9
9
|
import { Brackets } from "~/app/Brackets";
|
|
10
10
|
import { Store } from "~/app/Store";
|
|
11
|
+
import { Timer } from "~/core/Timer";
|
|
11
12
|
import { cli } from "~/core/cli";
|
|
12
13
|
import { colors } from "~/core/colors";
|
|
13
14
|
import { get_tmp_dir } from "~/core/get_tmp_dir";
|
|
@@ -19,6 +20,9 @@ export async function pr_list(): Promise<Array<PullRequest>> {
|
|
|
19
20
|
const state = Store.getState();
|
|
20
21
|
const actions = state.actions;
|
|
21
22
|
|
|
23
|
+
const timer = Timer();
|
|
24
|
+
actions.debug("start github.pr_list");
|
|
25
|
+
|
|
22
26
|
const username = state.username;
|
|
23
27
|
const repo_path = state.repo_path;
|
|
24
28
|
invariant(username, "username must exist");
|
|
@@ -53,6 +57,8 @@ export async function pr_list(): Promise<Array<PullRequest>> {
|
|
|
53
57
|
}
|
|
54
58
|
});
|
|
55
59
|
|
|
60
|
+
const duration = timer.duration();
|
|
61
|
+
actions.debug(`end github.pr_list (duration=${duration})`);
|
|
56
62
|
return result_pr_list;
|
|
57
63
|
}
|
|
58
64
|
|