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 +13 -6
- package/dist/cjs/index.cjs +56 -17
- package/package.json +1 -1
- package/src/app/MultiSelect.tsx +16 -4
- package/src/app/SelectCommitRanges.tsx +27 -8
- 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 +71 -6
- package/src/core/StackSummaryTable.ts +27 -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
|
}
|
|
@@ -27886,16 +27894,25 @@ function parse(body) {
|
|
|
27886
27894
|
return result;
|
|
27887
27895
|
}
|
|
27888
27896
|
const TEMPLATE = {
|
|
27889
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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 (
|
|
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,
|
|
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.
|
|
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
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");
|
|
@@ -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.
|
|
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
|
-
|
|
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 (
|
|
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>
|
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",
|
|
@@ -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.
|
|
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();
|
|
@@ -99,10 +106,14 @@ export function parse(body: string): Map<string, StackTableRow> {
|
|
|
99
106
|
}
|
|
100
107
|
|
|
101
108
|
const TEMPLATE = {
|
|
102
|
-
|
|
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
|
-
|
|
114
|
-
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]+)?)+)")
|
|
115
135
|
),
|
|
116
136
|
|
|
117
137
|
row: new RegExp(
|