git-stack-cli 1.5.1 → 1.6.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 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
  }
@@ -27886,16 +27894,25 @@ function parse(body) {
27886
27894
  return result;
27887
27895
  }
27888
27896
  const TEMPLATE = {
27889
- stack_table(rows) {
27897
+ stack_table_legacy(rows) {
27890
27898
  return `#### git stack${rows}`;
27891
27899
  },
27900
+ stack_table_link(rows) {
27901
+ return `#### [git stack](https://github.com/magus/git-stack-cli)${rows}`;
27902
+ },
27892
27903
  row(args) {
27893
27904
  return `- ${args.icon} \`${args.num}\` ${args.pr_url}`;
27894
27905
  },
27895
27906
  };
27896
27907
  const RE = {
27897
27908
  // https://regex101.com/r/kqB9Ft/1
27898
- 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]+)?)+)")),
27899
27916
  row: new RegExp(TEMPLATE.row({
27900
27917
  icon: "(?<icon>.+)",
27901
27918
  num: "(?<num>\\d+)",
@@ -28499,7 +28516,8 @@ function MultiSelect(props) {
28499
28516
  });
28500
28517
  // clamp index to keep in item range
28501
28518
  const [index, set_index] = reactExports.useReducer((_, value) => {
28502
- return clamp(value, 0, props.items.length - 1);
28519
+ const next_index = clamp(value, 0, props.items.length - 1);
28520
+ return next_index;
28503
28521
  }, 0, function find_initial_index() {
28504
28522
  let last_enabled;
28505
28523
  for (let i = props.items.length - 1; i >= 0; i--) {
@@ -28526,9 +28544,17 @@ function MultiSelect(props) {
28526
28544
  const selected_list = Array.from(selected_set);
28527
28545
  const selected = selected_set.has(index);
28528
28546
  const state = selected_list.map((index) => props.items[index].value);
28529
- // console.debug({ item, selected, state });
28547
+ // console.debug("onSelect", { item, selected, state });
28530
28548
  props.onSelect({ item, selected, state });
28531
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]);
28532
28558
  useInput((input, key) => {
28533
28559
  if (props.disabled) {
28534
28560
  // console.debug("[MultiSelect] disabled, ignoring input");
@@ -28663,7 +28689,7 @@ function TextInput(props) {
28663
28689
  reactExports.createElement(Text, { color: colors.yellow, dimColor: true, inverse: caret_visible }, " ")));
28664
28690
  }
28665
28691
  function get_value(props) {
28666
- return props.value || "";
28692
+ return props.value || props.defaultValue || "";
28667
28693
  }
28668
28694
 
28669
28695
  function SelectCommitRanges() {
@@ -28674,10 +28700,17 @@ function SelectCommitRanges() {
28674
28700
  function SelectCommitRangesInternal(props) {
28675
28701
  const actions = Store.useActions();
28676
28702
  const argv = Store.useState((state) => state.argv);
28677
- const [selected_group_id, set_selected_group_id] = reactExports.useState(props.commit_range.UNASSIGNED);
28703
+ const [selected_group_id, set_selected_group_id] = reactExports.useState(() => {
28704
+ const first_group = props.commit_range.group_list.find((g) => g.id !== props.commit_range.UNASSIGNED);
28705
+ if (first_group) {
28706
+ return first_group.id;
28707
+ }
28708
+ return props.commit_range.UNASSIGNED;
28709
+ });
28678
28710
  const [group_input, set_group_input] = reactExports.useState(false);
28679
28711
  const [new_group_list, create_group] = reactExports.useReducer((group_list, group) => {
28680
- return group_list.concat(group);
28712
+ const next_group_list = group_list.concat(group);
28713
+ return next_group_list;
28681
28714
  }, []);
28682
28715
  const [commit_map, update_commit_map] = reactExports.useReducer((map, args) => {
28683
28716
  map.set(args.key, args.value);
@@ -28698,9 +28731,10 @@ function SelectCommitRangesInternal(props) {
28698
28731
  unassigned_count++;
28699
28732
  }
28700
28733
  }
28701
- group_list.push(...new_group_list);
28702
28734
  const total_group_count = new_group_list.length + props.commit_range.group_list.length;
28703
- for (const group of props.commit_range.group_list) {
28735
+ for (let i = 0; i < props.commit_range.group_list.length; i++) {
28736
+ const index = props.commit_range.group_list.length - i - 1;
28737
+ const group = props.commit_range.group_list[index];
28704
28738
  if (group.pr?.state === "MERGED")
28705
28739
  continue;
28706
28740
  if (group.id === props.commit_range.UNASSIGNED) {
@@ -28718,6 +28752,7 @@ function SelectCommitRangesInternal(props) {
28718
28752
  title: group.pr?.title || group.title || group.id,
28719
28753
  });
28720
28754
  }
28755
+ group_list.push(...new_group_list);
28721
28756
  let current_index = group_list.findIndex((g) => g.id === selected_group_id);
28722
28757
  if (current_index === -1) {
28723
28758
  current_index = 0;
@@ -28794,9 +28829,13 @@ function SelectCommitRangesInternal(props) {
28794
28829
  group_title_width = Math.min(group.title.length, group_title_width);
28795
28830
  let max_item_width = max_group_label_width;
28796
28831
  max_item_width -= left_arrow.length + right_arrow.length;
28832
+ const [focused, set_focused] = reactExports.useState("");
28797
28833
  return (reactExports.createElement(Box, { flexDirection: "column" },
28798
28834
  reactExports.createElement(Box, { height: 1 }),
28799
- reactExports.createElement(MultiSelect, { key: group.id, items: items, maxWidth: max_item_width, disabled: group_input, onSelect: (args) => {
28835
+ reactExports.createElement(MultiSelect, { key: group.id, items: items, maxWidth: max_item_width, disabled: group_input, onFocus: (args) => {
28836
+ // console.debug("onFocus", args);
28837
+ set_focused(args.item.subject_line);
28838
+ }, onSelect: (args) => {
28800
28839
  // console.debug("onSelect", args);
28801
28840
  const key = args.item.sha;
28802
28841
  let value;
@@ -28841,7 +28880,7 @@ function SelectCommitRangesInternal(props) {
28841
28880
  enter: (reactExports.createElement(Text, { bold: true, color: colors.green }, SYMBOL.enter)),
28842
28881
  } }))),
28843
28882
  } }),
28844
- reactExports.createElement(TextInput, { onSubmit: submit_group_input }),
28883
+ reactExports.createElement(TextInput, { defaultValue: focused, onSubmit: submit_group_input }),
28845
28884
  reactExports.createElement(Box, { height: 1 }))),
28846
28885
  reactExports.createElement(Box, null,
28847
28886
  reactExports.createElement(FormatText, { wrapper: reactExports.createElement(Text, { color: colors.gray }), message: "Press {left} and {right} to view PR groups", values: {
@@ -34422,7 +34461,7 @@ async function command() {
34422
34461
  .wrap(123)
34423
34462
  // disallow unknown options
34424
34463
  .strict()
34425
- .version("1.5.1" )
34464
+ .version("1.6.1" )
34426
34465
  .showHidden("show-hidden", "Show hidden options via `git stack help --show-hidden`")
34427
34466
  .help("help", "Show usage via `git stack help`").argv);
34428
34467
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-stack-cli",
3
- "version": "1.5.1",
3
+ "version": "1.6.1",
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");
@@ -35,15 +35,24 @@ function SelectCommitRangesInternal(props: Props) {
35
35
 
36
36
  const argv = Store.useState((state) => state.argv);
37
37
 
38
- const [selected_group_id, set_selected_group_id] = React.useState(
39
- props.commit_range.UNASSIGNED
40
- );
38
+ const [selected_group_id, set_selected_group_id] = React.useState(() => {
39
+ const first_group = props.commit_range.group_list.find(
40
+ (g) => g.id !== props.commit_range.UNASSIGNED
41
+ );
42
+
43
+ if (first_group) {
44
+ return first_group.id;
45
+ }
46
+
47
+ return props.commit_range.UNASSIGNED;
48
+ });
41
49
 
42
50
  const [group_input, set_group_input] = React.useState(false);
43
51
 
44
52
  const [new_group_list, create_group] = React.useReducer(
45
53
  (group_list: Array<SimpleGroup>, group: SimpleGroup) => {
46
- return group_list.concat(group);
54
+ const next_group_list = group_list.concat(group);
55
+ return next_group_list;
47
56
  },
48
57
  []
49
58
  );
@@ -79,12 +88,13 @@ function SelectCommitRangesInternal(props: Props) {
79
88
  }
80
89
  }
81
90
 
82
- group_list.push(...new_group_list);
83
-
84
91
  const total_group_count =
85
92
  new_group_list.length + props.commit_range.group_list.length;
86
93
 
87
- for (const group of props.commit_range.group_list) {
94
+ for (let i = 0; i < props.commit_range.group_list.length; i++) {
95
+ const index = props.commit_range.group_list.length - i - 1;
96
+ const group = props.commit_range.group_list[index];
97
+
88
98
  if (group.pr?.state === "MERGED") continue;
89
99
 
90
100
  if (group.id === props.commit_range.UNASSIGNED) {
@@ -105,6 +115,8 @@ function SelectCommitRangesInternal(props: Props) {
105
115
  });
106
116
  }
107
117
 
118
+ group_list.push(...new_group_list);
119
+
108
120
  let current_index = group_list.findIndex((g) => g.id === selected_group_id);
109
121
  if (current_index === -1) {
110
122
  current_index = 0;
@@ -199,6 +211,8 @@ function SelectCommitRangesInternal(props: Props) {
199
211
  let max_item_width = max_group_label_width;
200
212
  max_item_width -= left_arrow.length + right_arrow.length;
201
213
 
214
+ const [focused, set_focused] = React.useState("");
215
+
202
216
  return (
203
217
  <Ink.Box flexDirection="column">
204
218
  <Ink.Box height={1} />
@@ -208,6 +222,11 @@ function SelectCommitRangesInternal(props: Props) {
208
222
  items={items}
209
223
  maxWidth={max_item_width}
210
224
  disabled={group_input}
225
+ onFocus={(args) => {
226
+ // console.debug("onFocus", args);
227
+
228
+ set_focused(args.item.subject_line);
229
+ }}
211
230
  onSelect={(args) => {
212
231
  // console.debug("onSelect", args);
213
232
 
@@ -326,7 +345,7 @@ function SelectCommitRangesInternal(props: Props) {
326
345
  }}
327
346
  />
328
347
 
329
- <TextInput onSubmit={submit_group_input} />
348
+ <TextInput defaultValue={focused} onSubmit={submit_group_input} />
330
349
 
331
350
  <Ink.Box height={1} />
332
351
  </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",
@@ -161,7 +161,7 @@ test("persist only valid urls, removed broken branch ids from interrupted sync",
161
161
  body: [
162
162
  "Summary of problem",
163
163
  "",
164
- "#### git stack",
164
+ "#### [git stack](https://github.com/magus/git-stack-cli)",
165
165
  "- ✅ `1` https://github.com/magus/git-multi-diff-playground/pull/43",
166
166
  "- ✅ `2` gs-P4EBkJm+q",
167
167
  "- 👉 `3` https://github.com/magus/git-multi-diff-playground/pull/47",
@@ -181,7 +181,7 @@ test("persist only valid urls, removed broken branch ids from interrupted sync",
181
181
  expect(output.split("\n")).toEqual([
182
182
  "Summary of problem",
183
183
  "",
184
- "#### git stack",
184
+ "#### [git stack](https://github.com/magus/git-stack-cli)",
185
185
  "- ✅ `1` https://github.com/magus/git-multi-diff-playground/pull/43",
186
186
  "- 👉 `2` https://github.com/magus/git-multi-diff-playground/pull/47",
187
187
  "- ⏳ `3` https://github.com/magus/git-multi-diff-playground/pull/54",
@@ -193,3 +193,68 @@ test("persist only valid urls, removed broken branch ids from interrupted sync",
193
193
 
194
194
  expect(rerun_output).toBe(output);
195
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();
@@ -99,10 +106,14 @@ export function parse(body: string): Map<string, StackTableRow> {
99
106
  }
100
107
 
101
108
  const TEMPLATE = {
102
- stack_table(rows: string) {
109
+ stack_table_legacy(rows: string) {
103
110
  return `#### git stack${rows}`;
104
111
  },
105
112
 
113
+ stack_table_link(rows: string) {
114
+ return `#### [git stack](https://github.com/magus/git-stack-cli)${rows}`;
115
+ },
116
+
106
117
  row(args: StackTableRow) {
107
118
  return `- ${args.icon} \`${args.num}\` ${args.pr_url}`;
108
119
  },
@@ -110,8 +121,17 @@ const TEMPLATE = {
110
121
 
111
122
  const RE = {
112
123
  // https://regex101.com/r/kqB9Ft/1
113
- stack_table: new RegExp(
114
- 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]+)?)+)")
115
135
  ),
116
136
 
117
137
  row: new RegExp(