git-stack-cli 0.3.0 → 0.4.2
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/README.md +2 -54
- package/dist/__fixtures__/metadata.js +399 -475
- package/dist/app/App copy.js +30 -0
- package/dist/app/App.js +9 -3
- package/dist/app/AutoUpdate.js +147 -0
- package/dist/app/Debug.js +4 -4
- package/dist/app/FormatText.js +9 -0
- package/dist/app/GatherMetadata copy.js +33 -29
- package/dist/app/GatherMetadata.js +30 -39
- package/dist/app/Input.js +15 -0
- package/dist/app/LocalCommitStatus.js +42 -0
- package/dist/app/LocalMergeRebase.js +113 -0
- package/dist/app/ManualRebase copy.js +127 -0
- package/dist/app/ManualRebase.js +23 -12
- package/dist/app/MultiSelect.js +2 -2
- package/dist/app/NPMAutoUpdate.js +34 -0
- package/dist/app/Parens copy.js +1 -1
- package/dist/app/PreLocalMergeRebase.js +21 -0
- package/dist/app/PreSelectCommitRanges copy.js +15 -23
- package/dist/app/PreSelectCommitRanges.js +10 -0
- package/dist/app/Providers.js +5 -0
- package/dist/app/SelectCommitRanges.js +95 -82
- package/dist/app/Status.js +18 -3
- package/dist/app/StatusTable.js +35 -26
- package/dist/app/Store.js +32 -6
- package/dist/app/TextInput.js +37 -0
- package/dist/app/YesNoPrompt.js +19 -5
- package/dist/app/main.js +6 -0
- package/dist/command.js +15 -34
- package/dist/core/CommitMetadata.js +19 -5
- package/dist/core/Metadata.js +4 -3
- package/dist/core/cli.js +4 -0
- package/dist/core/github.js +26 -18
- package/dist/core/id.js +61 -0
- package/dist/core/readJson.js +3 -0
- package/dist/core/read_json.js +12 -0
- package/dist/core/safe_quote.js +9 -0
- package/dist/core/short_id.js +60 -0
- package/dist/core/sleep copy.js +3 -0
- package/package.json +3 -5
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import * as Ink from "ink";
|
|
3
|
+
import * as CommitMetadata from "../core/CommitMetadata.js";
|
|
4
|
+
import * as Metadata from "../core/Metadata.js";
|
|
5
|
+
import { cli } from "../core/cli.js";
|
|
6
|
+
import * as github from "../core/github.js";
|
|
7
|
+
import { invariant } from "../core/invariant.js";
|
|
8
|
+
import { short_id } from "../core/short_id.js";
|
|
9
|
+
import { Await } from "./Await.js";
|
|
10
|
+
import { Brackets } from "./Brackets.js";
|
|
11
|
+
import { Store } from "./Store.js";
|
|
12
|
+
export function ManualRebase(props) {
|
|
13
|
+
return (React.createElement(Await, { fallback: React.createElement(Ink.Text, { color: "yellow" }, "Rebasing commits..."), function: () => run(props) }));
|
|
14
|
+
}
|
|
15
|
+
async function run(props) {
|
|
16
|
+
const state = Store.getState();
|
|
17
|
+
const actions = state.actions;
|
|
18
|
+
const branch_name = state.branch_name;
|
|
19
|
+
const merge_base = state.merge_base;
|
|
20
|
+
const commit_map = state.commit_map;
|
|
21
|
+
invariant(branch_name, "branch_name must exist");
|
|
22
|
+
invariant(merge_base, "merge_base must exist");
|
|
23
|
+
invariant(commit_map, "commit_map must exist");
|
|
24
|
+
// always listen for SIGINT event and restore git state
|
|
25
|
+
process.once("SIGINT", handle_exit);
|
|
26
|
+
const temp_branch_name = `${branch_name}_${short_id()}`;
|
|
27
|
+
const commit_range = await CommitMetadata.range(commit_map);
|
|
28
|
+
// reverse commit list so that we can cherry-pick in order
|
|
29
|
+
commit_range.group_list.reverse();
|
|
30
|
+
let rebase_merge_base = merge_base;
|
|
31
|
+
let rebase_group_index = 0;
|
|
32
|
+
for (let i = 0; i < commit_range.group_list.length; i++) {
|
|
33
|
+
const group = commit_range.group_list[i];
|
|
34
|
+
if (!group.dirty) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (i > 0) {
|
|
38
|
+
const last_group = commit_range.group_list[i - 1];
|
|
39
|
+
const last_commit = last_group.commits[last_group.commits.length - 1];
|
|
40
|
+
rebase_merge_base = last_commit.sha;
|
|
41
|
+
rebase_group_index = i;
|
|
42
|
+
}
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
// create temporary branch based on merge base
|
|
47
|
+
await cli(`git checkout -b ${temp_branch_name} ${rebase_merge_base}`);
|
|
48
|
+
for (let i = rebase_group_index; i < commit_range.group_list.length; i++) {
|
|
49
|
+
const group = commit_range.group_list[i];
|
|
50
|
+
invariant(group.base, "group.base must exist");
|
|
51
|
+
// cherry-pick and amend commits one by one
|
|
52
|
+
for (const commit of group.commits) {
|
|
53
|
+
await cli(`git cherry-pick ${commit.sha}`);
|
|
54
|
+
if (commit.branch_id !== group.id) {
|
|
55
|
+
const new_message = await Metadata.write(commit.message, group.id);
|
|
56
|
+
await cli(`git commit --amend -m "${new_message}"`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
actions.output(React.createElement(Ink.Text, { color: "yellow", wrap: "truncate-end" },
|
|
60
|
+
"Syncing ",
|
|
61
|
+
React.createElement(Brackets, null, group.pr?.title || group.id),
|
|
62
|
+
"..."));
|
|
63
|
+
if (!props.skipSync) {
|
|
64
|
+
// push to origin since github requires commit shas to line up perfectly
|
|
65
|
+
await cli(`git push -f origin HEAD:${group.id}`);
|
|
66
|
+
if (group.pr) {
|
|
67
|
+
// ensure base matches pr in github
|
|
68
|
+
await github.pr_base(group.id, group.base);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
// delete local group branch if leftover
|
|
72
|
+
await cli(`git branch -D ${group.id}`, { ignoreExitCode: true });
|
|
73
|
+
// move to temporary branch for creating pr
|
|
74
|
+
await cli(`git checkout -b ${group.id}`);
|
|
75
|
+
// create pr in github
|
|
76
|
+
await github.pr_create(group.id, group.base);
|
|
77
|
+
// move back to temp branch
|
|
78
|
+
await cli(`git checkout ${temp_branch_name}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// after all commits have been cherry-picked and amended
|
|
83
|
+
// move the branch pointer to the newly created temporary branch
|
|
84
|
+
// now we are in locally in sync with github and on the original branch
|
|
85
|
+
await cli(`git branch -f ${branch_name} ${temp_branch_name}`);
|
|
86
|
+
restore_git();
|
|
87
|
+
actions.set((state) => {
|
|
88
|
+
state.step = "post-rebase-status";
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
actions.output(React.createElement(Ink.Text, { color: "#ef4444" }, "Error during rebase."));
|
|
93
|
+
if (err instanceof Error) {
|
|
94
|
+
actions.debug(React.createElement(Ink.Text, { color: "#ef4444" }, err.message));
|
|
95
|
+
}
|
|
96
|
+
handle_exit();
|
|
97
|
+
}
|
|
98
|
+
// cleanup git operations if cancelled during manual rebase
|
|
99
|
+
function restore_git() {
|
|
100
|
+
// signint handler MUST run synchronously
|
|
101
|
+
// trying to use `await cli(...)` here will silently fail since
|
|
102
|
+
// all children processes receive the SIGINT signal
|
|
103
|
+
const spawn_options = { ignoreExitCode: true };
|
|
104
|
+
// always put self back in original branch
|
|
105
|
+
cli.sync(`git checkout ${branch_name}`, spawn_options);
|
|
106
|
+
// ...and cleanup temporary branch
|
|
107
|
+
cli.sync(`git branch -D ${temp_branch_name}`, spawn_options);
|
|
108
|
+
if (commit_range) {
|
|
109
|
+
// ...and cleanup pr group branches
|
|
110
|
+
for (const group of commit_range.group_list) {
|
|
111
|
+
cli.sync(`git branch -D ${group.id}`, spawn_options);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function handle_exit() {
|
|
116
|
+
actions.output(React.createElement(Ink.Text, { color: "yellow" },
|
|
117
|
+
"Restoring ",
|
|
118
|
+
React.createElement(Brackets, null, branch_name),
|
|
119
|
+
"..."));
|
|
120
|
+
restore_git();
|
|
121
|
+
actions.output(React.createElement(Ink.Text, { color: "yellow" },
|
|
122
|
+
"Restored ",
|
|
123
|
+
React.createElement(Brackets, null, branch_name),
|
|
124
|
+
"."));
|
|
125
|
+
actions.exit(5);
|
|
126
|
+
}
|
|
127
|
+
}
|
package/dist/app/ManualRebase.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import * as Ink from "ink";
|
|
3
|
-
import { v4 as uuid_v4 } from "uuid";
|
|
4
3
|
import * as CommitMetadata from "../core/CommitMetadata.js";
|
|
5
4
|
import * as Metadata from "../core/Metadata.js";
|
|
6
5
|
import { cli } from "../core/cli.js";
|
|
7
6
|
import * as github from "../core/github.js";
|
|
8
7
|
import { invariant } from "../core/invariant.js";
|
|
8
|
+
import { short_id } from "../core/short_id.js";
|
|
9
9
|
import { Await } from "./Await.js";
|
|
10
10
|
import { Brackets } from "./Brackets.js";
|
|
11
|
+
import { FormatText } from "./FormatText.js";
|
|
11
12
|
import { Store } from "./Store.js";
|
|
12
13
|
export function ManualRebase(props) {
|
|
13
14
|
return (React.createElement(Await, { fallback: React.createElement(Ink.Text, { color: "yellow" }, "Rebasing commits..."), function: () => run(props) }));
|
|
@@ -15,17 +16,18 @@ export function ManualRebase(props) {
|
|
|
15
16
|
async function run(props) {
|
|
16
17
|
const state = Store.getState();
|
|
17
18
|
const actions = state.actions;
|
|
19
|
+
const argv = state.argv;
|
|
18
20
|
const branch_name = state.branch_name;
|
|
19
21
|
const merge_base = state.merge_base;
|
|
20
22
|
const commit_map = state.commit_map;
|
|
23
|
+
invariant(argv, "argv must exist");
|
|
21
24
|
invariant(branch_name, "branch_name must exist");
|
|
22
25
|
invariant(merge_base, "merge_base must exist");
|
|
23
26
|
invariant(commit_map, "commit_map must exist");
|
|
24
27
|
// always listen for SIGINT event and restore git state
|
|
25
28
|
process.once("SIGINT", handle_exit);
|
|
26
|
-
const temp_branch_name = `${branch_name}_${
|
|
27
|
-
|
|
28
|
-
commit_range = await CommitMetadata.range(commit_map);
|
|
29
|
+
const temp_branch_name = `${branch_name}_${short_id()}`;
|
|
30
|
+
const commit_range = await CommitMetadata.range(commit_map);
|
|
29
31
|
// reverse commit list so that we can cherry-pick in order
|
|
30
32
|
commit_range.group_list.reverse();
|
|
31
33
|
let rebase_merge_base = merge_base;
|
|
@@ -57,13 +59,16 @@ async function run(props) {
|
|
|
57
59
|
await cli(`git commit --amend -m "${new_message}"`);
|
|
58
60
|
}
|
|
59
61
|
}
|
|
60
|
-
actions.output(React.createElement(Ink.Text, { color: "yellow", wrap: "truncate-end" },
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
"..."));
|
|
62
|
+
actions.output(React.createElement(FormatText, { wrapper: React.createElement(Ink.Text, { color: "yellow", wrap: "truncate-end" }), message: "Syncing {group}\u2026", values: {
|
|
63
|
+
group: (React.createElement(Brackets, null, group.pr?.title || group.title || group.id)),
|
|
64
|
+
} }));
|
|
64
65
|
if (!props.skipSync) {
|
|
65
66
|
// push to origin since github requires commit shas to line up perfectly
|
|
66
|
-
|
|
67
|
+
const git_push_command = [`git push -f origin HEAD:${group.id}`];
|
|
68
|
+
if (argv["no-verify"]) {
|
|
69
|
+
git_push_command.push("--no-verify");
|
|
70
|
+
}
|
|
71
|
+
await cli(git_push_command.join(" "));
|
|
67
72
|
if (group.pr) {
|
|
68
73
|
// ensure base matches pr in github
|
|
69
74
|
await github.pr_base(group.id, group.base);
|
|
@@ -74,7 +79,11 @@ async function run(props) {
|
|
|
74
79
|
// move to temporary branch for creating pr
|
|
75
80
|
await cli(`git checkout -b ${group.id}`);
|
|
76
81
|
// create pr in github
|
|
77
|
-
await github.pr_create(
|
|
82
|
+
await github.pr_create({
|
|
83
|
+
branch: group.id,
|
|
84
|
+
base: group.base,
|
|
85
|
+
title: group.title,
|
|
86
|
+
});
|
|
78
87
|
// move back to temp branch
|
|
79
88
|
await cli(`git checkout ${temp_branch_name}`);
|
|
80
89
|
}
|
|
@@ -90,9 +99,11 @@ async function run(props) {
|
|
|
90
99
|
});
|
|
91
100
|
}
|
|
92
101
|
catch (err) {
|
|
93
|
-
actions.
|
|
102
|
+
actions.error("Unable to rebase.");
|
|
94
103
|
if (err instanceof Error) {
|
|
95
|
-
actions.
|
|
104
|
+
if (actions.isDebug()) {
|
|
105
|
+
actions.error(err.message);
|
|
106
|
+
}
|
|
96
107
|
}
|
|
97
108
|
handle_exit();
|
|
98
109
|
}
|
package/dist/app/MultiSelect.js
CHANGED
|
@@ -87,7 +87,7 @@ export function MultiSelect(props) {
|
|
|
87
87
|
const active = i === index;
|
|
88
88
|
const selected = selected_set.has(i);
|
|
89
89
|
const disabled = item.disabled || false;
|
|
90
|
-
return (React.createElement(ItemRow, { key: item.label, label: item.label, active: active, selected: selected, disabled: disabled }));
|
|
90
|
+
return (React.createElement(ItemRow, { key: item.label, label: item.label, active: active, selected: selected, disabled: disabled, maxWidth: props.maxWidth }));
|
|
91
91
|
})));
|
|
92
92
|
}
|
|
93
93
|
function ItemRow(props) {
|
|
@@ -111,7 +111,7 @@ function ItemRow(props) {
|
|
|
111
111
|
}
|
|
112
112
|
return (React.createElement(Ink.Box, { flexDirection: "row", gap: 1 },
|
|
113
113
|
React.createElement(Radio, { selected: props.selected, disabled: props.disabled }),
|
|
114
|
-
React.createElement(Ink.Box,
|
|
114
|
+
React.createElement(Ink.Box, { width: props.maxWidth },
|
|
115
115
|
React.createElement(Ink.Text, { bold: bold, underline: underline, color: color, dimColor: dimColor, wrap: "truncate-end" }, props.label))));
|
|
116
116
|
}
|
|
117
117
|
function Radio(props) {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import * as Ink from "ink";
|
|
4
|
+
export function AutoUpdate(props) {
|
|
5
|
+
const props_ref = React.useRef(props);
|
|
6
|
+
props_ref.current = props;
|
|
7
|
+
React.useEffect(() => {
|
|
8
|
+
async function auto_update() {
|
|
9
|
+
try {
|
|
10
|
+
const npm_res = await fetch(`https://registry.npmjs.org/${props.name}`);
|
|
11
|
+
const npm_json = await npm_res.json();
|
|
12
|
+
const latest = npm_json["dist-tags"].latest;
|
|
13
|
+
console.debug({ latest });
|
|
14
|
+
const script_path = process.argv[1];
|
|
15
|
+
const path_list = [process.argv[1]];
|
|
16
|
+
const real_path_list = path_list.map((p) => fs.realpathSync(p));
|
|
17
|
+
console.debug({ real_path_list, path_list });
|
|
18
|
+
// const winner = await Promise.race([
|
|
19
|
+
// sleep(Math.random() * 5000).then(() => "a"),
|
|
20
|
+
// sleep(Math.random() * 5000).then(() => "b"),
|
|
21
|
+
// ]);
|
|
22
|
+
// console.debug({ winner });
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
console.debug({ err });
|
|
26
|
+
// handled by catch below
|
|
27
|
+
throw err;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const onError = props_ref.current.onError || (() => { });
|
|
31
|
+
auto_update().catch(onError);
|
|
32
|
+
}, []);
|
|
33
|
+
return React.createElement(Ink.Text, { color: "yellow" }, "Checking for latest version...");
|
|
34
|
+
}
|
package/dist/app/Parens copy.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import * as Ink from "ink";
|
|
3
3
|
export function Parens(props) {
|
|
4
|
-
const color = "#
|
|
4
|
+
const color = "#38bdf8";
|
|
5
5
|
return (React.createElement(Ink.Text, null,
|
|
6
6
|
React.createElement(Ink.Text, { color: color }, "("),
|
|
7
7
|
props.children,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { invariant } from "../core/invariant.js";
|
|
3
|
+
import { Store } from "./Store.js";
|
|
4
|
+
import { YesNoPrompt } from "./YesNoPrompt.js";
|
|
5
|
+
export function PreLocalMergeRebase() {
|
|
6
|
+
const actions = Store.useActions();
|
|
7
|
+
const argv = Store.useState((state) => state.argv);
|
|
8
|
+
invariant(argv, "argv must exist");
|
|
9
|
+
React.useEffect(() => {
|
|
10
|
+
if (argv.force) {
|
|
11
|
+
Store.setState((state) => {
|
|
12
|
+
state.step = "local-merge-rebase";
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
}, [argv]);
|
|
16
|
+
return (React.createElement(YesNoPrompt, { message: "Local branch needs to be rebased, would you like to rebase to update your local branch?", onYes: () => {
|
|
17
|
+
actions.set((state) => {
|
|
18
|
+
state.step = "local-merge-rebase";
|
|
19
|
+
});
|
|
20
|
+
}, onNo: () => actions.exit(0) }));
|
|
21
|
+
}
|
|
@@ -1,29 +1,21 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import
|
|
3
|
-
import { Exit } from "./Exit.js";
|
|
2
|
+
import { invariant } from "../core/invariant.js";
|
|
4
3
|
import { Store } from "./Store.js";
|
|
4
|
+
import { YesNoPrompt } from "./YesNoPrompt.js";
|
|
5
5
|
export function PreSelectCommitRanges() {
|
|
6
6
|
const actions = Store.useActions();
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
state.step = "select-commit-ranges";
|
|
15
|
-
});
|
|
7
|
+
const argv = Store.useState((state) => state.argv);
|
|
8
|
+
invariant(argv, "argv must exist");
|
|
9
|
+
React.useEffect(() => {
|
|
10
|
+
if (argv.force) {
|
|
11
|
+
Store.setState((state) => {
|
|
12
|
+
state.step = "select-commit-ranges";
|
|
13
|
+
});
|
|
16
14
|
}
|
|
17
|
-
});
|
|
18
|
-
return (React.createElement(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"(",
|
|
24
|
-
React.createElement(Ink.Text, { color: "gray" },
|
|
25
|
-
React.createElement(Ink.Text, { color: "green", dimColor: true }, "Y"),
|
|
26
|
-
"/",
|
|
27
|
-
React.createElement(Ink.Text, { color: "red", dimColor: true }, "n")),
|
|
28
|
-
")"))));
|
|
15
|
+
}, [argv]);
|
|
16
|
+
return (React.createElement(YesNoPrompt, { message: "Some commits are new or outdated, would you like to select new commit ranges?", onYes: () => {
|
|
17
|
+
actions.set((state) => {
|
|
18
|
+
state.step = "select-commit-ranges";
|
|
19
|
+
});
|
|
20
|
+
}, onNo: () => actions.exit(0) }));
|
|
29
21
|
}
|
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
+
import { invariant } from "../core/invariant.js";
|
|
2
3
|
import { Store } from "./Store.js";
|
|
3
4
|
import { YesNoPrompt } from "./YesNoPrompt.js";
|
|
4
5
|
export function PreSelectCommitRanges() {
|
|
5
6
|
const actions = Store.useActions();
|
|
7
|
+
const argv = Store.useState((state) => state.argv);
|
|
8
|
+
invariant(argv, "argv must exist");
|
|
9
|
+
React.useEffect(() => {
|
|
10
|
+
if (argv.force) {
|
|
11
|
+
Store.setState((state) => {
|
|
12
|
+
state.step = "select-commit-ranges";
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
}, [argv]);
|
|
6
16
|
return (React.createElement(YesNoPrompt, { message: "Some commits are new or outdated, would you like to select new commit ranges?", onYes: () => {
|
|
7
17
|
actions.set((state) => {
|
|
8
18
|
state.step = "select-commit-ranges";
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import * as Ink from "ink";
|
|
3
|
-
import { v4 as uuid_v4 } from "uuid";
|
|
4
3
|
import { invariant } from "../core/invariant.js";
|
|
4
|
+
import { short_id } from "../core/short_id.js";
|
|
5
5
|
import { wrap_index } from "../core/wrap_index.js";
|
|
6
|
+
import { Brackets } from "./Brackets.js";
|
|
7
|
+
import { FormatText } from "./FormatText.js";
|
|
6
8
|
import { MultiSelect } from "./MultiSelect.js";
|
|
7
9
|
import { Parens } from "./Parens.js";
|
|
8
10
|
import { Store } from "./Store.js";
|
|
11
|
+
import { TextInput } from "./TextInput.js";
|
|
9
12
|
export function SelectCommitRanges() {
|
|
10
13
|
const commit_range = Store.useState((state) => state.commit_range);
|
|
11
14
|
invariant(commit_range, "commit_range must exist");
|
|
@@ -13,11 +16,10 @@ export function SelectCommitRanges() {
|
|
|
13
16
|
}
|
|
14
17
|
function SelectCommitRangesInternal(props) {
|
|
15
18
|
const actions = Store.useActions();
|
|
16
|
-
const [
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
});
|
|
19
|
+
const [selected_group_id, set_selected_group_id] = React.useState(props.commit_range.UNASSIGNED);
|
|
20
|
+
const [group_input, set_group_input] = React.useState(false);
|
|
21
|
+
const [new_group_list, create_group] = React.useReducer((group_list, group) => {
|
|
22
|
+
return group_list.concat(group);
|
|
21
23
|
}, []);
|
|
22
24
|
const [commit_map, update_commit_map] = React.useReducer((map, args) => {
|
|
23
25
|
map.set(args.key, args.value);
|
|
@@ -31,7 +33,6 @@ function SelectCommitRangesInternal(props) {
|
|
|
31
33
|
});
|
|
32
34
|
const group_list = [];
|
|
33
35
|
// detect if there are unassigned commits
|
|
34
|
-
// unshift an unassigned group if so to collect them
|
|
35
36
|
let unassigned_count = 0;
|
|
36
37
|
for (const [, group_id] of commit_map.entries()) {
|
|
37
38
|
if (group_id === null) {
|
|
@@ -39,52 +40,56 @@ function SelectCommitRangesInternal(props) {
|
|
|
39
40
|
unassigned_count++;
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
|
-
if (unassigned_count) {
|
|
43
|
-
group_list.push({
|
|
44
|
-
id: "unassigned",
|
|
45
|
-
title: "Unassigned",
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
43
|
group_list.push(...new_group_list);
|
|
44
|
+
const total_group_count = new_group_list.length + props.commit_range.group_list.length;
|
|
49
45
|
for (const group of props.commit_range.group_list) {
|
|
50
|
-
if (group.pr)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
46
|
+
if (group.pr?.state === "MERGED")
|
|
47
|
+
continue;
|
|
48
|
+
if (group.id === props.commit_range.UNASSIGNED) {
|
|
49
|
+
// only include unassigned group when there are no other groups
|
|
50
|
+
if (total_group_count === 1) {
|
|
51
|
+
group_list.push({
|
|
52
|
+
id: group.id,
|
|
53
|
+
title: "Unassigned",
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
continue;
|
|
55
57
|
}
|
|
58
|
+
group_list.push({
|
|
59
|
+
id: group.id,
|
|
60
|
+
title: group.pr?.title || group.id,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
let current_index = group_list.findIndex((g) => g.id === selected_group_id);
|
|
64
|
+
if (current_index === -1) {
|
|
65
|
+
current_index = 0;
|
|
56
66
|
}
|
|
57
|
-
const [selected_group_id, set_selected_group_id] = React.useState(group_list[0].id);
|
|
58
|
-
const isUnassigned = selected_group_id === "unassigned";
|
|
59
|
-
const current_index = group_list.findIndex((g) => g.id === selected_group_id);
|
|
60
67
|
Ink.useInput((input, key) => {
|
|
61
68
|
const inputLower = input.toLowerCase();
|
|
62
|
-
|
|
69
|
+
const hasUnassignedCommits = unassigned_count > 0;
|
|
70
|
+
if (!hasUnassignedCommits && inputLower === "s") {
|
|
63
71
|
actions.set((state) => {
|
|
64
72
|
state.commit_map = {};
|
|
65
73
|
for (const [sha, id] of commit_map.entries()) {
|
|
66
74
|
if (id) {
|
|
67
|
-
|
|
75
|
+
const group = new_group_list.find((g) => g.id === id);
|
|
76
|
+
// console.debug({ sha, id, group });
|
|
77
|
+
if (group) {
|
|
78
|
+
state.commit_map[sha] = group;
|
|
79
|
+
}
|
|
68
80
|
}
|
|
69
81
|
}
|
|
70
82
|
switch (inputLower) {
|
|
71
83
|
case "s":
|
|
72
84
|
state.step = "manual-rebase";
|
|
73
85
|
break;
|
|
74
|
-
case "r":
|
|
75
|
-
state.step = "manual-rebase-no-sync";
|
|
76
86
|
}
|
|
77
87
|
});
|
|
78
88
|
return;
|
|
79
89
|
}
|
|
80
90
|
// only allow create when on unassigned group
|
|
81
|
-
if (
|
|
82
|
-
|
|
83
|
-
actions.output(React.createElement(Ink.Box, null,
|
|
84
|
-
React.createElement(Ink.Text, { dimColor: true }, "Created new group "),
|
|
85
|
-
React.createElement(Ink.Text, { color: "blueBright" }, id)));
|
|
86
|
-
create_group(id);
|
|
87
|
-
set_selected_group_id(id);
|
|
91
|
+
if (hasUnassignedCommits && inputLower === "c") {
|
|
92
|
+
set_group_input(true);
|
|
88
93
|
return;
|
|
89
94
|
}
|
|
90
95
|
if (key.leftArrow) {
|
|
@@ -99,21 +104,11 @@ function SelectCommitRangesInternal(props) {
|
|
|
99
104
|
}
|
|
100
105
|
});
|
|
101
106
|
const group = group_list[current_index];
|
|
102
|
-
// <- (2/4) #742 Title A ->
|
|
103
|
-
const max_group_label_width = 64;
|
|
104
|
-
let group_title_width = max_group_label_width;
|
|
105
|
-
const left_arrow = `${SYMBOL.left} `;
|
|
106
|
-
const right_arrow = ` ${SYMBOL.right}`;
|
|
107
|
-
const group_position = `(${current_index + 1}/${group_list.length}) `;
|
|
108
|
-
const title = group.title || "Unassigned";
|
|
109
|
-
group_title_width -= group_position.length;
|
|
110
|
-
group_title_width -= left_arrow.length + right_arrow.length;
|
|
111
|
-
group_title_width = Math.min(title.length, group_title_width);
|
|
112
107
|
const items = props.commit_range.commit_list.map((commit) => {
|
|
113
108
|
const commit_metadata_id = commit_map.get(commit.sha);
|
|
114
109
|
const selected = commit_metadata_id !== null;
|
|
115
110
|
let disabled;
|
|
116
|
-
if (
|
|
111
|
+
if (group.id === props.commit_range.UNASSIGNED) {
|
|
117
112
|
disabled = true;
|
|
118
113
|
}
|
|
119
114
|
else {
|
|
@@ -127,11 +122,25 @@ function SelectCommitRangesInternal(props) {
|
|
|
127
122
|
};
|
|
128
123
|
});
|
|
129
124
|
items.reverse();
|
|
130
|
-
//
|
|
125
|
+
// <- (2/4) #742 Title A ->
|
|
126
|
+
const left_arrow = `${SYMBOL.left} `;
|
|
127
|
+
const right_arrow = ` ${SYMBOL.right}`;
|
|
128
|
+
const group_position = `(${current_index + 1}/${group_list.length}) `;
|
|
129
|
+
const max_group_label_width = 80;
|
|
130
|
+
let group_title_width = max_group_label_width;
|
|
131
|
+
group_title_width -= group_position.length;
|
|
132
|
+
group_title_width -= left_arrow.length + right_arrow.length;
|
|
133
|
+
group_title_width = Math.min(group.title.length, group_title_width);
|
|
134
|
+
let max_item_width = max_group_label_width;
|
|
135
|
+
max_item_width -= left_arrow.length + right_arrow.length;
|
|
131
136
|
return (React.createElement(Ink.Box, { flexDirection: "column" },
|
|
132
137
|
React.createElement(Ink.Box, { height: 1 }),
|
|
133
|
-
React.createElement(MultiSelect, { key:
|
|
138
|
+
React.createElement(MultiSelect, { key: group.id, items: items, maxWidth: max_item_width, onSelect: (args) => {
|
|
134
139
|
// console.debug("onSelect", args);
|
|
140
|
+
if (group_input) {
|
|
141
|
+
// console.debug("group_input is true, ignoring onSelect");
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
135
144
|
const key = args.item.sha;
|
|
136
145
|
let value;
|
|
137
146
|
if (args.selected) {
|
|
@@ -147,48 +156,52 @@ function SelectCommitRangesInternal(props) {
|
|
|
147
156
|
React.createElement(Ink.Text, null, left_arrow),
|
|
148
157
|
React.createElement(Ink.Text, null, group_position),
|
|
149
158
|
React.createElement(Ink.Box, { width: group_title_width, justifyContent: "center" },
|
|
150
|
-
React.createElement(Ink.Text, { wrap: "truncate-end" }, title)),
|
|
159
|
+
React.createElement(Ink.Text, { wrap: "truncate-end" }, group.title)),
|
|
151
160
|
React.createElement(Ink.Text, null, right_arrow)),
|
|
152
161
|
React.createElement(Ink.Box, { height: 1 }),
|
|
153
|
-
unassigned_count > 0 ? (React.createElement(Ink.Text, { color: "gray" },
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
React.createElement(Ink.Text, null, ", press "),
|
|
158
|
-
React.createElement(Ink.Text, { bold: true, color: "#22c55e" }, "c"),
|
|
159
|
-
" to ",
|
|
160
|
-
React.createElement(Ink.Text, { bold: true, color: "#22c55e" },
|
|
162
|
+
unassigned_count > 0 ? (React.createElement(FormatText, { wrapper: React.createElement(Ink.Text, { color: "gray" }), message: "{count} unassigned commits, press {c} to {create} a new group", values: {
|
|
163
|
+
count: (React.createElement(Ink.Text, { color: "#3b82f6", bold: true }, unassigned_count)),
|
|
164
|
+
c: (React.createElement(Ink.Text, { bold: true, color: "#22c55e" }, "c")),
|
|
165
|
+
create: (React.createElement(Ink.Text, { bold: true, color: "#22c55e" },
|
|
161
166
|
React.createElement(Parens, null, "c"),
|
|
162
|
-
"reate"),
|
|
163
|
-
|
|
164
|
-
React.createElement(Ink.Text, null,
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
React.createElement(Ink.Text, { color: "gray" },
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
167
|
+
"reate")),
|
|
168
|
+
} })) : (React.createElement(React.Fragment, null,
|
|
169
|
+
React.createElement(FormatText, { wrapper: React.createElement(Ink.Text, null), message: "\uD83C\uDF89 Done! Press {s} to {sync} the commits to Github", values: {
|
|
170
|
+
s: (React.createElement(Ink.Text, { bold: true, color: "#22c55e" }, "s")),
|
|
171
|
+
sync: (React.createElement(Ink.Text, { bold: true, color: "#22c55e" },
|
|
172
|
+
React.createElement(Parens, null, "s"),
|
|
173
|
+
"ync")),
|
|
174
|
+
} }))),
|
|
175
|
+
!group_input ? null : (React.createElement(React.Fragment, null,
|
|
176
|
+
React.createElement(Ink.Box, { height: 1 }),
|
|
177
|
+
React.createElement(FormatText, { wrapper: React.createElement(Ink.Text, { color: "gray" }), message: "Enter a title for the PR {note}", values: {
|
|
178
|
+
note: (React.createElement(Parens, null,
|
|
179
|
+
React.createElement(FormatText, { message: "press {enter} to submit", values: {
|
|
180
|
+
enter: (React.createElement(Ink.Text, { bold: true, color: "#22c55e" }, SYMBOL.enter)),
|
|
181
|
+
} }))),
|
|
182
|
+
} }),
|
|
183
|
+
React.createElement(TextInput, { onSubmit: submit_group_input }),
|
|
184
|
+
React.createElement(Ink.Box, { height: 1 }))),
|
|
180
185
|
React.createElement(Ink.Box, null,
|
|
181
|
-
React.createElement(Ink.Text, { color: "gray" },
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
React.createElement(Ink.Text, { bold: true, color: "#22c55e" }, SYMBOL.right),
|
|
186
|
-
" to view PR groups")),
|
|
186
|
+
React.createElement(FormatText, { wrapper: React.createElement(Ink.Text, { color: "gray" }), message: "Press {left} and {right} to view PR groups", values: {
|
|
187
|
+
left: (React.createElement(Ink.Text, { bold: true, color: "#22c55e" }, SYMBOL.left)),
|
|
188
|
+
right: (React.createElement(Ink.Text, { bold: true, color: "#22c55e" }, SYMBOL.right)),
|
|
189
|
+
} })),
|
|
187
190
|
React.createElement(Ink.Box, null,
|
|
188
|
-
React.createElement(Ink.Text, { color: "gray" },
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
191
|
+
React.createElement(FormatText, { wrapper: React.createElement(Ink.Text, { color: "gray" }), message: "Press {enter} to toggle commit selection", values: {
|
|
192
|
+
enter: (React.createElement(Ink.Text, { bold: true, color: "#22c55e" }, SYMBOL.enter)),
|
|
193
|
+
} }))));
|
|
194
|
+
function submit_group_input(title) {
|
|
195
|
+
const id = short_id();
|
|
196
|
+
actions.output(React.createElement(FormatText, { wrapper: React.createElement(Ink.Text, { dimColor: true }), message: "Created new group {group} {note}", values: {
|
|
197
|
+
group: React.createElement(Brackets, null, title),
|
|
198
|
+
note: React.createElement(Parens, null, id),
|
|
199
|
+
} }));
|
|
200
|
+
// console.debug("submit_group_input", { title, id });
|
|
201
|
+
create_group({ id, title });
|
|
202
|
+
set_selected_group_id(id);
|
|
203
|
+
set_group_input(false);
|
|
204
|
+
}
|
|
192
205
|
}
|
|
193
206
|
const SYMBOL = {
|
|
194
207
|
left: "←",
|