git-stack-cli 0.2.1 → 0.3.1
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 +6 -2
- package/dist/__fixtures__/metadata.js +2 -0
- package/dist/app/App copy.js +30 -0
- package/dist/app/App.js +2 -1
- package/dist/app/Brackets copy.js +10 -0
- package/dist/app/Brackets.js +1 -1
- package/dist/app/Command.js +6 -0
- package/dist/app/Debug.js +7 -6
- package/dist/app/DependencyCheck.js +42 -25
- package/dist/app/FormatText.js +7 -0
- package/dist/app/GatherMetadata.js +16 -3
- package/dist/app/ManualRebase.js +20 -18
- package/dist/app/Parens.js +1 -1
- package/dist/app/Providers.js +5 -0
- package/dist/app/SelectCommitRanges.js +36 -23
- package/dist/app/StatusTable.js +16 -5
- package/dist/app/Store.js +2 -0
- package/dist/app/Url copy.js +6 -0
- package/dist/app/Url.js +6 -0
- package/dist/app/YesNoPrompt.js +20 -4
- package/dist/app/main.js +2 -0
- package/dist/command.js +46 -6
- package/dist/core/CommitMetadata.js +12 -12
- package/dist/core/cli.js +2 -1
- package/dist/core/env.js +4 -0
- package/dist/core/github.js +58 -11
- package/dist/core/is_dev.js +1 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# git-stack-cli
|
|
2
2
|
|
|
3
|
+
> create stacked diffs (PRs) on Github from a single branch by grouping commits
|
|
4
|
+
|
|
3
5
|
## install
|
|
4
6
|
|
|
5
7
|
```bash
|
|
@@ -15,13 +17,15 @@ git multi-diff
|
|
|
15
17
|
npm run dev
|
|
16
18
|
npm link
|
|
17
19
|
|
|
18
|
-
git stack
|
|
19
|
-
git multi-diff
|
|
20
|
+
git stack --debug
|
|
21
|
+
git multi-diff --debug
|
|
20
22
|
```
|
|
21
23
|
|
|
22
24
|
|
|
23
25
|
## TODO
|
|
24
26
|
|
|
27
|
+
- point value for querying all pr for user vs individual pr status (current)?
|
|
28
|
+
|
|
25
29
|
- select commit ranges
|
|
26
30
|
- capture PR title when creating new group
|
|
27
31
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// prettier-ignore
|
|
2
2
|
export const METADATA = {
|
|
3
|
+
"username": "magus",
|
|
4
|
+
"repo_path": "magus/git-multi-diff-playground",
|
|
3
5
|
"head": "1a50c5fe3cd129547c5c34a54d1611ec06ab213e",
|
|
4
6
|
"merge_base": "9528176b12abf81c779bc5244afc7d760f6fa422",
|
|
5
7
|
"branch_name": "dev/noah/a-test",
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Debug } from "./Debug.js";
|
|
3
|
+
import { DependencyCheck } from "./DependencyCheck.js";
|
|
4
|
+
import { GatherMetadata } from "./GatherMetadata.js";
|
|
5
|
+
import { GithubApiError } from "./GithubApiError.js";
|
|
6
|
+
import { Main } from "./Main.js";
|
|
7
|
+
import { Output } from "./Output.js";
|
|
8
|
+
import { Store } from "./Store.js";
|
|
9
|
+
export function App() {
|
|
10
|
+
const ink = Store.useState((state) => state.ink);
|
|
11
|
+
const argv = Store.useState((state) => state.argv);
|
|
12
|
+
if (!ink || !argv) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
// // debug component
|
|
16
|
+
// return (
|
|
17
|
+
// <React.Fragment>
|
|
18
|
+
// <Debug />
|
|
19
|
+
// <Output />
|
|
20
|
+
// <GithubApiError />
|
|
21
|
+
// </React.Fragment>
|
|
22
|
+
// );
|
|
23
|
+
return (React.createElement(Providers, null,
|
|
24
|
+
React.createElement(Debug, null),
|
|
25
|
+
React.createElement(Output, null),
|
|
26
|
+
!argv.debug ? null : React.createElement(GithubApiError, null),
|
|
27
|
+
React.createElement(DependencyCheck, null,
|
|
28
|
+
React.createElement(GatherMetadata, null,
|
|
29
|
+
React.createElement(Main, null)))));
|
|
30
|
+
}
|
package/dist/app/App.js
CHANGED
|
@@ -5,6 +5,7 @@ import { GatherMetadata } from "./GatherMetadata.js";
|
|
|
5
5
|
import { GithubApiError } from "./GithubApiError.js";
|
|
6
6
|
import { Main } from "./Main.js";
|
|
7
7
|
import { Output } from "./Output.js";
|
|
8
|
+
import { Providers } from "./Providers.js";
|
|
8
9
|
import { Store } from "./Store.js";
|
|
9
10
|
export function App() {
|
|
10
11
|
const ink = Store.useState((state) => state.ink);
|
|
@@ -20,7 +21,7 @@ export function App() {
|
|
|
20
21
|
// <GithubApiError />
|
|
21
22
|
// </React.Fragment>
|
|
22
23
|
// );
|
|
23
|
-
return (React.createElement(
|
|
24
|
+
return (React.createElement(Providers, null,
|
|
24
25
|
React.createElement(Debug, null),
|
|
25
26
|
React.createElement(Output, null),
|
|
26
27
|
!argv.debug ? null : React.createElement(GithubApiError, null),
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import * as Ink from "ink";
|
|
3
|
+
export function Brackets(props) {
|
|
4
|
+
const color = "#f97316";
|
|
5
|
+
const text_color = "#06b6d4";
|
|
6
|
+
return (React.createElement(Ink.Text, { color: text_color },
|
|
7
|
+
React.createElement(Ink.Text, { color: color }, "["),
|
|
8
|
+
props.children,
|
|
9
|
+
React.createElement(Ink.Text, { color: color }, "]")));
|
|
10
|
+
}
|
package/dist/app/Brackets.js
CHANGED
|
@@ -2,7 +2,7 @@ import * as React from "react";
|
|
|
2
2
|
import * as Ink from "ink";
|
|
3
3
|
export function Brackets(props) {
|
|
4
4
|
const color = "#f97316";
|
|
5
|
-
const text_color = "#
|
|
5
|
+
const text_color = "#38bdf8";
|
|
6
6
|
return (React.createElement(Ink.Text, { color: text_color },
|
|
7
7
|
React.createElement(Ink.Text, { color: color }, "["),
|
|
8
8
|
props.children,
|
package/dist/app/Debug.js
CHANGED
|
@@ -8,15 +8,16 @@ import { Store } from "./Store.js";
|
|
|
8
8
|
export function Debug() {
|
|
9
9
|
const actions = Store.useActions();
|
|
10
10
|
const state = Store.useState((state) => state);
|
|
11
|
-
const
|
|
11
|
+
const argv = Store.useState((state) => state.argv);
|
|
12
12
|
React.useEffect(function debugMessageOnce() {
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
actions.debug(React.createElement(Ink.Text, { color: "yellow" }, "Debug mode enabled"));
|
|
14
|
+
if (argv?.verbose) {
|
|
15
|
+
actions.debug(React.createElement(Ink.Text, { dimColor: true }, JSON.stringify(argv, null, 2)));
|
|
15
16
|
}
|
|
16
|
-
}, [
|
|
17
|
+
}, [argv]);
|
|
17
18
|
React.useEffect(function syncStateJson() {
|
|
18
19
|
invariant(state.cwd, "state.cwd must exist");
|
|
19
|
-
if (!
|
|
20
|
+
if (!argv?.["write-state-json"]) {
|
|
20
21
|
return;
|
|
21
22
|
}
|
|
22
23
|
const output_file = path.join(state.cwd, "git-multi-diff-state.json");
|
|
@@ -26,6 +27,6 @@ export function Debug() {
|
|
|
26
27
|
const serialized = json.serialize(state);
|
|
27
28
|
const content = JSON.stringify(serialized, null, 2);
|
|
28
29
|
fs.writeFileSync(output_file, content);
|
|
29
|
-
}, [
|
|
30
|
+
}, [argv, state]);
|
|
30
31
|
return null;
|
|
31
32
|
}
|
|
@@ -2,51 +2,68 @@ import * as React from "react";
|
|
|
2
2
|
import * as Ink from "ink";
|
|
3
3
|
import { cli } from "../core/cli.js";
|
|
4
4
|
import { is_command_available } from "../core/is_command_available.js";
|
|
5
|
+
import { match_group } from "../core/match_group.js";
|
|
5
6
|
import { Await } from "./Await.js";
|
|
7
|
+
import { Command } from "./Command.js";
|
|
8
|
+
import { Parens } from "./Parens.js";
|
|
6
9
|
import { Store } from "./Store.js";
|
|
10
|
+
import { Url } from "./Url.js";
|
|
7
11
|
export function DependencyCheck(props) {
|
|
8
12
|
const actions = Store.useActions();
|
|
9
|
-
return (React.createElement(Await, { fallback: React.createElement(Ink.
|
|
10
|
-
|
|
13
|
+
return (React.createElement(Await, { fallback: React.createElement(Ink.Text, { color: "yellow" },
|
|
14
|
+
"Checking ",
|
|
15
|
+
React.createElement(Command, null, "git"),
|
|
16
|
+
" install..."), function: async () => {
|
|
11
17
|
if (is_command_available("git")) {
|
|
12
18
|
return;
|
|
13
19
|
}
|
|
14
|
-
actions.output(React.createElement(Ink.Text,
|
|
15
|
-
React.createElement(
|
|
20
|
+
actions.output(React.createElement(Ink.Text, { color: "yellow" },
|
|
21
|
+
React.createElement(Command, null, "git"),
|
|
16
22
|
" must be installed."));
|
|
17
23
|
actions.exit(2);
|
|
18
24
|
} },
|
|
19
|
-
React.createElement(Await, { fallback: React.createElement(Ink.
|
|
20
|
-
React.createElement(Ink.Text, null,
|
|
25
|
+
React.createElement(Await, { fallback: React.createElement(Ink.Text, { color: "yellow" },
|
|
26
|
+
React.createElement(Ink.Text, null,
|
|
27
|
+
"Checking ",
|
|
28
|
+
React.createElement(Command, null, "gh"),
|
|
29
|
+
" install...")), function: async () => {
|
|
21
30
|
if (is_command_available("gh")) {
|
|
22
31
|
return;
|
|
23
32
|
}
|
|
24
|
-
actions.output(React.createElement(Ink.Text,
|
|
25
|
-
React.createElement(
|
|
33
|
+
actions.output(React.createElement(Ink.Text, { color: "yellow" },
|
|
34
|
+
React.createElement(Command, null, "gh"),
|
|
26
35
|
" must be installed."));
|
|
27
|
-
actions.output(React.createElement(Ink.
|
|
28
|
-
React.createElement(Ink.Text, null, "Visit"),
|
|
29
|
-
React.createElement(
|
|
30
|
-
React.createElement(Ink.Text, null, "to install the github cli"),
|
|
31
|
-
React.createElement(
|
|
32
|
-
|
|
33
|
-
React.createElement(Ink.Text, { color: "yellow" }, "gh"),
|
|
34
|
-
")")));
|
|
36
|
+
actions.output(React.createElement(Ink.Text, { color: "yellow" },
|
|
37
|
+
React.createElement(Ink.Text, null, "Visit "),
|
|
38
|
+
React.createElement(Url, null, "https://cli.github.com"),
|
|
39
|
+
React.createElement(Ink.Text, null, " to install the github cli "),
|
|
40
|
+
React.createElement(Parens, null,
|
|
41
|
+
React.createElement(Command, null, "gh"))));
|
|
35
42
|
actions.exit(3);
|
|
36
43
|
} },
|
|
37
|
-
React.createElement(Await, { fallback: React.createElement(Ink.
|
|
38
|
-
React.createElement(Ink.Text, null,
|
|
39
|
-
|
|
44
|
+
React.createElement(Await, { fallback: React.createElement(Ink.Text, { color: "yellow" },
|
|
45
|
+
React.createElement(Ink.Text, null,
|
|
46
|
+
"Checking ",
|
|
47
|
+
React.createElement(Command, null, "gh auth status"),
|
|
48
|
+
"...")), function: async () => {
|
|
49
|
+
const auth_output = await cli(`gh auth status`, {
|
|
40
50
|
ignoreExitCode: true,
|
|
41
51
|
});
|
|
42
|
-
if (
|
|
52
|
+
if (auth_output.code === 0) {
|
|
53
|
+
const username = match_group(auth_output.stdout, RE.auth_username, "username");
|
|
54
|
+
actions.set((state) => {
|
|
55
|
+
state.username = username;
|
|
56
|
+
});
|
|
43
57
|
return;
|
|
44
58
|
}
|
|
45
|
-
actions.output(React.createElement(Ink.
|
|
46
|
-
React.createElement(
|
|
47
|
-
React.createElement(Ink.Text, null, "requires login, please run"),
|
|
48
|
-
React.createElement(
|
|
49
|
-
React.createElement(Ink.Text, { color: "yellow" }, "gh auth login"))));
|
|
59
|
+
actions.output(React.createElement(Ink.Text, { color: "yellow" },
|
|
60
|
+
React.createElement(Command, null, "gh"),
|
|
61
|
+
React.createElement(Ink.Text, null, " requires login, please run "),
|
|
62
|
+
React.createElement(Command, null, "gh auth login")));
|
|
50
63
|
actions.exit(4);
|
|
51
64
|
} }, props.children))));
|
|
52
65
|
}
|
|
66
|
+
const RE = {
|
|
67
|
+
// Logged in to github.com as magus
|
|
68
|
+
auth_username: /Logged in to github.com as (?<username>[^\s]+)/,
|
|
69
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { FormattedMessage } from "react-intl";
|
|
3
|
+
export function FormatText(props) {
|
|
4
|
+
return (React.createElement(FormattedMessage, { id: "FormatText", defaultMessage: props.message, values: props.values }, (chunks) => {
|
|
5
|
+
return React.cloneElement(props.wrapper, {}, chunks);
|
|
6
|
+
}));
|
|
7
|
+
}
|
|
@@ -4,6 +4,7 @@ import * as CommitMetadata from "../core/CommitMetadata.js";
|
|
|
4
4
|
import { cli } from "../core/cli.js";
|
|
5
5
|
import { invariant } from "../core/invariant.js";
|
|
6
6
|
import * as json from "../core/json.js";
|
|
7
|
+
import { match_group } from "../core/match_group.js";
|
|
7
8
|
import { Await } from "./Await.js";
|
|
8
9
|
import { Store } from "./Store.js";
|
|
9
10
|
export function GatherMetadata(props) {
|
|
@@ -34,13 +35,20 @@ async function gather_metadata() {
|
|
|
34
35
|
actions.exit(0);
|
|
35
36
|
return;
|
|
36
37
|
}
|
|
38
|
+
// git@github.com:magus/git-multi-diff-playground.git
|
|
39
|
+
// https://github.com/magus/git-multi-diff-playground.git
|
|
40
|
+
const origin_url = (await cli(`git config --get remote.origin.url`)).stdout;
|
|
41
|
+
const repo_path = match_group(origin_url, RE.repo_path, "repo_path");
|
|
37
42
|
const branch_name = (await cli("git rev-parse --abbrev-ref HEAD")).stdout;
|
|
43
|
+
Store.setState((state) => {
|
|
44
|
+
state.repo_path = repo_path;
|
|
45
|
+
state.head = head;
|
|
46
|
+
state.merge_base = merge_base;
|
|
47
|
+
state.branch_name = branch_name;
|
|
48
|
+
});
|
|
38
49
|
try {
|
|
39
50
|
const commit_range = await CommitMetadata.range();
|
|
40
51
|
Store.setState((state) => {
|
|
41
|
-
state.head = head;
|
|
42
|
-
state.merge_base = merge_base;
|
|
43
|
-
state.branch_name = branch_name;
|
|
44
52
|
state.commit_range = commit_range;
|
|
45
53
|
state.step = "status";
|
|
46
54
|
});
|
|
@@ -52,3 +60,8 @@ async function gather_metadata() {
|
|
|
52
60
|
}
|
|
53
61
|
}
|
|
54
62
|
}
|
|
63
|
+
const RE = {
|
|
64
|
+
// git@github.com:magus/git-multi-diff-playground.git
|
|
65
|
+
// https://github.com/magus/git-multi-diff-playground.git
|
|
66
|
+
repo_path: /(?<repo_path>[^:^/]+\/[^/]+)\.git/,
|
|
67
|
+
};
|
package/dist/app/ManualRebase.js
CHANGED
|
@@ -9,10 +9,10 @@ import { invariant } from "../core/invariant.js";
|
|
|
9
9
|
import { Await } from "./Await.js";
|
|
10
10
|
import { Brackets } from "./Brackets.js";
|
|
11
11
|
import { Store } from "./Store.js";
|
|
12
|
-
export function ManualRebase() {
|
|
13
|
-
return (React.createElement(Await, { fallback: React.createElement(Ink.Text, { color: "yellow" }, "Rebasing commits..."), function: run }));
|
|
12
|
+
export function ManualRebase(props) {
|
|
13
|
+
return (React.createElement(Await, { fallback: React.createElement(Ink.Text, { color: "yellow" }, "Rebasing commits..."), function: () => run(props) }));
|
|
14
14
|
}
|
|
15
|
-
async function run() {
|
|
15
|
+
async function run(props) {
|
|
16
16
|
const state = Store.getState();
|
|
17
17
|
const actions = state.actions;
|
|
18
18
|
const branch_name = state.branch_name;
|
|
@@ -61,21 +61,23 @@ async function run() {
|
|
|
61
61
|
"Syncing ",
|
|
62
62
|
React.createElement(Brackets, null, group.pr?.title || group.id),
|
|
63
63
|
"..."));
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
64
|
+
if (!props.skipSync) {
|
|
65
|
+
// push to origin since github requires commit shas to line up perfectly
|
|
66
|
+
await cli(`git push -f origin HEAD:${group.id}`);
|
|
67
|
+
if (group.pr) {
|
|
68
|
+
// ensure base matches pr in github
|
|
69
|
+
await github.pr_base(group.id, group.base);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
// delete local group branch if leftover
|
|
73
|
+
await cli(`git branch -D ${group.id}`, { ignoreExitCode: true });
|
|
74
|
+
// move to temporary branch for creating pr
|
|
75
|
+
await cli(`git checkout -b ${group.id}`);
|
|
76
|
+
// create pr in github
|
|
77
|
+
await github.pr_create(group.id, group.base);
|
|
78
|
+
// move back to temp branch
|
|
79
|
+
await cli(`git checkout ${temp_branch_name}`);
|
|
80
|
+
}
|
|
79
81
|
}
|
|
80
82
|
}
|
|
81
83
|
// after all commits have been cherry-picked and amended
|
package/dist/app/Parens.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,
|
|
@@ -3,6 +3,7 @@ import * as Ink from "ink";
|
|
|
3
3
|
import { v4 as uuid_v4 } from "uuid";
|
|
4
4
|
import { invariant } from "../core/invariant.js";
|
|
5
5
|
import { wrap_index } from "../core/wrap_index.js";
|
|
6
|
+
import { FormatText } from "./FormatText.js";
|
|
6
7
|
import { MultiSelect } from "./MultiSelect.js";
|
|
7
8
|
import { Parens } from "./Parens.js";
|
|
8
9
|
import { Store } from "./Store.js";
|
|
@@ -47,10 +48,10 @@ function SelectCommitRangesInternal(props) {
|
|
|
47
48
|
}
|
|
48
49
|
group_list.push(...new_group_list);
|
|
49
50
|
for (const group of props.commit_range.group_list) {
|
|
50
|
-
if (group.
|
|
51
|
+
if (group.id !== props.commit_range.UNASSIGNED) {
|
|
51
52
|
group_list.push({
|
|
52
53
|
id: group.id,
|
|
53
|
-
title: group.pr.
|
|
54
|
+
title: group.pr?.title || group.id,
|
|
54
55
|
});
|
|
55
56
|
}
|
|
56
57
|
}
|
|
@@ -59,7 +60,8 @@ function SelectCommitRangesInternal(props) {
|
|
|
59
60
|
const current_index = group_list.findIndex((g) => g.id === selected_group_id);
|
|
60
61
|
Ink.useInput((input, key) => {
|
|
61
62
|
const inputLower = input.toLowerCase();
|
|
62
|
-
|
|
63
|
+
const hasUnassignedCommits = unassigned_count > 0;
|
|
64
|
+
if (!hasUnassignedCommits && (inputLower === "r" || inputLower === "s")) {
|
|
63
65
|
actions.set((state) => {
|
|
64
66
|
state.commit_map = {};
|
|
65
67
|
for (const [sha, id] of commit_map.entries()) {
|
|
@@ -67,12 +69,18 @@ function SelectCommitRangesInternal(props) {
|
|
|
67
69
|
state.commit_map[sha] = id;
|
|
68
70
|
}
|
|
69
71
|
}
|
|
70
|
-
|
|
72
|
+
switch (inputLower) {
|
|
73
|
+
case "s":
|
|
74
|
+
state.step = "manual-rebase";
|
|
75
|
+
break;
|
|
76
|
+
case "r":
|
|
77
|
+
state.step = "manual-rebase-no-sync";
|
|
78
|
+
}
|
|
71
79
|
});
|
|
72
80
|
return;
|
|
73
81
|
}
|
|
74
82
|
// only allow create when on unassigned group
|
|
75
|
-
if (
|
|
83
|
+
if (hasUnassignedCommits && inputLower === "c") {
|
|
76
84
|
const id = uuid_v4();
|
|
77
85
|
actions.output(React.createElement(Ink.Box, null,
|
|
78
86
|
React.createElement(Ink.Text, { dimColor: true }, "Created new group "),
|
|
@@ -121,10 +129,10 @@ function SelectCommitRangesInternal(props) {
|
|
|
121
129
|
};
|
|
122
130
|
});
|
|
123
131
|
items.reverse();
|
|
124
|
-
// console.debug({ group, isUnassigned });
|
|
132
|
+
// console.debug({ current_index, group, isUnassigned });
|
|
125
133
|
return (React.createElement(Ink.Box, { flexDirection: "column" },
|
|
126
134
|
React.createElement(Ink.Box, { height: 1 }),
|
|
127
|
-
React.createElement(MultiSelect, { key:
|
|
135
|
+
React.createElement(MultiSelect, { key: group.id, items: items, onSelect: (args) => {
|
|
128
136
|
// console.debug("onSelect", args);
|
|
129
137
|
const key = args.item.sha;
|
|
130
138
|
let value;
|
|
@@ -144,24 +152,29 @@ function SelectCommitRangesInternal(props) {
|
|
|
144
152
|
React.createElement(Ink.Text, { wrap: "truncate-end" }, title)),
|
|
145
153
|
React.createElement(Ink.Text, null, right_arrow)),
|
|
146
154
|
React.createElement(Ink.Box, { height: 1 }),
|
|
147
|
-
unassigned_count > 0 ? (React.createElement(Ink.Text, { color: "gray" },
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
155
|
+
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: {
|
|
156
|
+
count: (React.createElement(Ink.Text, { color: "#3b82f6", bold: true }, unassigned_count)),
|
|
157
|
+
c: (React.createElement(Ink.Text, { bold: true, color: "#22c55e" }, "c")),
|
|
158
|
+
create: (React.createElement(Ink.Text, { bold: true, color: "#22c55e" },
|
|
159
|
+
React.createElement(Parens, null, "c"),
|
|
160
|
+
"reate")),
|
|
161
|
+
} })) : (React.createElement(React.Fragment, null,
|
|
162
|
+
React.createElement(Ink.Text, null,
|
|
163
|
+
"🎉 Done! Press ",
|
|
164
|
+
React.createElement(Ink.Text, { bold: true, color: "#22c55e" }, "s"),
|
|
153
165
|
" to ",
|
|
154
166
|
React.createElement(Ink.Text, { bold: true, color: "#22c55e" },
|
|
155
|
-
React.createElement(Parens, null, "
|
|
156
|
-
"
|
|
157
|
-
"
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
React.createElement(
|
|
163
|
-
|
|
164
|
-
|
|
167
|
+
React.createElement(Parens, null, "s"),
|
|
168
|
+
"ync"),
|
|
169
|
+
" the commits to Github"),
|
|
170
|
+
React.createElement(Ink.Text, { color: "gray" },
|
|
171
|
+
React.createElement(Ink.Text, null, "Press "),
|
|
172
|
+
React.createElement(Ink.Text, { bold: true, color: "#22c55e" }, "r"),
|
|
173
|
+
" to locally ",
|
|
174
|
+
React.createElement(Ink.Text, { bold: true, color: "#22c55e" },
|
|
175
|
+
React.createElement(Parens, null, "r"),
|
|
176
|
+
"ebase"),
|
|
177
|
+
" only"))),
|
|
165
178
|
React.createElement(Ink.Box, null,
|
|
166
179
|
React.createElement(Ink.Text, { color: "gray" },
|
|
167
180
|
React.createElement(Ink.Text, null, "Press "),
|
package/dist/app/StatusTable.js
CHANGED
|
@@ -14,8 +14,9 @@ export function StatusTable() {
|
|
|
14
14
|
status: "",
|
|
15
15
|
title: "",
|
|
16
16
|
url: "",
|
|
17
|
+
id: group.id,
|
|
17
18
|
};
|
|
18
|
-
if (
|
|
19
|
+
if (group.id === commit_range.UNASSIGNED) {
|
|
19
20
|
row.icon = "⭑";
|
|
20
21
|
row.status = "NEW";
|
|
21
22
|
row.title = "Unassigned";
|
|
@@ -31,9 +32,19 @@ export function StatusTable() {
|
|
|
31
32
|
row.icon = "✔";
|
|
32
33
|
row.status = "SYNCED";
|
|
33
34
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
if (group.pr) {
|
|
36
|
+
if (group.pr.state === "MERGED") {
|
|
37
|
+
row.icon = "↗";
|
|
38
|
+
row.status = "MERGED";
|
|
39
|
+
}
|
|
40
|
+
row.title = group.pr.title;
|
|
41
|
+
row.count = `${group.pr.commits.length}/${group.commits.length}`;
|
|
42
|
+
row.url = group.pr.url;
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
row.title = group.id;
|
|
46
|
+
row.count = `0/${group.commits.length}`;
|
|
47
|
+
}
|
|
37
48
|
}
|
|
38
49
|
row_list.push(row);
|
|
39
50
|
}
|
|
@@ -75,7 +86,7 @@ export function StatusTable() {
|
|
|
75
86
|
return (React.createElement(Ink.Box, { flexDirection: "column", width: available_width },
|
|
76
87
|
React.createElement(Ink.Box, { height: 1 }),
|
|
77
88
|
row_list.map((row) => {
|
|
78
|
-
return (React.createElement(Ink.Box, { key: row.
|
|
89
|
+
return (React.createElement(Ink.Box, { key: row.id,
|
|
79
90
|
// borderStyle="round"
|
|
80
91
|
flexDirection: "row", columnGap: columnGap, width: available_width },
|
|
81
92
|
React.createElement(Ink.Box, { width: max_col_width.icon },
|
package/dist/app/Store.js
CHANGED
package/dist/app/Url.js
ADDED
package/dist/app/YesNoPrompt.js
CHANGED
|
@@ -2,8 +2,10 @@ import * as React from "react";
|
|
|
2
2
|
import * as Ink from "ink";
|
|
3
3
|
import { Parens } from "./Parens.js";
|
|
4
4
|
export function YesNoPrompt(props) {
|
|
5
|
+
const [answer, set_answer] = React.useState("");
|
|
5
6
|
Ink.useInput((input) => {
|
|
6
7
|
const inputLower = input.toLowerCase();
|
|
8
|
+
set_answer(inputLower);
|
|
7
9
|
switch (inputLower) {
|
|
8
10
|
case "n":
|
|
9
11
|
return props.onNo();
|
|
@@ -11,13 +13,27 @@ export function YesNoPrompt(props) {
|
|
|
11
13
|
return props.onYes();
|
|
12
14
|
}
|
|
13
15
|
});
|
|
16
|
+
// prettier-ignore
|
|
17
|
+
const y = React.createElement(Ink.Text, { bold: true, color: "#22c55e" }, "Y");
|
|
18
|
+
const n = React.createElement(Ink.Text, { color: "#ef4444" }, "n");
|
|
19
|
+
let choices;
|
|
20
|
+
switch (answer) {
|
|
21
|
+
case "y":
|
|
22
|
+
choices = y;
|
|
23
|
+
break;
|
|
24
|
+
case "n":
|
|
25
|
+
choices = n;
|
|
26
|
+
break;
|
|
27
|
+
default:
|
|
28
|
+
choices = (React.createElement(React.Fragment, null,
|
|
29
|
+
y,
|
|
30
|
+
React.createElement(Ink.Text, null, "/"),
|
|
31
|
+
n));
|
|
32
|
+
}
|
|
14
33
|
return (React.createElement(Ink.Box, { flexDirection: "column" },
|
|
15
34
|
React.createElement(Ink.Box, null,
|
|
16
35
|
React.createElement(Ink.Text, { color: "yellow" }, props.message),
|
|
17
36
|
React.createElement(Ink.Text, null, " "),
|
|
18
37
|
React.createElement(Parens, null,
|
|
19
|
-
React.createElement(Ink.Text, { color: "gray" },
|
|
20
|
-
React.createElement(Ink.Text, { bold: true, color: "#22c55e" }, "Y"),
|
|
21
|
-
"/",
|
|
22
|
-
React.createElement(Ink.Text, { color: "#ef4444" }, "n"))))));
|
|
38
|
+
React.createElement(Ink.Text, { color: "gray" }, choices)))));
|
|
23
39
|
}
|
package/dist/app/main.js
CHANGED
|
@@ -22,6 +22,8 @@ export function Main() {
|
|
|
22
22
|
return React.createElement(SelectCommitRanges, null);
|
|
23
23
|
case "manual-rebase":
|
|
24
24
|
return React.createElement(ManualRebase, null);
|
|
25
|
+
case "manual-rebase-no-sync":
|
|
26
|
+
return React.createElement(ManualRebase, { skipSync: true });
|
|
25
27
|
case "post-rebase-status":
|
|
26
28
|
return React.createElement(PostRebaseStatus, null);
|
|
27
29
|
default:
|
package/dist/command.js
CHANGED
|
@@ -1,22 +1,62 @@
|
|
|
1
1
|
import yargs from "yargs";
|
|
2
2
|
import { hideBin } from "yargs/helpers";
|
|
3
3
|
export async function command() {
|
|
4
|
-
|
|
4
|
+
const debug_argv = await yargs(hideBin(process.argv))
|
|
5
|
+
.option("debug", {
|
|
6
|
+
type: "boolean",
|
|
7
|
+
description: "Enable debug mode with more options for debugging",
|
|
8
|
+
})
|
|
9
|
+
.help(false).argv;
|
|
10
|
+
if (!debug_argv.debug) {
|
|
11
|
+
return NormalMode();
|
|
12
|
+
}
|
|
13
|
+
return DebugMode();
|
|
14
|
+
}
|
|
15
|
+
function NormalMode() {
|
|
16
|
+
return (yargs(hideBin(process.argv))
|
|
17
|
+
.option("force", {
|
|
18
|
+
type: "boolean",
|
|
19
|
+
description: "Force sync even if no changes are detected",
|
|
20
|
+
})
|
|
21
|
+
.option("check", {
|
|
22
|
+
type: "boolean",
|
|
23
|
+
description: "Print status table without syncing",
|
|
24
|
+
})
|
|
25
|
+
.option("debug", {
|
|
26
|
+
type: "boolean",
|
|
27
|
+
description: "Enable debug mode with more options for debugging",
|
|
28
|
+
})
|
|
29
|
+
// disallow unknown options
|
|
30
|
+
.strict()
|
|
31
|
+
.help().argv);
|
|
32
|
+
}
|
|
33
|
+
function DebugMode() {
|
|
34
|
+
return (yargs(hideBin(process.argv))
|
|
5
35
|
.option("force", {
|
|
6
36
|
type: "boolean",
|
|
7
|
-
description: "
|
|
37
|
+
description: "Force sync even if no changes are detected",
|
|
8
38
|
})
|
|
9
39
|
.option("check", {
|
|
10
40
|
type: "boolean",
|
|
11
|
-
description: "
|
|
41
|
+
description: "Print status table without syncing",
|
|
12
42
|
})
|
|
13
43
|
.option("debug", {
|
|
14
44
|
type: "boolean",
|
|
15
|
-
description: "debug",
|
|
45
|
+
description: "Enable debug mode with more options for debugging",
|
|
46
|
+
})
|
|
47
|
+
.option("verbose", {
|
|
48
|
+
type: "boolean",
|
|
49
|
+
description: "Log extra information during execution",
|
|
50
|
+
})
|
|
51
|
+
.option("write-state-json", {
|
|
52
|
+
type: "boolean",
|
|
53
|
+
description: "Write state to local json file for debugging",
|
|
16
54
|
})
|
|
17
55
|
.option("mock-metadata", {
|
|
18
56
|
type: "boolean",
|
|
19
|
-
description: "
|
|
57
|
+
description: "Mock local store metadata for testing",
|
|
20
58
|
})
|
|
21
|
-
|
|
59
|
+
// disallow unknown options
|
|
60
|
+
.strict()
|
|
61
|
+
.help().argv);
|
|
22
62
|
}
|
|
@@ -2,6 +2,9 @@ import * as Metadata from "./Metadata.js";
|
|
|
2
2
|
import { cli } from "./cli.js";
|
|
3
3
|
import * as github from "./github.js";
|
|
4
4
|
export async function range(commit_map) {
|
|
5
|
+
// gather all open prs in repo first
|
|
6
|
+
// cheaper query to populate cache
|
|
7
|
+
await github.pr_list();
|
|
5
8
|
const commit_list = await get_commit_list();
|
|
6
9
|
let invalid = false;
|
|
7
10
|
const group_map = new Map();
|
|
@@ -11,9 +14,8 @@ export async function range(commit_map) {
|
|
|
11
14
|
if (commit_map) {
|
|
12
15
|
id = commit_map[commit.sha];
|
|
13
16
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
// console.debug("INVALID", "MISSING PR", commit.message);
|
|
17
|
+
if (!id) {
|
|
18
|
+
// console.debug("INVALID", "MISSING ID", commit.message);
|
|
17
19
|
invalid = true;
|
|
18
20
|
}
|
|
19
21
|
if (id) {
|
|
@@ -33,7 +35,7 @@ export async function range(commit_map) {
|
|
|
33
35
|
}
|
|
34
36
|
const group = group_map.get(id) || {
|
|
35
37
|
id,
|
|
36
|
-
pr,
|
|
38
|
+
pr: null,
|
|
37
39
|
base: null,
|
|
38
40
|
dirty: false,
|
|
39
41
|
commits: [],
|
|
@@ -47,6 +49,12 @@ export async function range(commit_map) {
|
|
|
47
49
|
let unassigned_group;
|
|
48
50
|
for (let i = 0; i < group_value_list.length; i++) {
|
|
49
51
|
const group = group_value_list[i];
|
|
52
|
+
if (group.id !== UNASSIGNED) {
|
|
53
|
+
const pr_result = await github.pr_status(group.id);
|
|
54
|
+
if (pr_result && pr_result.state !== "CLOSED") {
|
|
55
|
+
group.pr = pr_result;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
50
58
|
// console.debug("group", group.pr?.title.substring(0, 40));
|
|
51
59
|
// console.debug(" ", "id", group.id);
|
|
52
60
|
if (group.id === UNASSIGNED) {
|
|
@@ -117,18 +125,10 @@ export async function commit(sha) {
|
|
|
117
125
|
const raw_message = (await cli(`git show -s --format=%B ${sha}`)).stdout;
|
|
118
126
|
const branch_id = await Metadata.read(raw_message);
|
|
119
127
|
const message = display_message(raw_message);
|
|
120
|
-
let pr = null;
|
|
121
|
-
if (branch_id) {
|
|
122
|
-
const pr_result = await github.pr_status(branch_id);
|
|
123
|
-
if (pr_result && pr_result.state === "OPEN") {
|
|
124
|
-
pr = pr_result;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
128
|
return {
|
|
128
129
|
sha,
|
|
129
130
|
message,
|
|
130
131
|
raw_message,
|
|
131
|
-
pr,
|
|
132
132
|
branch_id,
|
|
133
133
|
};
|
|
134
134
|
}
|
package/dist/core/cli.js
CHANGED
|
@@ -20,6 +20,7 @@ export async function cli(command, unsafe_options) {
|
|
|
20
20
|
}
|
|
21
21
|
else {
|
|
22
22
|
const result = {
|
|
23
|
+
command,
|
|
23
24
|
code: code || 0,
|
|
24
25
|
stdout: stdout.trimEnd(),
|
|
25
26
|
stderr: stderr.trimEnd(),
|
|
@@ -40,5 +41,5 @@ cli.sync = function cli_sync(command, unsafe_options) {
|
|
|
40
41
|
const stderr = String(spawn_return.stderr);
|
|
41
42
|
const output = String(spawn_return.output);
|
|
42
43
|
const code = spawn_return.status || 0;
|
|
43
|
-
return { code, stdout, stderr, output };
|
|
44
|
+
return { command, code, stdout, stderr, output };
|
|
44
45
|
};
|
package/dist/core/env.js
ADDED
package/dist/core/github.js
CHANGED
|
@@ -1,20 +1,46 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import * as Ink from "ink";
|
|
3
|
+
import { Brackets } from "../app/Brackets.js";
|
|
3
4
|
import { Store } from "../app/Store.js";
|
|
4
5
|
import { cli } from "./cli.js";
|
|
5
|
-
|
|
6
|
+
import { invariant } from "./invariant.js";
|
|
7
|
+
// prettier-ignore
|
|
8
|
+
const JSON_FIELDS = "--json number,state,baseRefName,headRefName,commits,title,url";
|
|
9
|
+
export async function pr_list() {
|
|
6
10
|
const state = Store.getState();
|
|
7
11
|
const actions = state.actions;
|
|
8
|
-
const
|
|
12
|
+
const username = state.username;
|
|
13
|
+
const repo_path = state.repo_path;
|
|
14
|
+
invariant(username, "username must exist");
|
|
15
|
+
invariant(repo_path, "repo_path must exist");
|
|
16
|
+
const cli_result = await cli(`gh pr list --repo ${repo_path} --author ${username} --state open ${JSON_FIELDS}`, {
|
|
9
17
|
ignoreExitCode: true,
|
|
10
18
|
});
|
|
11
|
-
if (
|
|
12
|
-
|
|
13
|
-
actions.set((state) => {
|
|
14
|
-
state.step = "github-api-error";
|
|
15
|
-
});
|
|
16
|
-
throw new Error("Unable to fetch PR status");
|
|
19
|
+
if (cli_result.code !== 0) {
|
|
20
|
+
handle_error(cli_result.output);
|
|
17
21
|
}
|
|
22
|
+
const result_pr_list = JSON.parse(cli_result.stdout);
|
|
23
|
+
actions.debug(React.createElement(Ink.Text, { dimColor: true },
|
|
24
|
+
React.createElement(Ink.Text, null, "Github cache "),
|
|
25
|
+
React.createElement(Ink.Text, { bold: true, color: "yellow" }, result_pr_list.length),
|
|
26
|
+
React.createElement(Ink.Text, null, " open PRs from "),
|
|
27
|
+
React.createElement(Brackets, null, repo_path),
|
|
28
|
+
React.createElement(Ink.Text, null, " authored by "),
|
|
29
|
+
React.createElement(Brackets, null, username)));
|
|
30
|
+
actions.set((state) => {
|
|
31
|
+
for (const pr of result_pr_list) {
|
|
32
|
+
state.pr[pr.headRefName] = pr;
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
return result_pr_list;
|
|
36
|
+
}
|
|
37
|
+
export async function pr_status(branch) {
|
|
38
|
+
const state = Store.getState();
|
|
39
|
+
const actions = state.actions;
|
|
40
|
+
const username = state.username;
|
|
41
|
+
const repo_path = state.repo_path;
|
|
42
|
+
invariant(username, "username must exist");
|
|
43
|
+
invariant(repo_path, "repo_path must exist");
|
|
18
44
|
const cache = state.pr[branch];
|
|
19
45
|
if (cache) {
|
|
20
46
|
actions.debug(React.createElement(Ink.Text, null,
|
|
@@ -31,15 +57,36 @@ export async function pr_status(branch) {
|
|
|
31
57
|
React.createElement(Ink.Text, { bold: true, color: "#ef4444" }, "MISS"),
|
|
32
58
|
React.createElement(Ink.Text, null, " "),
|
|
33
59
|
React.createElement(Ink.Text, { dimColor: true }, branch)));
|
|
34
|
-
const
|
|
60
|
+
const cli_result = await cli(`gh pr view ${branch} --repo ${repo_path} ${JSON_FIELDS}`, {
|
|
61
|
+
ignoreExitCode: true,
|
|
62
|
+
});
|
|
63
|
+
if (cli_result.code !== 0) {
|
|
64
|
+
// handle_error(cli_result.output);
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
const pr = JSON.parse(cli_result.stdout);
|
|
35
68
|
actions.set((state) => {
|
|
36
69
|
state.pr[pr.headRefName] = pr;
|
|
37
70
|
});
|
|
38
71
|
return pr;
|
|
39
72
|
}
|
|
40
73
|
export async function pr_create(branch, base) {
|
|
41
|
-
await cli(`gh pr create --fill --head ${branch} --base ${base}`);
|
|
74
|
+
const cli_result = await cli(`gh pr create --fill --head ${branch} --base ${base}`);
|
|
75
|
+
if (cli_result.code !== 0) {
|
|
76
|
+
handle_error(cli_result.output);
|
|
77
|
+
}
|
|
42
78
|
}
|
|
43
79
|
export async function pr_base(branch, base) {
|
|
44
|
-
await cli(`gh pr edit ${branch} --base ${base}`);
|
|
80
|
+
const cli_result = await cli(`gh pr edit ${branch} --base ${base}`);
|
|
81
|
+
if (cli_result.code !== 0) {
|
|
82
|
+
handle_error(cli_result.output);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function handle_error(output) {
|
|
86
|
+
const state = Store.getState();
|
|
87
|
+
const actions = state.actions;
|
|
88
|
+
actions.set((state) => {
|
|
89
|
+
state.step = "github-api-error";
|
|
90
|
+
});
|
|
91
|
+
throw new Error(output);
|
|
45
92
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "git-stack-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"author": "magus",
|
|
6
6
|
"license": "MIT",
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"immer": "^10.0.3",
|
|
30
30
|
"ink": "^4.4.1",
|
|
31
31
|
"react": "^18.2.0",
|
|
32
|
+
"react-intl": "^6.5.5",
|
|
32
33
|
"uuid": "^9.0.1",
|
|
33
34
|
"yargs": "^17.7.2",
|
|
34
35
|
"zustand": "^4.4.4"
|