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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-stack-cli",
3
- "version": "2.8.1",
3
+ "version": "2.9.0",
4
4
  "description": "",
5
5
  "author": "magus",
6
6
  "license": "MIT",
@@ -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
- repo_path: /(?<repo_path>[^:^/]+\/[^/]+)\.git/,
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 = {
@@ -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: string;
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
- const active = i === index;
185
- const selected = selected_set.has(i);
186
- const disabled = item.disabled || false;
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: string;
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 as keyof typeof PR_TEMPLATE];
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(pr_template_fn(repo_root))) {
45
- pr_template_body = await fs.readFile(pr_template_fn(repo_root), "utf-8");
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
- if (await safe_exists(PR_TEMPLATE.TemplateDir(repo_root))) {
64
- pr_templates = await fs.readdir(PR_TEMPLATE.TemplateDir(repo_root));
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
- <FormatText
75
- wrapper={<Ink.Text color={colors.yellow} />}
76
- message="{count} queryable templates found under {dir}, but not supported."
77
- values={{
78
- count: <Ink.Text color={colors.blue}>{pr_templates.length}</Ink.Text>,
79
- dir: <Brackets>{PR_TEMPLATE.TemplateDir("")}</Brackets>,
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
- TemplateDir: (root: string) => path.join(root, ".github", "PULL_REQUEST_TEMPLATE"),
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
- const PR_TEMPLATE_KEY_LIST = Object.keys(PR_TEMPLATE) as Array<keyof typeof PR_TEMPLATE>;
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 commit_range = Store.useState((state) => state.commit_range);
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 = props.commit_range.group_list.find(
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 props.commit_range.UNASSIGNED;
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 props.commit_range.group_list) {
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 props.commit_range.commit_list) {
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
- // do not allow input when inputting group title
108
- if (group_input) {
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 = props.commit_range.UNASSIGNED;
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 !== props.commit_range.UNASSIGNED && input_lower === SYMBOL.m) {
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 (!props.commit_range.group_list.length) {
197
+ if (!commit_range.group_list.length) {
184
198
  return null;
185
199
  }
186
200
 
187
- const total_group_count = new_group_list.length + props.commit_range.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 < props.commit_range.group_list.length; i++) {
190
- const index = props.commit_range.group_list.length - i - 1;
191
- const group = props.commit_range.group_list[index];
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 === props.commit_range.UNASSIGNED) {
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 = group_input;
230
- const multiselect_disableSelect = group.id === props.commit_range.UNASSIGNED;
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 !== props.commit_range.UNASSIGNED;
247
+ const has_groups = group.id !== commit_range.UNASSIGNED;
234
248
 
235
- const items = props.commit_range.commit_list.map((commit) => {
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 (group_input) {
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 || group_input ? null : (
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 || group_input ? null : (
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
- {group_input ? null : (
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 === props.commit_range.UNASSIGNED ? null : (
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 < props.commit_range.commit_list.length; i++) {
541
- const commit = props.commit_range.commit_list[i];
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 = {
@@ -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
@@ -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) {