git-stack-cli 1.14.0 → 1.15.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 +2 -4
- package/dist/cjs/index.cjs +310 -180
- package/package.json +2 -1
- package/scripts/link.ts +14 -0
- package/src/app/App.tsx +41 -30
- package/src/app/AutoUpdate.tsx +9 -24
- package/src/app/CherryPickCheck.tsx +1 -2
- package/src/app/Debug.tsx +5 -6
- package/src/app/DependencyCheck.tsx +6 -6
- package/src/app/DetectInitialPR.tsx +2 -8
- package/src/app/DirtyCheck.tsx +51 -26
- package/src/app/Exit.tsx +41 -8
- package/src/app/FormatText.tsx +1 -5
- package/src/app/GatherMetadata.tsx +6 -13
- package/src/app/GithubApiError.tsx +1 -1
- package/src/app/HandleCtrlCSigint.tsx +36 -0
- package/src/app/LocalCommitStatus.tsx +1 -3
- package/src/app/LogTimestamp.tsx +1 -5
- package/src/app/ManualRebase.tsx +15 -37
- package/src/app/MultiSelect.tsx +2 -2
- package/src/app/PostRebaseStatus.tsx +2 -0
- package/src/app/PreManualRebase.tsx +3 -5
- package/src/app/RebaseCheck.tsx +1 -2
- package/src/app/SelectCommitRanges.tsx +6 -10
- package/src/app/Status.tsx +1 -1
- package/src/app/StatusTable.tsx +1 -4
- package/src/app/Store.tsx +29 -3
- package/src/app/SyncGithub.tsx +15 -45
- package/src/app/Table.tsx +4 -14
- package/src/app/TextInput.tsx +2 -7
- package/src/app/VerboseDebugInfo.tsx +1 -5
- package/src/app/YesNoPrompt.tsx +42 -31
- package/src/command.ts +8 -17
- package/src/commands/Fixup.tsx +17 -24
- package/src/commands/Log.tsx +3 -7
- package/src/commands/Rebase.tsx +18 -38
- package/src/components/ErrorBoundary.tsx +79 -0
- package/src/components/ExitingGate.tsx +27 -0
- package/src/core/CommitMetadata.ts +1 -1
- package/src/core/GitReviseTodo.test.ts +3 -3
- package/src/core/GitReviseTodo.ts +6 -8
- package/src/core/Metadata.test.ts +4 -4
- package/src/core/StackSummaryTable.ts +3 -3
- package/src/core/chalk.ts +1 -5
- package/src/core/cli.ts +2 -2
- package/src/core/github.tsx +15 -14
- package/src/core/pretty_json.ts +7 -0
- package/src/github/gh.auth_status.test.ts +2 -6
- package/src/index.tsx +42 -6
package/src/commands/Fixup.tsx
CHANGED
|
@@ -22,31 +22,23 @@ async function run() {
|
|
|
22
22
|
|
|
23
23
|
if (!relative_number) {
|
|
24
24
|
actions.output(
|
|
25
|
-
<Ink.Text color={colors.red}>
|
|
26
|
-
❗️ Usage: git fixup {"<relative-commit-number>"}
|
|
27
|
-
</Ink.Text>
|
|
25
|
+
<Ink.Text color={colors.red}>❗️ Usage: git fixup {"<relative-commit-number>"}</Ink.Text>,
|
|
28
26
|
);
|
|
29
27
|
actions.output("");
|
|
28
|
+
actions.output("This script automates the process of adding staged changes as a fixup commit");
|
|
30
29
|
actions.output(
|
|
31
|
-
"
|
|
32
|
-
);
|
|
33
|
-
actions.output(
|
|
34
|
-
"and the subsequent git rebase to flatten the commits based on relative commit number"
|
|
35
|
-
);
|
|
36
|
-
actions.output(
|
|
37
|
-
"You can use a `git log` like below to get the relative commit number"
|
|
30
|
+
"and the subsequent git rebase to flatten the commits based on relative commit number",
|
|
38
31
|
);
|
|
32
|
+
actions.output("You can use a `git log` like below to get the relative commit number");
|
|
39
33
|
actions.output("");
|
|
40
34
|
actions.output(" ❯ git stack log");
|
|
41
35
|
actions.output(
|
|
42
|
-
" 1\te329794d5f881cbf0fc3f26d2108cf6f3fdebabe enable drop_error_subtask test param"
|
|
36
|
+
" 1\te329794d5f881cbf0fc3f26d2108cf6f3fdebabe enable drop_error_subtask test param",
|
|
43
37
|
);
|
|
44
38
|
actions.output(
|
|
45
|
-
" 2\t57f43b596e5c6b97bc47e2a591f82ccc81651156 test drop_error_subtask baseline"
|
|
46
|
-
);
|
|
47
|
-
actions.output(
|
|
48
|
-
" 3\t838e878d483c6a2d5393063fc59baf2407225c6d ErrorSubtask test baseline"
|
|
39
|
+
" 2\t57f43b596e5c6b97bc47e2a591f82ccc81651156 test drop_error_subtask baseline",
|
|
49
40
|
);
|
|
41
|
+
actions.output(" 3\t838e878d483c6a2d5393063fc59baf2407225c6d ErrorSubtask test baseline");
|
|
50
42
|
actions.output("");
|
|
51
43
|
actions.output("To target `838e87` above, you would call `fixup 3`");
|
|
52
44
|
|
|
@@ -71,8 +63,7 @@ async function run() {
|
|
|
71
63
|
const adjusted_number = Number(relative_number) - 1;
|
|
72
64
|
|
|
73
65
|
// get the commit SHA of the target commit
|
|
74
|
-
const commit_sha = (await cli(`git rev-parse HEAD~${adjusted_number}`))
|
|
75
|
-
.stdout;
|
|
66
|
+
const commit_sha = (await cli(`git rev-parse HEAD~${adjusted_number}`)).stdout;
|
|
76
67
|
|
|
77
68
|
actions.output(
|
|
78
69
|
<FormatText
|
|
@@ -82,7 +73,7 @@ async function run() {
|
|
|
82
73
|
commit_sha: <Parens>{commit_sha}</Parens>,
|
|
83
74
|
relative_number: relative_number,
|
|
84
75
|
}}
|
|
85
|
-
|
|
76
|
+
/>,
|
|
86
77
|
);
|
|
87
78
|
|
|
88
79
|
await cli(`git commit --fixup ${commit_sha}`);
|
|
@@ -97,9 +88,13 @@ async function run() {
|
|
|
97
88
|
if (diff_cmd.code) {
|
|
98
89
|
save_stash = true;
|
|
99
90
|
|
|
100
|
-
await cli("git stash -
|
|
91
|
+
await cli("git stash --include-untracked");
|
|
101
92
|
|
|
102
|
-
actions.output(
|
|
93
|
+
actions.output(
|
|
94
|
+
<Ink.Text color={colors.yellow}>
|
|
95
|
+
<FormatText message="📦 Changes saved to stash" />
|
|
96
|
+
</Ink.Text>,
|
|
97
|
+
);
|
|
103
98
|
}
|
|
104
99
|
|
|
105
100
|
try {
|
|
@@ -118,11 +113,9 @@ async function run() {
|
|
|
118
113
|
await cli("git reset --soft HEAD~1");
|
|
119
114
|
} finally {
|
|
120
115
|
if (save_stash) {
|
|
121
|
-
await cli("git stash pop
|
|
116
|
+
await cli("git stash pop");
|
|
122
117
|
|
|
123
|
-
actions.output(
|
|
124
|
-
<Ink.Text color={colors.green}>✅ Changes restored from stash</Ink.Text>
|
|
125
|
-
);
|
|
118
|
+
actions.output(<Ink.Text color={colors.green}>✅ Changes restored from stash</Ink.Text>);
|
|
126
119
|
}
|
|
127
120
|
}
|
|
128
121
|
}
|
package/src/commands/Log.tsx
CHANGED
|
@@ -50,13 +50,9 @@ async function run(args: Args) {
|
|
|
50
50
|
const subject_format = `%<(60,trunc)%s`;
|
|
51
51
|
|
|
52
52
|
// combine all the above formats into one
|
|
53
|
-
const format = [
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
author_format,
|
|
57
|
-
decoration_format,
|
|
58
|
-
subject_format,
|
|
59
|
-
].join(" ");
|
|
53
|
+
const format = [sha_format, date_format, author_format, decoration_format, subject_format].join(
|
|
54
|
+
" ",
|
|
55
|
+
);
|
|
60
56
|
|
|
61
57
|
// view the SHA, description and history graph of last 20 commits
|
|
62
58
|
const rest_args = process_argv.slice(3).join(" ");
|
package/src/commands/Rebase.tsx
CHANGED
|
@@ -16,35 +16,15 @@ import { invariant } from "~/core/invariant";
|
|
|
16
16
|
import { short_id } from "~/core/short_id";
|
|
17
17
|
|
|
18
18
|
export function Rebase() {
|
|
19
|
-
const abort_handler = React.useRef(() => {});
|
|
20
|
-
|
|
21
|
-
React.useEffect(function listen_sigint() {
|
|
22
|
-
process.once("SIGINT", sigint_handler);
|
|
23
|
-
|
|
24
|
-
return function cleanup() {
|
|
25
|
-
process.removeListener("SIGINT", sigint_handler);
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
function sigint_handler() {
|
|
29
|
-
abort_handler.current();
|
|
30
|
-
}
|
|
31
|
-
}, []);
|
|
32
|
-
|
|
33
19
|
return (
|
|
34
20
|
<Await
|
|
35
21
|
fallback={<Ink.Text color={colors.yellow}>Rebasing commits…</Ink.Text>}
|
|
36
|
-
function={
|
|
37
|
-
await Rebase.run({ abort_handler });
|
|
38
|
-
}}
|
|
22
|
+
function={Rebase.run}
|
|
39
23
|
/>
|
|
40
24
|
);
|
|
41
25
|
}
|
|
42
26
|
|
|
43
|
-
|
|
44
|
-
abort_handler: React.MutableRefObject<() => void>;
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
Rebase.run = async function run(args: Args) {
|
|
27
|
+
Rebase.run = async function run() {
|
|
48
28
|
const state = Store.getState();
|
|
49
29
|
const actions = state.actions;
|
|
50
30
|
const branch_name = state.branch_name;
|
|
@@ -57,11 +37,12 @@ Rebase.run = async function run(args: Args) {
|
|
|
57
37
|
invariant(commit_range, "commit_range must exist");
|
|
58
38
|
invariant(repo_root, "repo_root must exist");
|
|
59
39
|
|
|
60
|
-
//
|
|
61
|
-
|
|
40
|
+
// immediately register abort_handler in case of ctrl+c exit
|
|
41
|
+
actions.register_abort_handler(async function abort_rebase() {
|
|
62
42
|
actions.output(<Ink.Text color={colors.red}>🚨 Abort</Ink.Text>);
|
|
63
|
-
handle_exit(
|
|
64
|
-
|
|
43
|
+
handle_exit();
|
|
44
|
+
return 19;
|
|
45
|
+
});
|
|
65
46
|
|
|
66
47
|
const temp_branch_name = `${branch_name}_${short_id()}`;
|
|
67
48
|
|
|
@@ -73,9 +54,7 @@ Rebase.run = async function run(args: Args) {
|
|
|
73
54
|
await cli(`pwd`);
|
|
74
55
|
|
|
75
56
|
// update local master to match remote
|
|
76
|
-
await cli(
|
|
77
|
-
`git fetch --no-tags -v origin ${master_branch}:${master_branch}`
|
|
78
|
-
);
|
|
57
|
+
await cli(`git fetch --no-tags -v origin ${master_branch}:${master_branch}`);
|
|
79
58
|
|
|
80
59
|
const master_sha = (await cli(`git rev-parse ${master_branch}`)).stdout;
|
|
81
60
|
const rebase_merge_base = master_sha;
|
|
@@ -102,7 +81,7 @@ Rebase.run = async function run(args: Args) {
|
|
|
102
81
|
commit_message: <Brackets>{commit.subject_line}</Brackets>,
|
|
103
82
|
pr_status: <Parens>MERGED</Parens>,
|
|
104
83
|
}}
|
|
105
|
-
|
|
84
|
+
/>,
|
|
106
85
|
);
|
|
107
86
|
}
|
|
108
87
|
|
|
@@ -117,7 +96,7 @@ Rebase.run = async function run(args: Args) {
|
|
|
117
96
|
values={{
|
|
118
97
|
commit_message: <Brackets>{commit.subject_line}</Brackets>,
|
|
119
98
|
}}
|
|
120
|
-
|
|
99
|
+
/>,
|
|
121
100
|
);
|
|
122
101
|
}
|
|
123
102
|
|
|
@@ -150,9 +129,11 @@ Rebase.run = async function run(args: Args) {
|
|
|
150
129
|
branch_name: <Brackets>{branch_name}</Brackets>,
|
|
151
130
|
origin_branch: <Brackets>{`origin/${master_branch}`}</Brackets>,
|
|
152
131
|
}}
|
|
153
|
-
|
|
132
|
+
/>,
|
|
154
133
|
);
|
|
155
134
|
|
|
135
|
+
actions.unregister_abort_handler();
|
|
136
|
+
|
|
156
137
|
actions.set((state) => {
|
|
157
138
|
state.commit_range = next_commit_range;
|
|
158
139
|
state.step = "status";
|
|
@@ -166,7 +147,8 @@ Rebase.run = async function run(args: Args) {
|
|
|
166
147
|
}
|
|
167
148
|
}
|
|
168
149
|
|
|
169
|
-
handle_exit(
|
|
150
|
+
handle_exit();
|
|
151
|
+
actions.exit(20);
|
|
170
152
|
}
|
|
171
153
|
|
|
172
154
|
// cleanup git operations if cancelled during manual rebase
|
|
@@ -194,11 +176,11 @@ Rebase.run = async function run(args: Args) {
|
|
|
194
176
|
cli.sync(`pwd`, spawn_options);
|
|
195
177
|
}
|
|
196
178
|
|
|
197
|
-
function handle_exit(
|
|
179
|
+
function handle_exit() {
|
|
198
180
|
actions.output(
|
|
199
181
|
<Ink.Text color={colors.yellow}>
|
|
200
182
|
Restoring <Brackets>{branch_name}</Brackets>…
|
|
201
|
-
</Ink.Text
|
|
183
|
+
</Ink.Text>,
|
|
202
184
|
);
|
|
203
185
|
|
|
204
186
|
restore_git();
|
|
@@ -206,9 +188,7 @@ Rebase.run = async function run(args: Args) {
|
|
|
206
188
|
actions.output(
|
|
207
189
|
<Ink.Text color={colors.yellow}>
|
|
208
190
|
Restored <Brackets>{branch_name}</Brackets>.
|
|
209
|
-
</Ink.Text
|
|
191
|
+
</Ink.Text>,
|
|
210
192
|
);
|
|
211
|
-
|
|
212
|
-
actions.exit(code);
|
|
213
193
|
}
|
|
214
194
|
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
|
|
4
|
+
import * as Ink from "ink-cjs";
|
|
5
|
+
|
|
6
|
+
import { FormatText } from "~/app/FormatText";
|
|
7
|
+
import { Store } from "~/app/Store";
|
|
8
|
+
import { colors } from "~/core/colors";
|
|
9
|
+
|
|
10
|
+
type Props = {
|
|
11
|
+
children: React.ReactNode;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
type State = {
|
|
15
|
+
error: null | Error;
|
|
16
|
+
component_stack: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export class ErrorBoundary extends React.Component<Props, State> {
|
|
20
|
+
constructor(props: Props) {
|
|
21
|
+
super(props);
|
|
22
|
+
|
|
23
|
+
this.state = {
|
|
24
|
+
error: null,
|
|
25
|
+
component_stack: "",
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
static getDerivedStateFromError(error: Error) {
|
|
30
|
+
return { error };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
override componentDidCatch(_error: Error, error_info: React.ErrorInfo) {
|
|
34
|
+
let component_stack = error_info.componentStack;
|
|
35
|
+
|
|
36
|
+
if (component_stack) {
|
|
37
|
+
// remove first line of component_stack
|
|
38
|
+
component_stack = component_stack.split("\n").slice(1).join("\n");
|
|
39
|
+
this.setState({ component_stack });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
override render() {
|
|
44
|
+
if (!this.state.error) {
|
|
45
|
+
return this.props.children;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const message = this.state.error.message;
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<Ink.Box flexDirection="column" gap={0}>
|
|
52
|
+
<Ink.Text color={colors.red}>
|
|
53
|
+
<FormatText
|
|
54
|
+
message="🚨 Unhandled error {message}"
|
|
55
|
+
values={{
|
|
56
|
+
message: <Ink.Text color={colors.gray}>{message}</Ink.Text>,
|
|
57
|
+
}}
|
|
58
|
+
/>
|
|
59
|
+
</Ink.Text>
|
|
60
|
+
|
|
61
|
+
{this._render_verbose()}
|
|
62
|
+
</Ink.Box>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
_render_verbose() {
|
|
67
|
+
const store_state = Store.getState();
|
|
68
|
+
|
|
69
|
+
if (store_state.argv.verbose) {
|
|
70
|
+
return <Ink.Text color={colors.gray}>{this.state.component_stack}</Ink.Text>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<Ink.Text color={colors.gray}>
|
|
75
|
+
<FormatText message="Try again with `--verbose` to see more information." />
|
|
76
|
+
</Ink.Text>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import * as Ink from "ink-cjs";
|
|
4
|
+
|
|
5
|
+
import { FormatText } from "~/app/FormatText";
|
|
6
|
+
import { Store } from "~/app/Store";
|
|
7
|
+
import { colors } from "~/core/colors";
|
|
8
|
+
|
|
9
|
+
type Props = {
|
|
10
|
+
children: React.ReactNode;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function ExitingGate(props: Props) {
|
|
14
|
+
const is_exiting = Store.useState((state) => state.is_exiting);
|
|
15
|
+
|
|
16
|
+
if (!is_exiting) {
|
|
17
|
+
return props.children;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<Ink.Box flexDirection="column">
|
|
22
|
+
<Ink.Text color={colors.red}>
|
|
23
|
+
<FormatText message="🚨 Exiting…" />
|
|
24
|
+
</Ink.Text>
|
|
25
|
+
</Ink.Box>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -167,7 +167,7 @@ export async function range(commit_group_map?: CommitGroupMap) {
|
|
|
167
167
|
async function get_commit_list() {
|
|
168
168
|
const master_branch = Store.getState().master_branch;
|
|
169
169
|
const log_result = await cli(
|
|
170
|
-
`git log ${master_branch}..HEAD --oneline --format=%H --color=never
|
|
170
|
+
`git log ${master_branch}..HEAD --oneline --format=%H --color=never`,
|
|
171
171
|
);
|
|
172
172
|
|
|
173
173
|
if (!log_result.stdout) {
|
|
@@ -35,7 +35,7 @@ test("git-revise-todo from commit range with single new commit", () => {
|
|
|
35
35
|
"",
|
|
36
36
|
"git-stack-id: E63ytp5dj",
|
|
37
37
|
"git-stack-title: lemon color",
|
|
38
|
-
].join("\n")
|
|
38
|
+
].join("\n"),
|
|
39
39
|
);
|
|
40
40
|
});
|
|
41
41
|
|
|
@@ -53,7 +53,7 @@ test("git-revise-todo from commit range with single new commit in new group", ()
|
|
|
53
53
|
"",
|
|
54
54
|
"git-stack-id: 6Ak-qn+5Z",
|
|
55
55
|
"git-stack-title: new group",
|
|
56
|
-
].join("\n")
|
|
56
|
+
].join("\n"),
|
|
57
57
|
);
|
|
58
58
|
});
|
|
59
59
|
|
|
@@ -71,7 +71,7 @@ test("git-revise-todo handles double quotes in commit message", () => {
|
|
|
71
71
|
"",
|
|
72
72
|
"git-stack-id: 6Ak-qn+5Z",
|
|
73
73
|
'git-stack-title: [new] invalid \\"by me\\" quotes',
|
|
74
|
-
].join("\n")
|
|
74
|
+
].join("\n"),
|
|
75
75
|
);
|
|
76
76
|
});
|
|
77
77
|
|
|
@@ -5,6 +5,7 @@ import path from "node:path";
|
|
|
5
5
|
import * as Metadata from "~/core/Metadata";
|
|
6
6
|
import { cli } from "~/core/cli";
|
|
7
7
|
import { invariant } from "~/core/invariant";
|
|
8
|
+
import { safe_rm } from "~/core/safe_rm";
|
|
8
9
|
|
|
9
10
|
import type * as CommitMetadata from "~/core/CommitMetadata";
|
|
10
11
|
|
|
@@ -86,10 +87,7 @@ GitReviseTodo.todo = function todo(args: CommitListArgs) {
|
|
|
86
87
|
|
|
87
88
|
const metadata = { id, title };
|
|
88
89
|
|
|
89
|
-
const unsafe_message_with_id = Metadata.write(
|
|
90
|
-
commit.full_message,
|
|
91
|
-
metadata
|
|
92
|
-
);
|
|
90
|
+
const unsafe_message_with_id = Metadata.write(commit.full_message, metadata);
|
|
93
91
|
|
|
94
92
|
let message_with_id = unsafe_message_with_id;
|
|
95
93
|
|
|
@@ -111,10 +109,7 @@ GitReviseTodo.todo = function todo(args: CommitListArgs) {
|
|
|
111
109
|
|
|
112
110
|
GitReviseTodo.execute = async function grt_execute(args: ExecuteArgs) {
|
|
113
111
|
// generate temporary directory and drop sequence editor script
|
|
114
|
-
const tmp_git_sequence_editor_path = path.join(
|
|
115
|
-
os.tmpdir(),
|
|
116
|
-
"git-sequence-editor.sh"
|
|
117
|
-
);
|
|
112
|
+
const tmp_git_sequence_editor_path = path.join(os.tmpdir(), "git-sequence-editor.sh");
|
|
118
113
|
|
|
119
114
|
// replaced at build time with literal contents of `scripts/git-sequence-editor.sh`
|
|
120
115
|
const GIT_SEQUENCE_EDITOR_SCRIPT = `process.env.GIT_SEQUENCE_EDITOR_SCRIPT`;
|
|
@@ -140,6 +135,9 @@ GitReviseTodo.execute = async function grt_execute(args: ExecuteArgs) {
|
|
|
140
135
|
// change to pipe to see output temporarily
|
|
141
136
|
// https://github.com/magus/git-stack-cli/commit/f9f10e3ac3cd9a35ee75d3e0851a48391967a23f
|
|
142
137
|
await cli(command, { stdio: ["ignore", "ignore", "ignore"] });
|
|
138
|
+
|
|
139
|
+
// cleanup tmp_git_sequence_editor_path
|
|
140
|
+
await safe_rm(tmp_git_sequence_editor_path);
|
|
143
141
|
};
|
|
144
142
|
|
|
145
143
|
type ExecuteArgs = {
|
|
@@ -48,7 +48,7 @@ test("write handles bulleted lists", () => {
|
|
|
48
48
|
"",
|
|
49
49
|
"git-stack-id: abcd1234",
|
|
50
50
|
"git-stack-title: banana",
|
|
51
|
-
].join("\n")
|
|
51
|
+
].join("\n"),
|
|
52
52
|
);
|
|
53
53
|
});
|
|
54
54
|
|
|
@@ -94,7 +94,7 @@ test("write handles bulleted lists", () => {
|
|
|
94
94
|
"",
|
|
95
95
|
"git-stack-id: fix-slash-branch",
|
|
96
96
|
"git-stack-title: fix slash branch",
|
|
97
|
-
].join("\n")
|
|
97
|
+
].join("\n"),
|
|
98
98
|
);
|
|
99
99
|
});
|
|
100
100
|
|
|
@@ -133,7 +133,7 @@ test("write handles double quotes", () => {
|
|
|
133
133
|
"",
|
|
134
134
|
"git-stack-id: abc123",
|
|
135
135
|
'git-stack-title: Revert \\"[abc / 123] subject (#1234)\\"',
|
|
136
|
-
].join("\n")
|
|
136
|
+
].join("\n"),
|
|
137
137
|
);
|
|
138
138
|
});
|
|
139
139
|
|
|
@@ -156,6 +156,6 @@ test("removes metadata", () => {
|
|
|
156
156
|
"- keyboard modality escape key",
|
|
157
157
|
"- centralize settings",
|
|
158
158
|
"- move logic inside if branch",
|
|
159
|
-
].join("\n")
|
|
159
|
+
].join("\n"),
|
|
160
160
|
);
|
|
161
161
|
});
|
|
@@ -122,7 +122,7 @@ const TEMPLATE = {
|
|
|
122
122
|
const RE = {
|
|
123
123
|
// https://regex101.com/r/kqB9Ft/1
|
|
124
124
|
stack_table_legacy: new RegExp(
|
|
125
|
-
TEMPLATE.stack_table_legacy("\\s+(?<rows>(?:- [^\r^\n]*(?:[\r\n]+)?)+)")
|
|
125
|
+
TEMPLATE.stack_table_legacy("\\s+(?<rows>(?:- [^\r^\n]*(?:[\r\n]+)?)+)"),
|
|
126
126
|
),
|
|
127
127
|
|
|
128
128
|
stack_table_link: new RegExp(
|
|
@@ -131,7 +131,7 @@ const RE = {
|
|
|
131
131
|
.replace("]", "\\]")
|
|
132
132
|
.replace("(", "\\(")
|
|
133
133
|
.replace(")", "\\)")
|
|
134
|
-
.replace("ROWS", "\\s+(?<rows>(?:- [^\r^\n]*(?:[\r\n]+)?)+)")
|
|
134
|
+
.replace("ROWS", "\\s+(?<rows>(?:- [^\r^\n]*(?:[\r\n]+)?)+)"),
|
|
135
135
|
),
|
|
136
136
|
|
|
137
137
|
row: new RegExp(
|
|
@@ -139,7 +139,7 @@ const RE = {
|
|
|
139
139
|
icon: "(?<icon>.+)",
|
|
140
140
|
num: "(?<num>\\d+)",
|
|
141
141
|
pr_url: "(?<pr_url>.+)",
|
|
142
|
-
})
|
|
142
|
+
}),
|
|
143
143
|
),
|
|
144
144
|
|
|
145
145
|
pr_url: /^https:\/\/.*$/,
|
package/src/core/chalk.ts
CHANGED
|
@@ -19,11 +19,7 @@ function create_color_proxy(base: typeof chalk): ColorProxy {
|
|
|
19
19
|
|
|
20
20
|
case "bracket":
|
|
21
21
|
return (str: string) =>
|
|
22
|
-
[
|
|
23
|
-
target.bold.whiteBright("["),
|
|
24
|
-
str,
|
|
25
|
-
target.bold.whiteBright("]"),
|
|
26
|
-
].join("");
|
|
22
|
+
[target.bold.whiteBright("["), str, target.bold.whiteBright("]")].join("");
|
|
27
23
|
|
|
28
24
|
case "url":
|
|
29
25
|
return target.bold.underline.blueBright;
|
package/src/core/cli.ts
CHANGED
|
@@ -22,7 +22,7 @@ let i = 0;
|
|
|
22
22
|
|
|
23
23
|
export async function cli(
|
|
24
24
|
unsafe_command: string | Array<string | number>,
|
|
25
|
-
unsafe_options?: Options
|
|
25
|
+
unsafe_options?: Options,
|
|
26
26
|
): Promise<Return> {
|
|
27
27
|
const options = Object.assign({}, unsafe_options);
|
|
28
28
|
|
|
@@ -97,7 +97,7 @@ export async function cli(
|
|
|
97
97
|
|
|
98
98
|
cli.sync = function cli_sync(
|
|
99
99
|
unsafe_command: string | Array<string | number>,
|
|
100
|
-
unsafe_options?: Options
|
|
100
|
+
unsafe_options?: Options,
|
|
101
101
|
): Return {
|
|
102
102
|
const options = Object.assign({}, unsafe_options);
|
|
103
103
|
|
package/src/core/github.tsx
CHANGED
|
@@ -24,7 +24,7 @@ export async function pr_list(): Promise<Array<PullRequest>> {
|
|
|
24
24
|
invariant(repo_path, "repo_path must exist");
|
|
25
25
|
|
|
26
26
|
const result_pr_list = await gh_json<Array<PullRequest>>(
|
|
27
|
-
`pr list --repo ${repo_path} --author ${username} --state open ${JSON_FIELDS}
|
|
27
|
+
`pr list --repo ${repo_path} --author ${username} --state open ${JSON_FIELDS}`,
|
|
28
28
|
);
|
|
29
29
|
|
|
30
30
|
if (result_pr_list instanceof Error) {
|
|
@@ -42,7 +42,7 @@ export async function pr_list(): Promise<Array<PullRequest>> {
|
|
|
42
42
|
<Brackets>{repo_path}</Brackets>
|
|
43
43
|
<Ink.Text>{" authored by "}</Ink.Text>
|
|
44
44
|
<Brackets>{username}</Brackets>
|
|
45
|
-
</Ink.Text
|
|
45
|
+
</Ink.Text>,
|
|
46
46
|
);
|
|
47
47
|
}
|
|
48
48
|
|
|
@@ -77,7 +77,7 @@ export async function pr_status(branch: string): Promise<null | PullRequest> {
|
|
|
77
77
|
</Ink.Text>
|
|
78
78
|
<Ink.Text> </Ink.Text>
|
|
79
79
|
<Ink.Text dimColor>{branch}</Ink.Text>
|
|
80
|
-
</Ink.Text
|
|
80
|
+
</Ink.Text>,
|
|
81
81
|
);
|
|
82
82
|
}
|
|
83
83
|
|
|
@@ -94,13 +94,11 @@ export async function pr_status(branch: string): Promise<null | PullRequest> {
|
|
|
94
94
|
</Ink.Text>
|
|
95
95
|
<Ink.Text> </Ink.Text>
|
|
96
96
|
<Ink.Text dimColor>{branch}</Ink.Text>
|
|
97
|
-
</Ink.Text
|
|
97
|
+
</Ink.Text>,
|
|
98
98
|
);
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
const pr = await gh_json<PullRequest>(
|
|
102
|
-
`pr view ${branch} --repo ${repo_path} ${JSON_FIELDS}`
|
|
103
|
-
);
|
|
101
|
+
const pr = await gh_json<PullRequest>(`pr view ${branch} --repo ${repo_path} ${JSON_FIELDS}`);
|
|
104
102
|
|
|
105
103
|
if (pr instanceof Error) {
|
|
106
104
|
return null;
|
|
@@ -161,8 +159,10 @@ type EditPullRequestArgs = {
|
|
|
161
159
|
export async function pr_edit(args: EditPullRequestArgs) {
|
|
162
160
|
const command_parts = [`gh pr edit ${args.branch} --base ${args.base}`];
|
|
163
161
|
|
|
162
|
+
let body_file: string | undefined;
|
|
163
|
+
|
|
164
164
|
if (args.body) {
|
|
165
|
-
|
|
165
|
+
body_file = await write_body_file(args);
|
|
166
166
|
command_parts.push(`--body-file="${body_file}"`);
|
|
167
167
|
}
|
|
168
168
|
|
|
@@ -171,6 +171,11 @@ export async function pr_edit(args: EditPullRequestArgs) {
|
|
|
171
171
|
if (cli_result.code !== 0) {
|
|
172
172
|
handle_error(cli_result.output);
|
|
173
173
|
}
|
|
174
|
+
|
|
175
|
+
// cleanup body_file
|
|
176
|
+
if (body_file) {
|
|
177
|
+
await safe_rm(body_file);
|
|
178
|
+
}
|
|
174
179
|
}
|
|
175
180
|
|
|
176
181
|
type DraftPullRequestArgs = {
|
|
@@ -183,9 +188,7 @@ export async function pr_draft(args: DraftPullRequestArgs) {
|
|
|
183
188
|
// https://docs.github.com/en/graphql/reference/mutations#convertpullrequesttodraft
|
|
184
189
|
// https://docs.github.com/en/graphql/reference/mutations#markpullrequestreadyforreview
|
|
185
190
|
|
|
186
|
-
const mutation_name = args.draft
|
|
187
|
-
? "convertPullRequestToDraft"
|
|
188
|
-
: "markPullRequestReadyForReview";
|
|
191
|
+
const mutation_name = args.draft ? "convertPullRequestToDraft" : "markPullRequestReadyForReview";
|
|
189
192
|
|
|
190
193
|
let query = `
|
|
191
194
|
mutation($id: ID!) {
|
|
@@ -208,9 +211,7 @@ export async function pr_draft(args: DraftPullRequestArgs) {
|
|
|
208
211
|
const cache_pr = state.pr[args.branch];
|
|
209
212
|
invariant(cache_pr, "cache_pr must exist");
|
|
210
213
|
|
|
211
|
-
const command_parts = [
|
|
212
|
-
`gh api graphql -F id="${cache_pr.id}" -f query='${query}'`,
|
|
213
|
-
];
|
|
214
|
+
const command_parts = [`gh api graphql -F id="${cache_pr.id}" -f query='${query}'`];
|
|
214
215
|
|
|
215
216
|
const command = command_parts.join(" ");
|
|
216
217
|
|
|
@@ -3,17 +3,13 @@ import { test, expect } from "bun:test";
|
|
|
3
3
|
import * as gh from "./gh";
|
|
4
4
|
|
|
5
5
|
test("logged in as", () => {
|
|
6
|
-
const username = gh.auth_status(
|
|
7
|
-
" ✓ Logged in to github.com as magus (keyring)\n"
|
|
8
|
-
);
|
|
6
|
+
const username = gh.auth_status(" ✓ Logged in to github.com as magus (keyring)\n");
|
|
9
7
|
|
|
10
8
|
expect(username).toBe("magus");
|
|
11
9
|
});
|
|
12
10
|
|
|
13
11
|
test("logged in without as", () => {
|
|
14
|
-
const username = gh.auth_status(
|
|
15
|
-
"✓ Logged in to github.com account xoxohorses (keyring)"
|
|
16
|
-
);
|
|
12
|
+
const username = gh.auth_status("✓ Logged in to github.com account xoxohorses (keyring)");
|
|
17
13
|
|
|
18
14
|
expect(username).toBe("xoxohorses");
|
|
19
15
|
});
|