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 +13 -6
- package/dist/cjs/index.cjs +47 -13
- package/package.json +1 -1
- package/src/app/MultiSelect.tsx +16 -4
- package/src/app/SelectCommitRanges.tsx +8 -1
- package/src/app/TextInput.tsx +2 -1
- package/src/core/GitReviseTodo.test.ts +51 -0
- package/src/core/GitReviseTodo.ts +5 -1
- package/src/core/StackSummaryTable.test.ts +107 -4
- package/src/core/StackSummaryTable.ts +33 -7
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
|
-
|
|
25
|
-
brew tap magus/git-stack
|
|
26
|
-
brew install git-stack
|
|
27
|
-
```
|
|
28
|
+
<details>
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
<summary>
|
|
31
|
+
npm alternative
|
|
32
|
+
</summary>
|
|
30
33
|
|
|
31
|
-
|
|
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
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -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
|
|
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.
|
|
27820
|
+
if (RE.stack_table_link.test(result)) {
|
|
27820
27821
|
// replace stack table
|
|
27821
|
-
result = result.replace(RE.
|
|
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.
|
|
27869
|
+
return TEMPLATE.stack_table_link(["", ...stack_list, "", ""].join("\n"));
|
|
27865
27870
|
}
|
|
27866
27871
|
function parse(body) {
|
|
27867
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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.
|
|
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
package/src/app/MultiSelect.tsx
CHANGED
|
@@ -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:
|
|
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
|
|
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
|
-
|
|
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>
|
package/src/app/TextInput.tsx
CHANGED
|
@@ -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
|
|
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.
|
|
12
|
+
if (RE.stack_table_link.test(result)) {
|
|
13
13
|
// replace stack table
|
|
14
|
-
result = result.replace(RE.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
110
|
-
TEMPLATE.
|
|
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 = {
|