git-stack-cli 2.8.1 → 2.9.0
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 -0
- package/dist/js/index.js +75 -75
- package/package.json +1 -1
- package/src/app/GatherMetadata.test.tsx +33 -0
- package/src/app/GatherMetadata.tsx +12 -1
- package/src/app/MultiSelect.tsx +10 -5
- package/src/app/PreManualRebase.tsx +39 -16
- package/src/app/SelectCommitRanges.tsx +179 -47
- package/src/app/SyncGithub.tsx +0 -2
- package/src/core/github.tsx +4 -0
package/package.json
CHANGED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { test, expect } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import * as GatherMetadata from "~/app/GatherMetadata";
|
|
4
|
+
|
|
5
|
+
test("invalid origin", () => {
|
|
6
|
+
const origin_url = "";
|
|
7
|
+
const repo_path = GatherMetadata.get_repo_path(origin_url);
|
|
8
|
+
expect(repo_path).toBe(null);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test("https .git", () => {
|
|
12
|
+
const origin_url = "https://github.com/magus/git-multi-diff-playground.git";
|
|
13
|
+
const repo_path = GatherMetadata.get_repo_path(origin_url);
|
|
14
|
+
expect(repo_path).toBe("magus/git-multi-diff-playground");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("https without .git", () => {
|
|
18
|
+
const origin_url = "https://github.com/magus/git-multi-diff-playground";
|
|
19
|
+
const repo_path = GatherMetadata.get_repo_path(origin_url);
|
|
20
|
+
expect(repo_path).toBe("magus/git-multi-diff-playground");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("git@ .git", () => {
|
|
24
|
+
const origin_url = "git@github.com:magus/git-multi-diff-playground.git";
|
|
25
|
+
const repo_path = GatherMetadata.get_repo_path(origin_url);
|
|
26
|
+
expect(repo_path).toBe("magus/git-multi-diff-playground");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("git@ without .git", () => {
|
|
30
|
+
const origin_url = "git@github.com:magus/git-multi-diff-playground";
|
|
31
|
+
const repo_path = GatherMetadata.get_repo_path(origin_url);
|
|
32
|
+
expect(repo_path).toBe("magus/git-multi-diff-playground");
|
|
33
|
+
});
|
|
@@ -97,10 +97,21 @@ async function run() {
|
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
export function get_repo_path(origin_url: string): null | string {
|
|
101
|
+
try {
|
|
102
|
+
return match_group(origin_url, RE.repo_path, "repo_path");
|
|
103
|
+
} catch {
|
|
104
|
+
// pass
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
100
109
|
const RE = {
|
|
101
110
|
// git@github.com:magus/git-multi-diff-playground.git
|
|
102
111
|
// https://github.com/magus/git-multi-diff-playground.git
|
|
103
|
-
|
|
112
|
+
// the .git suffix is optional for remote urls, and may not always be provided
|
|
113
|
+
// https://regex101.com/r/pICG7G/1
|
|
114
|
+
repo_path: /(?<repo_path>[^:^/]+\/[^/]+?)(\.git)?$/,
|
|
104
115
|
};
|
|
105
116
|
|
|
106
117
|
const BRANCH = {
|
package/src/app/MultiSelect.tsx
CHANGED
|
@@ -15,10 +15,11 @@ type Props<T> = {
|
|
|
15
15
|
disableSelect?: boolean;
|
|
16
16
|
maxWidth?: number;
|
|
17
17
|
startIndex?: number;
|
|
18
|
+
withSelectPreview?: boolean;
|
|
18
19
|
};
|
|
19
20
|
|
|
20
21
|
type Item<T> = {
|
|
21
|
-
label:
|
|
22
|
+
label: React.ReactNode;
|
|
22
23
|
value: T;
|
|
23
24
|
selected?: ItemRowProps["selected"];
|
|
24
25
|
disabled?: ItemRowProps["disabled"];
|
|
@@ -181,9 +182,13 @@ export function MultiSelect<T>(props: Props<T>) {
|
|
|
181
182
|
return (
|
|
182
183
|
<Ink.Box flexDirection="column">
|
|
183
184
|
{props.items.map((item, i) => {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
185
|
+
let active = i === index;
|
|
186
|
+
let selected = selected_set.has(i);
|
|
187
|
+
let disabled = item.disabled || false;
|
|
188
|
+
|
|
189
|
+
if (props.withSelectPreview) {
|
|
190
|
+
selected = active;
|
|
191
|
+
}
|
|
187
192
|
|
|
188
193
|
return (
|
|
189
194
|
<ItemRow
|
|
@@ -201,7 +206,7 @@ export function MultiSelect<T>(props: Props<T>) {
|
|
|
201
206
|
}
|
|
202
207
|
|
|
203
208
|
type ItemRowProps = {
|
|
204
|
-
label:
|
|
209
|
+
label: React.ReactNode;
|
|
205
210
|
active: boolean;
|
|
206
211
|
selected: boolean;
|
|
207
212
|
disabled: boolean;
|
|
@@ -39,10 +39,11 @@ async function run() {
|
|
|
39
39
|
// ./pull_request_template.md
|
|
40
40
|
// ./docs/pull_request_template.md
|
|
41
41
|
for (const key of PR_TEMPLATE_KEY_LIST) {
|
|
42
|
-
const pr_template_fn = PR_TEMPLATE[key
|
|
42
|
+
const pr_template_fn = PR_TEMPLATE[key];
|
|
43
|
+
const pr_template_file = pr_template_fn(repo_root);
|
|
43
44
|
|
|
44
|
-
if (await safe_exists(
|
|
45
|
-
pr_template_body = await fs.readFile(
|
|
45
|
+
if (await safe_exists(pr_template_file)) {
|
|
46
|
+
pr_template_body = await fs.readFile(pr_template_file, "utf-8");
|
|
46
47
|
|
|
47
48
|
actions.output(
|
|
48
49
|
<FormatText
|
|
@@ -58,10 +59,23 @@ async function run() {
|
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
// ./.github/PULL_REQUEST_TEMPLATE/*.md
|
|
62
62
|
let pr_templates: Array<string> = [];
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
let pr_dir: string = "";
|
|
64
|
+
|
|
65
|
+
// ./.github/PULL_REQUEST_TEMPLATE/*.md
|
|
66
|
+
pr_dir = PR_TEMPLATE.DirGithub(repo_root);
|
|
67
|
+
if (await safe_exists(pr_dir)) {
|
|
68
|
+
for (const filename of await fs.readdir(pr_dir)) {
|
|
69
|
+
pr_templates.push(path.join(pr_dir, filename));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ./docs/PULL_REQUEST_TEMPLATE/*.md
|
|
74
|
+
pr_dir = PR_TEMPLATE.DirDocs(repo_root);
|
|
75
|
+
if (await safe_exists(pr_dir)) {
|
|
76
|
+
for (const filename of await fs.readdir(pr_dir)) {
|
|
77
|
+
pr_templates.push(path.join(pr_dir, filename));
|
|
78
|
+
}
|
|
65
79
|
}
|
|
66
80
|
|
|
67
81
|
// check if repo has multiple pr templates
|
|
@@ -71,14 +85,20 @@ async function run() {
|
|
|
71
85
|
|
|
72
86
|
if (pr_templates.length > 0) {
|
|
73
87
|
actions.output(
|
|
74
|
-
<
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
88
|
+
<Ink.Box flexDirection="column">
|
|
89
|
+
{pr_templates.map((filepath) => {
|
|
90
|
+
const relpath = path.relative(repo_root, filepath);
|
|
91
|
+
return <Ink.Text key={filepath}>- {relpath}</Ink.Text>;
|
|
92
|
+
})}
|
|
93
|
+
|
|
94
|
+
<FormatText
|
|
95
|
+
wrapper={<Ink.Text color={colors.yellow} />}
|
|
96
|
+
message="{count} queryable templates found, but not supported."
|
|
97
|
+
values={{
|
|
98
|
+
count: <Ink.Text color={colors.blue}>{pr_templates.length}</Ink.Text>,
|
|
99
|
+
}}
|
|
100
|
+
/>
|
|
101
|
+
</Ink.Box>,
|
|
82
102
|
);
|
|
83
103
|
}
|
|
84
104
|
|
|
@@ -91,8 +111,11 @@ const PR_TEMPLATE = Object.freeze({
|
|
|
91
111
|
Github: (root: string) => path.join(root, ".github", "pull_request_template.md"),
|
|
92
112
|
Root: (root: string) => path.join(root, "pull_request_template.md"),
|
|
93
113
|
Docs: (root: string) => path.join(root, "docs", "pull_request_template.md"),
|
|
94
|
-
|
|
114
|
+
|
|
115
|
+
DirDocs: (root: string) => path.join(root, "docs", "PULL_REQUEST_TEMPLATE/"),
|
|
116
|
+
DirGithub: (root: string) => path.join(root, ".github", "PULL_REQUEST_TEMPLATE/"),
|
|
95
117
|
});
|
|
96
118
|
|
|
97
119
|
// prettier-ignore
|
|
98
|
-
|
|
120
|
+
//
|
|
121
|
+
const PR_TEMPLATE_KEY_LIST = ["Github", "Root", "Docs"] satisfies Array<keyof typeof PR_TEMPLATE>;
|
|
@@ -14,49 +14,36 @@ import { invariant } from "~/core/invariant";
|
|
|
14
14
|
import { short_id } from "~/core/short_id";
|
|
15
15
|
import { wrap_index } from "~/core/wrap_index";
|
|
16
16
|
|
|
17
|
-
import type { State } from "~/app/Store";
|
|
18
17
|
import type * as CommitMetadata from "~/core/CommitMetadata";
|
|
19
18
|
|
|
20
19
|
type CommitRangeGroup = NonNullable<Parameters<typeof CommitMetadata.range>[0]>[string];
|
|
20
|
+
type SimpleGroup = { id: string; title: string };
|
|
21
21
|
|
|
22
22
|
export function SelectCommitRanges() {
|
|
23
|
-
const
|
|
23
|
+
const actions = Store.useActions();
|
|
24
24
|
|
|
25
|
+
const commit_range = Store.useState((state) => state.commit_range);
|
|
25
26
|
invariant(commit_range, "commit_range must exist");
|
|
26
27
|
|
|
27
|
-
return <SelectCommitRangesInternal commit_range={commit_range} />;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
type Props = {
|
|
31
|
-
commit_range: CommitRange;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
type CommitRange = NonNullable<State["commit_range"]>;
|
|
35
|
-
type SimpleGroup = { id: string; title: string };
|
|
36
|
-
|
|
37
|
-
function SelectCommitRangesInternal(props: Props) {
|
|
38
|
-
const actions = Store.useActions();
|
|
39
|
-
|
|
40
28
|
const argv = Store.useState((state) => state.argv);
|
|
41
29
|
const branch_name = Store.useState((state) => state.branch_name);
|
|
42
30
|
invariant(branch_name, "branch_name must exist");
|
|
43
31
|
|
|
44
32
|
const [focused, set_focused] = React.useState("");
|
|
33
|
+
const [group_input, set_group_input] = React.useState(false);
|
|
34
|
+
const [pr_select, set_pr_select] = React.useState(false);
|
|
35
|
+
const is_input_mode = group_input || pr_select;
|
|
45
36
|
|
|
46
37
|
const [selected_group_id, set_selected_group_id] = React.useState(() => {
|
|
47
|
-
const first_group =
|
|
48
|
-
(g) => g.id !== props.commit_range.UNASSIGNED,
|
|
49
|
-
);
|
|
38
|
+
const first_group = commit_range.group_list.find((g) => g.id !== commit_range.UNASSIGNED);
|
|
50
39
|
|
|
51
40
|
if (first_group) {
|
|
52
41
|
return first_group.id;
|
|
53
42
|
}
|
|
54
43
|
|
|
55
|
-
return
|
|
44
|
+
return commit_range.UNASSIGNED;
|
|
56
45
|
});
|
|
57
46
|
|
|
58
|
-
const [group_input, set_group_input] = React.useState(false);
|
|
59
|
-
|
|
60
47
|
const [new_group_list, create_group] = React.useReducer(
|
|
61
48
|
(group_list: Array<SimpleGroup>, group: SimpleGroup) => {
|
|
62
49
|
const next_group_list = group_list.concat(group);
|
|
@@ -65,6 +52,22 @@ function SelectCommitRangesInternal(props: Props) {
|
|
|
65
52
|
[],
|
|
66
53
|
);
|
|
67
54
|
|
|
55
|
+
const existing_pr_list = Store.useState((state) => {
|
|
56
|
+
// collect set of groups already represented as prs
|
|
57
|
+
const existing_group_set = new Set<string>();
|
|
58
|
+
for (const group of commit_range.group_list) {
|
|
59
|
+
if (group.pr) {
|
|
60
|
+
existing_group_set.add(group.pr.headRefName);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
for (const group of new_group_list) {
|
|
64
|
+
existing_group_set.add(group.id);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// exclude existing_group_set from existing state.pr values
|
|
68
|
+
return Object.values(state.pr).filter((pr) => !existing_group_set.has(pr.headRefName));
|
|
69
|
+
});
|
|
70
|
+
|
|
68
71
|
const [group_master_base, set_group_master_base] = React.useReducer(
|
|
69
72
|
(set: Set<string>, group_id: string) => {
|
|
70
73
|
set.has(group_id) ? set.delete(group_id) : set.add(group_id);
|
|
@@ -72,7 +75,7 @@ function SelectCommitRangesInternal(props: Props) {
|
|
|
72
75
|
},
|
|
73
76
|
new Set<string>(),
|
|
74
77
|
(set) => {
|
|
75
|
-
for (const group of
|
|
78
|
+
for (const group of commit_range.group_list) {
|
|
76
79
|
if (group.master_base) {
|
|
77
80
|
set.add(group.id);
|
|
78
81
|
}
|
|
@@ -91,7 +94,7 @@ function SelectCommitRangesInternal(props: Props) {
|
|
|
91
94
|
},
|
|
92
95
|
new Map(),
|
|
93
96
|
(map) => {
|
|
94
|
-
for (const commit of
|
|
97
|
+
for (const commit of commit_range.commit_list) {
|
|
95
98
|
map.set(commit.sha, commit.branch_id);
|
|
96
99
|
}
|
|
97
100
|
|
|
@@ -104,8 +107,19 @@ function SelectCommitRangesInternal(props: Props) {
|
|
|
104
107
|
Ink.useInput((input, key) => {
|
|
105
108
|
const input_lower = input.toLowerCase();
|
|
106
109
|
|
|
107
|
-
//
|
|
108
|
-
if (
|
|
110
|
+
// only allow pr select when on unassigned group
|
|
111
|
+
if (has_unassigned_commits && input_lower === SYMBOL.p) {
|
|
112
|
+
set_pr_select((v) => !v);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
// allow cancelling pr select with esc
|
|
116
|
+
if (pr_select && key.escape) {
|
|
117
|
+
set_pr_select(false);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// do not allow input when inputting
|
|
122
|
+
if (is_input_mode) {
|
|
109
123
|
return;
|
|
110
124
|
}
|
|
111
125
|
|
|
@@ -122,7 +136,7 @@ function SelectCommitRangesInternal(props: Props) {
|
|
|
122
136
|
|
|
123
137
|
// handle allow_unassigned case
|
|
124
138
|
if (!id) {
|
|
125
|
-
id =
|
|
139
|
+
id = commit_range.UNASSIGNED;
|
|
126
140
|
const title = "allow_unassigned";
|
|
127
141
|
state_commit_map[sha] = { id, title, master_base: false };
|
|
128
142
|
continue;
|
|
@@ -149,7 +163,7 @@ function SelectCommitRangesInternal(props: Props) {
|
|
|
149
163
|
}
|
|
150
164
|
|
|
151
165
|
// only allow setting base branch when on a created group
|
|
152
|
-
if (group.id !==
|
|
166
|
+
if (group.id !== commit_range.UNASSIGNED && input_lower === SYMBOL.m) {
|
|
153
167
|
const group = group_list[current_index];
|
|
154
168
|
set_group_master_base(group.id);
|
|
155
169
|
return;
|
|
@@ -180,19 +194,19 @@ function SelectCommitRangesInternal(props: Props) {
|
|
|
180
194
|
}
|
|
181
195
|
}
|
|
182
196
|
|
|
183
|
-
if (!
|
|
197
|
+
if (!commit_range.group_list.length) {
|
|
184
198
|
return null;
|
|
185
199
|
}
|
|
186
200
|
|
|
187
|
-
const total_group_count = new_group_list.length +
|
|
201
|
+
const total_group_count = new_group_list.length + commit_range.group_list.length;
|
|
188
202
|
|
|
189
|
-
for (let i = 0; i <
|
|
190
|
-
const index =
|
|
191
|
-
const group =
|
|
203
|
+
for (let i = 0; i < commit_range.group_list.length; i++) {
|
|
204
|
+
const index = commit_range.group_list.length - i - 1;
|
|
205
|
+
const group = commit_range.group_list[index];
|
|
192
206
|
|
|
193
207
|
if (group.pr?.state === "MERGED") continue;
|
|
194
208
|
|
|
195
|
-
if (group.id ===
|
|
209
|
+
if (group.id === commit_range.UNASSIGNED) {
|
|
196
210
|
// only include unassigned group when there are no other groups
|
|
197
211
|
if (total_group_count === 1) {
|
|
198
212
|
group_list.push({
|
|
@@ -226,20 +240,20 @@ function SelectCommitRangesInternal(props: Props) {
|
|
|
226
240
|
const group = group_list[current_index];
|
|
227
241
|
const is_master_base = group_master_base.has(group.id);
|
|
228
242
|
|
|
229
|
-
const multiselect_disabled =
|
|
230
|
-
const multiselect_disableSelect = group.id ===
|
|
243
|
+
const multiselect_disabled = is_input_mode;
|
|
244
|
+
const multiselect_disableSelect = group.id === commit_range.UNASSIGNED;
|
|
231
245
|
|
|
232
246
|
const max_width = 80;
|
|
233
|
-
const has_groups = group.id !==
|
|
247
|
+
const has_groups = group.id !== commit_range.UNASSIGNED;
|
|
234
248
|
|
|
235
|
-
const items =
|
|
249
|
+
const items = commit_range.commit_list.map((commit) => {
|
|
236
250
|
const commit_metadata_id = commit_map.get(commit.sha);
|
|
237
251
|
|
|
238
252
|
const selected = commit_metadata_id !== null;
|
|
239
253
|
|
|
240
254
|
let disabled;
|
|
241
255
|
|
|
242
|
-
if (
|
|
256
|
+
if (is_input_mode) {
|
|
243
257
|
disabled = true;
|
|
244
258
|
} else if (!has_groups) {
|
|
245
259
|
disabled = true;
|
|
@@ -261,7 +275,7 @@ function SelectCommitRangesInternal(props: Props) {
|
|
|
261
275
|
<Ink.Box flexDirection="column">
|
|
262
276
|
<Ink.Box height={1} />
|
|
263
277
|
|
|
264
|
-
{has_groups ||
|
|
278
|
+
{has_groups || is_input_mode ? null : (
|
|
265
279
|
<Ink.Box flexDirection="column">
|
|
266
280
|
<Ink.Text bold color={colors.blue}>
|
|
267
281
|
👋 Welcome to <Command>git stack</Command>!
|
|
@@ -283,10 +297,28 @@ function SelectCommitRangesInternal(props: Props) {
|
|
|
283
297
|
}}
|
|
284
298
|
/>
|
|
285
299
|
</Ink.Text>
|
|
300
|
+
|
|
301
|
+
<Ink.Text color={colors.blue}>
|
|
302
|
+
<FormatText
|
|
303
|
+
message="Press {p} to {pick} an existing PR"
|
|
304
|
+
values={{
|
|
305
|
+
p: (
|
|
306
|
+
<Ink.Text bold color={colors.green}>
|
|
307
|
+
p
|
|
308
|
+
</Ink.Text>
|
|
309
|
+
),
|
|
310
|
+
pick: (
|
|
311
|
+
<Ink.Text bold color={colors.green}>
|
|
312
|
+
<Parens>p</Parens>pick
|
|
313
|
+
</Ink.Text>
|
|
314
|
+
),
|
|
315
|
+
}}
|
|
316
|
+
/>
|
|
317
|
+
</Ink.Text>
|
|
286
318
|
</Ink.Box>
|
|
287
319
|
)}
|
|
288
320
|
|
|
289
|
-
{!has_groups ||
|
|
321
|
+
{!has_groups || is_input_mode ? null : (
|
|
290
322
|
<React.Fragment>
|
|
291
323
|
<Ink.Box width={max_width} flexDirection="row">
|
|
292
324
|
<Ink.Box flexDirection="row">
|
|
@@ -320,8 +352,6 @@ function SelectCommitRangesInternal(props: Props) {
|
|
|
320
352
|
|
|
321
353
|
{!group_input ? null : (
|
|
322
354
|
<React.Fragment>
|
|
323
|
-
<Ink.Box height={1} />
|
|
324
|
-
|
|
325
355
|
<FormatText
|
|
326
356
|
wrapper={<Ink.Text color={colors.gray} />}
|
|
327
357
|
message="Enter a title for the PR {note}"
|
|
@@ -329,13 +359,18 @@ function SelectCommitRangesInternal(props: Props) {
|
|
|
329
359
|
note: (
|
|
330
360
|
<Parens>
|
|
331
361
|
<FormatText
|
|
332
|
-
message="press {enter} to submit"
|
|
362
|
+
message="press {enter} to submit, {esc} to clear"
|
|
333
363
|
values={{
|
|
334
364
|
enter: (
|
|
335
365
|
<Ink.Text bold color={colors.green}>
|
|
336
366
|
{SYMBOL.enter}
|
|
337
367
|
</Ink.Text>
|
|
338
368
|
),
|
|
369
|
+
esc: (
|
|
370
|
+
<Ink.Text bold color={colors.green}>
|
|
371
|
+
{SYMBOL.esc}
|
|
372
|
+
</Ink.Text>
|
|
373
|
+
),
|
|
339
374
|
}}
|
|
340
375
|
/>
|
|
341
376
|
</Parens>
|
|
@@ -351,6 +386,80 @@ function SelectCommitRangesInternal(props: Props) {
|
|
|
351
386
|
</React.Fragment>
|
|
352
387
|
)}
|
|
353
388
|
|
|
389
|
+
{!pr_select ? null : (
|
|
390
|
+
<React.Fragment>
|
|
391
|
+
<FormatText
|
|
392
|
+
wrapper={<Ink.Text color={colors.gray} />}
|
|
393
|
+
message="Pick an existing PR {note}"
|
|
394
|
+
values={{
|
|
395
|
+
note: (
|
|
396
|
+
<Parens>
|
|
397
|
+
<FormatText
|
|
398
|
+
message="press {enter} to select, {esc} to cancel"
|
|
399
|
+
values={{
|
|
400
|
+
enter: (
|
|
401
|
+
<Ink.Text bold color={colors.green}>
|
|
402
|
+
{SYMBOL.enter}
|
|
403
|
+
</Ink.Text>
|
|
404
|
+
),
|
|
405
|
+
esc: (
|
|
406
|
+
<Ink.Text bold color={colors.green}>
|
|
407
|
+
{SYMBOL.esc}
|
|
408
|
+
</Ink.Text>
|
|
409
|
+
),
|
|
410
|
+
}}
|
|
411
|
+
/>
|
|
412
|
+
</Parens>
|
|
413
|
+
),
|
|
414
|
+
}}
|
|
415
|
+
/>
|
|
416
|
+
|
|
417
|
+
<MultiSelect
|
|
418
|
+
withSelectPreview
|
|
419
|
+
maxWidth={max_width}
|
|
420
|
+
startIndex={existing_pr_list.length - 1}
|
|
421
|
+
onSelect={(args) => {
|
|
422
|
+
if (args.selected) {
|
|
423
|
+
const title = args.item.title;
|
|
424
|
+
const id = args.item.headRefName;
|
|
425
|
+
|
|
426
|
+
actions.output(
|
|
427
|
+
<FormatText
|
|
428
|
+
wrapper={<Ink.Text dimColor />}
|
|
429
|
+
message="Selected existing PR {title} {id}"
|
|
430
|
+
values={{
|
|
431
|
+
title: <Brackets>{title}</Brackets>,
|
|
432
|
+
id: <Parens>{id}</Parens>,
|
|
433
|
+
}}
|
|
434
|
+
/>,
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
// console.debug("submit_group_input", { title, id });
|
|
438
|
+
create_group({ id, title });
|
|
439
|
+
set_selected_group_id(id);
|
|
440
|
+
set_pr_select(false);
|
|
441
|
+
}
|
|
442
|
+
}}
|
|
443
|
+
items={existing_pr_list.map((pr) => {
|
|
444
|
+
return {
|
|
445
|
+
value: pr,
|
|
446
|
+
selected: false,
|
|
447
|
+
disabled: false,
|
|
448
|
+
label: (
|
|
449
|
+
<FormatText
|
|
450
|
+
message="{title} {ref}"
|
|
451
|
+
values={{
|
|
452
|
+
title: <Ink.Text>{pr.title}</Ink.Text>,
|
|
453
|
+
ref: <Ink.Text color={colors.gray}>{pr.headRefName}</Ink.Text>,
|
|
454
|
+
}}
|
|
455
|
+
/>
|
|
456
|
+
),
|
|
457
|
+
};
|
|
458
|
+
})}
|
|
459
|
+
/>
|
|
460
|
+
</React.Fragment>
|
|
461
|
+
)}
|
|
462
|
+
|
|
354
463
|
<Ink.Box height={1} />
|
|
355
464
|
|
|
356
465
|
<MultiSelect
|
|
@@ -396,7 +505,7 @@ function SelectCommitRangesInternal(props: Props) {
|
|
|
396
505
|
}}
|
|
397
506
|
/>
|
|
398
507
|
|
|
399
|
-
{
|
|
508
|
+
{is_input_mode ? null : (
|
|
400
509
|
<FormatText
|
|
401
510
|
wrapper={<Ink.Text color={colors.gray} />}
|
|
402
511
|
message="Press {c} to {create} a new PR"
|
|
@@ -415,6 +524,25 @@ function SelectCommitRangesInternal(props: Props) {
|
|
|
415
524
|
/>
|
|
416
525
|
)}
|
|
417
526
|
|
|
527
|
+
{is_input_mode ? null : (
|
|
528
|
+
<FormatText
|
|
529
|
+
wrapper={<Ink.Text color={colors.gray} />}
|
|
530
|
+
message="Press {p} to {pick} an existing PR"
|
|
531
|
+
values={{
|
|
532
|
+
p: (
|
|
533
|
+
<Ink.Text bold color={colors.green}>
|
|
534
|
+
p
|
|
535
|
+
</Ink.Text>
|
|
536
|
+
),
|
|
537
|
+
pick: (
|
|
538
|
+
<Ink.Text bold color={colors.green}>
|
|
539
|
+
<Parens>p</Parens>pick
|
|
540
|
+
</Ink.Text>
|
|
541
|
+
),
|
|
542
|
+
}}
|
|
543
|
+
/>
|
|
544
|
+
)}
|
|
545
|
+
|
|
418
546
|
{sync_status !== "allow_unassigned" ? null : (
|
|
419
547
|
<FormatText
|
|
420
548
|
wrapper={<Ink.Text color={colors.gray} />}
|
|
@@ -479,7 +607,7 @@ function SelectCommitRangesInternal(props: Props) {
|
|
|
479
607
|
/>
|
|
480
608
|
</Ink.Box>
|
|
481
609
|
|
|
482
|
-
{group.id ===
|
|
610
|
+
{group.id === commit_range.UNASSIGNED ? null : (
|
|
483
611
|
<Ink.Box>
|
|
484
612
|
<FormatText
|
|
485
613
|
wrapper={<Ink.Text color={colors.gray} />}
|
|
@@ -527,6 +655,8 @@ function SelectCommitRangesInternal(props: Props) {
|
|
|
527
655
|
}
|
|
528
656
|
|
|
529
657
|
function detect_sync_status() {
|
|
658
|
+
invariant(commit_range, "commit_range must exist");
|
|
659
|
+
|
|
530
660
|
if (!has_unassigned_commits) {
|
|
531
661
|
return "allow";
|
|
532
662
|
}
|
|
@@ -537,8 +667,8 @@ function SelectCommitRangesInternal(props: Props) {
|
|
|
537
667
|
|
|
538
668
|
let allow_unassigned_sync = null;
|
|
539
669
|
|
|
540
|
-
for (let i = 0; i <
|
|
541
|
-
const commit =
|
|
670
|
+
for (let i = 0; i < commit_range.commit_list.length; i++) {
|
|
671
|
+
const commit = commit_range.commit_list[i];
|
|
542
672
|
const group_id = commit_map.get(commit.sha);
|
|
543
673
|
// console.debug(commit.sha, group_id);
|
|
544
674
|
|
|
@@ -570,9 +700,11 @@ const SYMBOL = {
|
|
|
570
700
|
left: "←",
|
|
571
701
|
right: "→",
|
|
572
702
|
enter: "Enter",
|
|
703
|
+
esc: "Esc",
|
|
573
704
|
c: "c",
|
|
574
705
|
s: "s",
|
|
575
706
|
m: "m",
|
|
707
|
+
p: "p",
|
|
576
708
|
};
|
|
577
709
|
|
|
578
710
|
const S_TO_SYNC_VALUES = {
|
package/src/app/SyncGithub.tsx
CHANGED
|
@@ -240,8 +240,6 @@ async function run() {
|
|
|
240
240
|
if (!is_master_base(group)) {
|
|
241
241
|
// ensure base matches pr in github
|
|
242
242
|
await github.pr_edit({ branch: group.id, base: group.base });
|
|
243
|
-
} else {
|
|
244
|
-
await github.pr_edit({ branch: group.id });
|
|
245
243
|
}
|
|
246
244
|
} else {
|
|
247
245
|
// create pr in github
|
package/src/core/github.tsx
CHANGED
|
@@ -171,6 +171,10 @@ export async function pr_edit(args: EditPullRequestArgs) {
|
|
|
171
171
|
// const actions = state.actions;
|
|
172
172
|
// actions.debug(`github.pr_edit ${JSON.stringify(args)}`);
|
|
173
173
|
|
|
174
|
+
if (!args.base && !args.body) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
174
178
|
const command_parts = [`gh pr edit ${args.branch}`];
|
|
175
179
|
|
|
176
180
|
if (args.base) {
|