git-stack-cli 1.5.0 → 1.6.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 CHANGED
@@ -20,15 +20,18 @@
20
20
  > [!TIP]
21
21
  >
22
22
  > Install via **[Homebrew](https://brew.sh/)** to ensure the **[official Github CLI](https://cli.github.com/)** and **[git revise](https://github.com/mystor/git-revise)** dependencies are installed automatically
23
+ >
24
+ > ```bash
25
+ > brew install magus/git-stack/git-stack
26
+ > ```
23
27
 
24
- ```bash
25
- brew tap magus/git-stack
26
- brew install git-stack
27
- ```
28
+ <details>
28
29
 
29
- ### `npm`
30
+ <summary>
31
+ npm alternative
32
+ </summary>
30
33
 
31
- Installing via **[npm](https://www.npmjs.com/)** requires installing the **[official Github CLI](https://cli.github.com/)** and **[git revise](https://github.com/mystor/git-revise)** dependencies separarely
34
+ If you prefer to use **[npm](https://www.npmjs.com/)** you will need to install the **[official Github CLI](https://cli.github.com/)** and **[git revise](https://github.com/mystor/git-revise)** dependencies separarely
32
35
 
33
36
  ```bash
34
37
  brew install gh
@@ -37,6 +40,8 @@ brew install git-revise
37
40
  npm i -g git-stack-cli
38
41
  ```
39
42
 
43
+ </details>
44
+
40
45
  ## Usage
41
46
 
42
47
  ```bash
@@ -76,6 +81,8 @@ Managing even a few stacked diffs requires a relatively strong knowledge of `git
76
81
 
77
82
  ## Development
78
83
 
84
+ Ensure `node --version` is the same across both projects you are using to test the `git-stack` cli
85
+
79
86
  ```bash
80
87
  git submodule update --init --recursive
81
88
  npm i
@@ -27800,7 +27800,8 @@ function GitReviseTodo(args) {
27800
27800
  for (const commit of group.commits) {
27801
27801
  // update git commit message with stack id
27802
27802
  const metadata = { id: group.id, title: group.title };
27803
- const message_with_id = write$1(commit.full_message, metadata);
27803
+ const unsafe_message_with_id = write$1(commit.full_message, metadata);
27804
+ const message_with_id = unsafe_message_with_id.replace(/"/g, '\\"');
27804
27805
  // get first 12 characters of commit sha
27805
27806
  const sha = commit.sha.slice(0, 12);
27806
27807
  // generate git revise entry
@@ -27816,9 +27817,13 @@ function GitReviseTodo(args) {
27816
27817
  function write(args) {
27817
27818
  const stack_table = table(args);
27818
27819
  let result = args.body;
27819
- if (RE.stack_table.test(result)) {
27820
+ if (RE.stack_table_link.test(result)) {
27820
27821
  // replace stack table
27821
- result = result.replace(RE.stack_table, stack_table);
27822
+ result = result.replace(RE.stack_table_link, stack_table);
27823
+ }
27824
+ else if (RE.stack_table_legacy.test(result)) {
27825
+ // replace stack table
27826
+ result = result.replace(RE.stack_table_legacy, stack_table);
27822
27827
  }
27823
27828
  else {
27824
27829
  // append stack table
@@ -27861,10 +27866,13 @@ function table(args) {
27861
27866
  if (!stack_list.length) {
27862
27867
  return "";
27863
27868
  }
27864
- return TEMPLATE.stack_table(["", ...stack_list, "", ""].join("\n"));
27869
+ return TEMPLATE.stack_table_link(["", ...stack_list, "", ""].join("\n"));
27865
27870
  }
27866
27871
  function parse(body) {
27867
- const stack_table_match = body.match(RE.stack_table);
27872
+ let stack_table_match = body.match(RE.stack_table_link);
27873
+ if (!stack_table_match?.groups) {
27874
+ stack_table_match = body.match(RE.stack_table_legacy);
27875
+ }
27868
27876
  if (!stack_table_match?.groups) {
27869
27877
  return new Map();
27870
27878
  }
@@ -27878,26 +27886,39 @@ function parse(body) {
27878
27886
  // skip invalid row
27879
27887
  continue;
27880
27888
  }
27889
+ if (!RE.pr_url.test(parsed_row.pr_url)) {
27890
+ continue;
27891
+ }
27881
27892
  result.set(parsed_row.pr_url, parsed_row);
27882
27893
  }
27883
27894
  return result;
27884
27895
  }
27885
27896
  const TEMPLATE = {
27886
- stack_table(rows) {
27897
+ stack_table_legacy(rows) {
27887
27898
  return `#### git stack${rows}`;
27888
27899
  },
27900
+ stack_table_link(rows) {
27901
+ return `#### [git stack](https://github.com/magus/git-stack-cli)${rows}`;
27902
+ },
27889
27903
  row(args) {
27890
27904
  return `- ${args.icon} \`${args.num}\` ${args.pr_url}`;
27891
27905
  },
27892
27906
  };
27893
27907
  const RE = {
27894
27908
  // https://regex101.com/r/kqB9Ft/1
27895
- stack_table: new RegExp(TEMPLATE.stack_table("\\s+(?<rows>(?:- [^\r^\n]*(?:[\r\n]+)?)+)")),
27909
+ stack_table_legacy: new RegExp(TEMPLATE.stack_table_legacy("\\s+(?<rows>(?:- [^\r^\n]*(?:[\r\n]+)?)+)")),
27910
+ stack_table_link: new RegExp(TEMPLATE.stack_table_link("ROWS")
27911
+ .replace("[", "\\[")
27912
+ .replace("]", "\\]")
27913
+ .replace("(", "\\(")
27914
+ .replace(")", "\\)")
27915
+ .replace("ROWS", "\\s+(?<rows>(?:- [^\r^\n]*(?:[\r\n]+)?)+)")),
27896
27916
  row: new RegExp(TEMPLATE.row({
27897
27917
  icon: "(?<icon>.+)",
27898
27918
  num: "(?<num>\\d+)",
27899
27919
  pr_url: "(?<pr_url>.+)",
27900
27920
  })),
27921
+ pr_url: /^https:\/\/.*$/,
27901
27922
  };
27902
27923
 
27903
27924
  function ManualRebase() {
@@ -28495,7 +28516,8 @@ function MultiSelect(props) {
28495
28516
  });
28496
28517
  // clamp index to keep in item range
28497
28518
  const [index, set_index] = reactExports.useReducer((_, value) => {
28498
- return clamp(value, 0, props.items.length - 1);
28519
+ const next_index = clamp(value, 0, props.items.length - 1);
28520
+ return next_index;
28499
28521
  }, 0, function find_initial_index() {
28500
28522
  let last_enabled;
28501
28523
  for (let i = props.items.length - 1; i >= 0; i--) {
@@ -28522,9 +28544,17 @@ function MultiSelect(props) {
28522
28544
  const selected_list = Array.from(selected_set);
28523
28545
  const selected = selected_set.has(index);
28524
28546
  const state = selected_list.map((index) => props.items[index].value);
28525
- // console.debug({ item, selected, state });
28547
+ // console.debug("onSelect", { item, selected, state });
28526
28548
  props.onSelect({ item, selected, state });
28527
28549
  }, [selected_set]);
28550
+ reactExports.useEffect(() => {
28551
+ const item = props.items[index].value;
28552
+ const selected_list = Array.from(selected_set);
28553
+ const selected = selected_set.has(index);
28554
+ const state = selected_list.map((index) => props.items[index].value);
28555
+ // console.debug("onFocus", { item, selected, state });
28556
+ props.onFocus?.({ item, selected, state });
28557
+ }, [index]);
28528
28558
  useInput((input, key) => {
28529
28559
  if (props.disabled) {
28530
28560
  // console.debug("[MultiSelect] disabled, ignoring input");
@@ -28659,7 +28689,7 @@ function TextInput(props) {
28659
28689
  reactExports.createElement(Text, { color: colors.yellow, dimColor: true, inverse: caret_visible }, " ")));
28660
28690
  }
28661
28691
  function get_value(props) {
28662
- return props.value || "";
28692
+ return props.value || props.defaultValue || "";
28663
28693
  }
28664
28694
 
28665
28695
  function SelectCommitRanges() {
@@ -28790,9 +28820,13 @@ function SelectCommitRangesInternal(props) {
28790
28820
  group_title_width = Math.min(group.title.length, group_title_width);
28791
28821
  let max_item_width = max_group_label_width;
28792
28822
  max_item_width -= left_arrow.length + right_arrow.length;
28823
+ const [focused, set_focused] = reactExports.useState("");
28793
28824
  return (reactExports.createElement(Box, { flexDirection: "column" },
28794
28825
  reactExports.createElement(Box, { height: 1 }),
28795
- reactExports.createElement(MultiSelect, { key: group.id, items: items, maxWidth: max_item_width, disabled: group_input, onSelect: (args) => {
28826
+ reactExports.createElement(MultiSelect, { key: group.id, items: items, maxWidth: max_item_width, disabled: group_input, onFocus: (args) => {
28827
+ // console.debug("onFocus", args);
28828
+ set_focused(args.item.subject_line);
28829
+ }, onSelect: (args) => {
28796
28830
  // console.debug("onSelect", args);
28797
28831
  const key = args.item.sha;
28798
28832
  let value;
@@ -28837,7 +28871,7 @@ function SelectCommitRangesInternal(props) {
28837
28871
  enter: (reactExports.createElement(Text, { bold: true, color: colors.green }, SYMBOL.enter)),
28838
28872
  } }))),
28839
28873
  } }),
28840
- reactExports.createElement(TextInput, { onSubmit: submit_group_input }),
28874
+ reactExports.createElement(TextInput, { defaultValue: focused, onSubmit: submit_group_input }),
28841
28875
  reactExports.createElement(Box, { height: 1 }))),
28842
28876
  reactExports.createElement(Box, null,
28843
28877
  reactExports.createElement(FormatText, { wrapper: reactExports.createElement(Text, { color: colors.gray }), message: "Press {left} and {right} to view PR groups", values: {
@@ -34418,7 +34452,7 @@ async function command() {
34418
34452
  .wrap(123)
34419
34453
  // disallow unknown options
34420
34454
  .strict()
34421
- .version("1.5.0" )
34455
+ .version("1.6.0" )
34422
34456
  .showHidden("show-hidden", "Show hidden options via `git stack help --show-hidden`")
34423
34457
  .help("help", "Show usage via `git stack help`").argv);
34424
34458
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-stack-cli",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "",
5
5
  "author": "magus",
6
6
  "license": "MIT",
@@ -9,7 +9,8 @@ import { wrap_index } from "~/core/wrap_index";
9
9
 
10
10
  type Props<T> = {
11
11
  items: Array<Item<T>>;
12
- onSelect(args: SelectArgs<T>): void;
12
+ onSelect: (args: CallbackArgs<T>) => void;
13
+ onFocus?: (args: CallbackArgs<T>) => void;
13
14
  disabled?: boolean;
14
15
  maxWidth?: number;
15
16
  };
@@ -21,7 +22,7 @@ type Item<T> = {
21
22
  disabled?: ItemRowProps["disabled"];
22
23
  };
23
24
 
24
- type SelectArgs<T> = {
25
+ type CallbackArgs<T> = {
25
26
  item: T;
26
27
  selected: boolean;
27
28
  state: Array<T>;
@@ -55,7 +56,8 @@ export function MultiSelect<T>(props: Props<T>) {
55
56
  // clamp index to keep in item range
56
57
  const [index, set_index] = React.useReducer(
57
58
  (_: unknown, value: number) => {
58
- return clamp(value, 0, props.items.length - 1);
59
+ const next_index = clamp(value, 0, props.items.length - 1);
60
+ return next_index;
59
61
  },
60
62
  0,
61
63
  function find_initial_index() {
@@ -94,10 +96,20 @@ export function MultiSelect<T>(props: Props<T>) {
94
96
  const selected = selected_set.has(index);
95
97
  const state = selected_list.map((index) => props.items[index].value);
96
98
 
97
- // console.debug({ item, selected, state });
99
+ // console.debug("onSelect", { item, selected, state });
98
100
  props.onSelect({ item, selected, state });
99
101
  }, [selected_set]);
100
102
 
103
+ React.useEffect(() => {
104
+ const item = props.items[index].value;
105
+ const selected_list = Array.from(selected_set);
106
+ const selected = selected_set.has(index);
107
+ const state = selected_list.map((index) => props.items[index].value);
108
+
109
+ // console.debug("onFocus", { item, selected, state });
110
+ props.onFocus?.({ item, selected, state });
111
+ }, [index]);
112
+
101
113
  Ink.useInput((input, key) => {
102
114
  if (props.disabled) {
103
115
  // console.debug("[MultiSelect] disabled, ignoring input");
@@ -199,6 +199,8 @@ function SelectCommitRangesInternal(props: Props) {
199
199
  let max_item_width = max_group_label_width;
200
200
  max_item_width -= left_arrow.length + right_arrow.length;
201
201
 
202
+ const [focused, set_focused] = React.useState("");
203
+
202
204
  return (
203
205
  <Ink.Box flexDirection="column">
204
206
  <Ink.Box height={1} />
@@ -208,6 +210,11 @@ function SelectCommitRangesInternal(props: Props) {
208
210
  items={items}
209
211
  maxWidth={max_item_width}
210
212
  disabled={group_input}
213
+ onFocus={(args) => {
214
+ // console.debug("onFocus", args);
215
+
216
+ set_focused(args.item.subject_line);
217
+ }}
211
218
  onSelect={(args) => {
212
219
  // console.debug("onSelect", args);
213
220
 
@@ -326,7 +333,7 @@ function SelectCommitRangesInternal(props: Props) {
326
333
  }}
327
334
  />
328
335
 
329
- <TextInput onSubmit={submit_group_input} />
336
+ <TextInput defaultValue={focused} onSubmit={submit_group_input} />
330
337
 
331
338
  <Ink.Box height={1} />
332
339
  </React.Fragment>
@@ -6,6 +6,7 @@ import { colors } from "~/core/colors";
6
6
 
7
7
  type Props = {
8
8
  multiline?: boolean;
9
+ defaultValue?: string;
9
10
  value?: string;
10
11
  onChange?: (value: string) => void;
11
12
  onSubmit?: (value: string) => void;
@@ -84,5 +85,5 @@ export function TextInput(props: Props) {
84
85
  }
85
86
 
86
87
  function get_value(props: Props) {
87
- return props.value || "";
88
+ return props.value || props.defaultValue || "";
88
89
  }
@@ -57,6 +57,24 @@ test("git-revise-todo from commit range with single new commit in new group", ()
57
57
  );
58
58
  });
59
59
 
60
+ test("git-revise-todo handles double quotes in commit message", () => {
61
+ const rebase_group_index = 0;
62
+ const commit_range = COMMIT_MESSAGE_WITH_QUOTES;
63
+
64
+ const git_revise_todo = GitReviseTodo({ rebase_group_index, commit_range });
65
+
66
+ expect(git_revise_todo).toBe(
67
+ [
68
+ //force line break
69
+ "++ pick f143d03c723c",
70
+ '[new] invalid \\\\"by me\\\\" quotes',
71
+ "",
72
+ "git-stack-id: 6Ak-qn+5Z",
73
+ 'git-stack-title: [new] invalid \\"by me\\" quotes',
74
+ ].join("\n")
75
+ );
76
+ });
77
+
60
78
  const SINGLE_COMMIT_EXISTING_GROUP: CommitMetadata.CommitRange = {
61
79
  "invalid": false,
62
80
  "group_list": [
@@ -595,3 +613,36 @@ const SINGLE_COMMIT_NEW_GROUP: CommitMetadata.CommitRange = {
595
613
  },
596
614
  "UNASSIGNED": "unassigned",
597
615
  };
616
+
617
+ const COMMIT_MESSAGE_WITH_QUOTES: CommitMetadata.CommitRange = {
618
+ "invalid": false,
619
+ "group_list": [
620
+ {
621
+ "id": "6Ak-qn+5Z",
622
+ "title": '[new] invalid "by me" quotes',
623
+ "pr": null,
624
+ "base": "E63ytp5dj",
625
+ "dirty": true,
626
+ "commits": [
627
+ {
628
+ "sha": "f143d03c723c9f5231a81c1e12098511611898cb",
629
+ "full_message": '[new] invalid "by me" quotes',
630
+ "subject_line": '[new] invalid "by me" quotes',
631
+ "branch_id": null,
632
+ "title": null,
633
+ },
634
+ ],
635
+ },
636
+ ],
637
+ "commit_list": [
638
+ {
639
+ "sha": "f143d03c723c9f5231a81c1e12098511611898cb",
640
+ "full_message": '[new] invalid "by me" quotes',
641
+ "subject_line": '[new] invalid "by me" quotes',
642
+ "branch_id": null,
643
+ "title": null,
644
+ },
645
+ ],
646
+ "pr_lookup": {},
647
+ "UNASSIGNED": "unassigned",
648
+ };
@@ -57,7 +57,11 @@ export function GitReviseTodo(args: Args): string {
57
57
  for (const commit of group.commits) {
58
58
  // update git commit message with stack id
59
59
  const metadata = { id: group.id, title: group.title };
60
- const message_with_id = Metadata.write(commit.full_message, metadata);
60
+ const unsafe_message_with_id = Metadata.write(
61
+ commit.full_message,
62
+ metadata
63
+ );
64
+ const message_with_id = unsafe_message_with_id.replace(/"/g, '\\"');
61
65
 
62
66
  // get first 12 characters of commit sha
63
67
  const sha = commit.sha.slice(0, 12);
@@ -79,7 +79,7 @@ test("builds list of prs with selected emoji", () => {
79
79
  expect(output.split("\n")).toEqual([
80
80
  ...args.body.split("\n"),
81
81
  "",
82
- "#### git stack",
82
+ "#### [git stack](https://github.com/magus/git-stack-cli)",
83
83
  "- 👉 `1` https://github.com/magus/git-multi-diff-playground/pull/43",
84
84
  "- ⏳ `2` https://github.com/magus/git-multi-diff-playground/pull/47",
85
85
  ]);
@@ -89,7 +89,7 @@ test("can parse stack table from body", () => {
89
89
  const body_line_list = [
90
90
  "",
91
91
  "",
92
- "#### git stack",
92
+ "#### [git stack](https://github.com/magus/git-stack-cli)",
93
93
  "- invalid line that will be dropped",
94
94
  "- ⏳ `2` https://github.com/magus/git-multi-diff-playground/pull/47",
95
95
  "- 👉 `1` https://github.com/magus/git-multi-diff-playground/pull/43",
@@ -122,7 +122,7 @@ test("persists removed pr urls from previous stack table", () => {
122
122
  body: [
123
123
  "Summary of problem",
124
124
  "",
125
- "#### git stack",
125
+ "#### [git stack](https://github.com/magus/git-stack-cli)",
126
126
  "- ⏳ `1` https://github.com/magus/git-multi-diff-playground/pull/43",
127
127
  "- ⏳ `2` https://github.com/magus/git-multi-diff-playground/pull/44",
128
128
  "- 👉 `3` https://github.com/magus/git-multi-diff-playground/pull/47",
@@ -142,7 +142,7 @@ test("persists removed pr urls from previous stack table", () => {
142
142
  expect(output.split("\n")).toEqual([
143
143
  "Summary of problem",
144
144
  "",
145
- "#### git stack",
145
+ "#### [git stack](https://github.com/magus/git-stack-cli)",
146
146
  "- ✅ `1` https://github.com/magus/git-multi-diff-playground/pull/43",
147
147
  "- ✅ `2` https://github.com/magus/git-multi-diff-playground/pull/44",
148
148
  "- 👉 `3` https://github.com/magus/git-multi-diff-playground/pull/47",
@@ -155,3 +155,106 @@ test("persists removed pr urls from previous stack table", () => {
155
155
 
156
156
  expect(rerun_output).toBe(output);
157
157
  });
158
+
159
+ test("persist only valid urls, removed broken branch ids from interrupted sync", () => {
160
+ const args = {
161
+ body: [
162
+ "Summary of problem",
163
+ "",
164
+ "#### [git stack](https://github.com/magus/git-stack-cli)",
165
+ "- ✅ `1` https://github.com/magus/git-multi-diff-playground/pull/43",
166
+ "- ✅ `2` gs-P4EBkJm+q",
167
+ "- 👉 `3` https://github.com/magus/git-multi-diff-playground/pull/47",
168
+ ].join("\n"),
169
+
170
+ pr_url_list: [
171
+ "https://github.com/magus/git-multi-diff-playground/pull/47",
172
+ "https://github.com/magus/git-multi-diff-playground/pull/54",
173
+ "https://github.com/magus/git-multi-diff-playground/pull/61",
174
+ ],
175
+
176
+ selected_url: "https://github.com/magus/git-multi-diff-playground/pull/47",
177
+ };
178
+
179
+ const output = StackSummaryTable.write(args);
180
+
181
+ expect(output.split("\n")).toEqual([
182
+ "Summary of problem",
183
+ "",
184
+ "#### [git stack](https://github.com/magus/git-stack-cli)",
185
+ "- ✅ `1` https://github.com/magus/git-multi-diff-playground/pull/43",
186
+ "- 👉 `2` https://github.com/magus/git-multi-diff-playground/pull/47",
187
+ "- ⏳ `3` https://github.com/magus/git-multi-diff-playground/pull/54",
188
+ "- ⏳ `4` https://github.com/magus/git-multi-diff-playground/pull/61",
189
+ ]);
190
+
191
+ // run again on the output to make sure it doesn't change
192
+ const rerun_output = StackSummaryTable.write({ ...args, body: output });
193
+
194
+ expect(rerun_output).toBe(output);
195
+ });
196
+
197
+ test("can parse legacy git stack", () => {
198
+ const body_line_list = [
199
+ "",
200
+ "",
201
+ "#### git stack",
202
+ "- invalid line that will be dropped",
203
+ "- ⏳ `2` https://github.com/magus/git-multi-diff-playground/pull/47",
204
+ "- 👉 `1` https://github.com/magus/git-multi-diff-playground/pull/43",
205
+ ];
206
+
207
+ const parsed = StackSummaryTable.parse(body_line_list.join("\n"));
208
+
209
+ expect(Array.from(parsed.entries())).toEqual([
210
+ [
211
+ "https://github.com/magus/git-multi-diff-playground/pull/47",
212
+ {
213
+ icon: "⏳",
214
+ num: "2",
215
+ pr_url: "https://github.com/magus/git-multi-diff-playground/pull/47",
216
+ },
217
+ ],
218
+ [
219
+ "https://github.com/magus/git-multi-diff-playground/pull/43",
220
+ {
221
+ icon: "👉",
222
+ num: "1",
223
+ pr_url: "https://github.com/magus/git-multi-diff-playground/pull/43",
224
+ },
225
+ ],
226
+ ]);
227
+ });
228
+
229
+ test("converts legacy git stack to link version", () => {
230
+ const args = {
231
+ body: [
232
+ "Summary of problem",
233
+ "",
234
+ "#### git stack",
235
+ "- ✅ `1` https://github.com/magus/git-multi-diff-playground/pull/43",
236
+ "- ✅ `2` gs-P4EBkJm+q",
237
+ "- 👉 `3` https://github.com/magus/git-multi-diff-playground/pull/47",
238
+ ].join("\n"),
239
+
240
+ pr_url_list: [
241
+ "https://github.com/magus/git-multi-diff-playground/pull/47",
242
+ "https://github.com/magus/git-multi-diff-playground/pull/54",
243
+ "https://github.com/magus/git-multi-diff-playground/pull/61",
244
+ ],
245
+
246
+ selected_url: "https://github.com/magus/git-multi-diff-playground/pull/47",
247
+ };
248
+
249
+ const output = StackSummaryTable.write(args);
250
+
251
+ expect(output.split("\n")).toEqual([
252
+ "Summary of problem",
253
+ "",
254
+ "#### [git stack](https://github.com/magus/git-stack-cli)",
255
+ "- ✅ `1` https://github.com/magus/git-multi-diff-playground/pull/43",
256
+ "- 👉 `2` https://github.com/magus/git-multi-diff-playground/pull/47",
257
+ "- ⏳ `3` https://github.com/magus/git-multi-diff-playground/pull/54",
258
+ "- ⏳ `4` https://github.com/magus/git-multi-diff-playground/pull/61",
259
+ ]);
260
+ });
@@ -9,9 +9,12 @@ export function write(args: WriteArgs) {
9
9
 
10
10
  let result = args.body;
11
11
 
12
- if (RE.stack_table.test(result)) {
12
+ if (RE.stack_table_link.test(result)) {
13
13
  // replace stack table
14
- result = result.replace(RE.stack_table, stack_table);
14
+ result = result.replace(RE.stack_table_link, stack_table);
15
+ } else if (RE.stack_table_legacy.test(result)) {
16
+ // replace stack table
17
+ result = result.replace(RE.stack_table_legacy, stack_table);
15
18
  } else {
16
19
  // append stack table
17
20
  result = `${result}\n\n${stack_table}`;
@@ -64,11 +67,15 @@ export function table(args: WriteArgs) {
64
67
  return "";
65
68
  }
66
69
 
67
- return TEMPLATE.stack_table(["", ...stack_list, "", ""].join("\n"));
70
+ return TEMPLATE.stack_table_link(["", ...stack_list, "", ""].join("\n"));
68
71
  }
69
72
 
70
73
  export function parse(body: string): Map<string, StackTableRow> {
71
- const stack_table_match = body.match(RE.stack_table);
74
+ let stack_table_match = body.match(RE.stack_table_link);
75
+
76
+ if (!stack_table_match?.groups) {
77
+ stack_table_match = body.match(RE.stack_table_legacy);
78
+ }
72
79
 
73
80
  if (!stack_table_match?.groups) {
74
81
  return new Map();
@@ -88,6 +95,10 @@ export function parse(body: string): Map<string, StackTableRow> {
88
95
  continue;
89
96
  }
90
97
 
98
+ if (!RE.pr_url.test(parsed_row.pr_url)) {
99
+ continue;
100
+ }
101
+
91
102
  result.set(parsed_row.pr_url, parsed_row);
92
103
  }
93
104
 
@@ -95,10 +106,14 @@ export function parse(body: string): Map<string, StackTableRow> {
95
106
  }
96
107
 
97
108
  const TEMPLATE = {
98
- stack_table(rows: string) {
109
+ stack_table_legacy(rows: string) {
99
110
  return `#### git stack${rows}`;
100
111
  },
101
112
 
113
+ stack_table_link(rows: string) {
114
+ return `#### [git stack](https://github.com/magus/git-stack-cli)${rows}`;
115
+ },
116
+
102
117
  row(args: StackTableRow) {
103
118
  return `- ${args.icon} \`${args.num}\` ${args.pr_url}`;
104
119
  },
@@ -106,8 +121,17 @@ const TEMPLATE = {
106
121
 
107
122
  const RE = {
108
123
  // https://regex101.com/r/kqB9Ft/1
109
- stack_table: new RegExp(
110
- TEMPLATE.stack_table("\\s+(?<rows>(?:- [^\r^\n]*(?:[\r\n]+)?)+)")
124
+ stack_table_legacy: new RegExp(
125
+ TEMPLATE.stack_table_legacy("\\s+(?<rows>(?:- [^\r^\n]*(?:[\r\n]+)?)+)")
126
+ ),
127
+
128
+ stack_table_link: new RegExp(
129
+ TEMPLATE.stack_table_link("ROWS")
130
+ .replace("[", "\\[")
131
+ .replace("]", "\\]")
132
+ .replace("(", "\\(")
133
+ .replace(")", "\\)")
134
+ .replace("ROWS", "\\s+(?<rows>(?:- [^\r^\n]*(?:[\r\n]+)?)+)")
111
135
  ),
112
136
 
113
137
  row: new RegExp(
@@ -117,6 +141,8 @@ const RE = {
117
141
  pr_url: "(?<pr_url>.+)",
118
142
  })
119
143
  ),
144
+
145
+ pr_url: /^https:\/\/.*$/,
120
146
  };
121
147
 
122
148
  type StackTableRow = {