git-stack-cli 2.7.8 → 2.8.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/dist/js/index.js +76 -76
- package/package.json +1 -1
- package/scripts/bun-build.ts +3 -2
- package/scripts/release-npm.ts +6 -4
- package/src/app/ManualRebase.tsx +69 -14
- package/src/app/MultiSelect.tsx +19 -0
- package/src/app/SelectCommitRanges.tsx +63 -8
- package/src/app/SyncGithub.tsx +100 -48
- package/src/core/CommitMetadata.ts +71 -13
- package/src/core/GitReviseTodo.test.ts +46 -0
- package/src/core/GitReviseTodo.ts +6 -1
- package/src/core/Metadata.test.ts +51 -0
- package/src/core/Metadata.ts +24 -1
- package/src/core/__snapshots__/git.test.ts.snap +4 -0
- package/src/core/git.ts +15 -1
- package/src/core/github.tsx +21 -4
package/package.json
CHANGED
package/scripts/bun-build.ts
CHANGED
|
@@ -67,10 +67,11 @@ async function run_build() {
|
|
|
67
67
|
const result = await Bun.build(BUILD_CONFIG);
|
|
68
68
|
|
|
69
69
|
const duration_ms = Date.now() - start;
|
|
70
|
+
const status = result.success ? "✅" : "❌";
|
|
70
71
|
|
|
71
|
-
log(
|
|
72
|
+
log(`${status} build (${duration_ms}ms)`);
|
|
72
73
|
|
|
73
|
-
if (VERBOSE) {
|
|
74
|
+
if (VERBOSE || !result.success) {
|
|
74
75
|
log({ result });
|
|
75
76
|
}
|
|
76
77
|
}
|
package/scripts/release-npm.ts
CHANGED
|
@@ -6,6 +6,9 @@ import { spawn } from "~/core/spawn";
|
|
|
6
6
|
|
|
7
7
|
process.env.NODE_ENV = "production";
|
|
8
8
|
|
|
9
|
+
// ensure npm is authenticated
|
|
10
|
+
await spawn(`npm whoami`);
|
|
11
|
+
|
|
9
12
|
// get paths relative to this script
|
|
10
13
|
const REPO_ROOT = (await spawn.sync("git rev-parse --show-toplevel")).stdout;
|
|
11
14
|
const DIST_DIR = path.join(REPO_ROOT, "dist");
|
|
@@ -18,10 +21,9 @@ process.chdir(REPO_ROOT);
|
|
|
18
21
|
|
|
19
22
|
// require clean git status besides changes to package.json version
|
|
20
23
|
const git_status = await spawn.sync("git status --porcelain");
|
|
21
|
-
if (!/^M\s+package.json
|
|
22
|
-
console.error(
|
|
23
|
-
|
|
24
|
-
);
|
|
24
|
+
if (!/^M\s+package.json$/.test(git_status.stdout)) {
|
|
25
|
+
console.error("please commit local changes");
|
|
26
|
+
console.error("only update package.json version before running release");
|
|
25
27
|
process.exit(4);
|
|
26
28
|
}
|
|
27
29
|
|
package/src/app/ManualRebase.tsx
CHANGED
|
@@ -57,6 +57,7 @@ async function run() {
|
|
|
57
57
|
const group_from_map = commit_map[commit.sha];
|
|
58
58
|
commit.branch_id = group_from_map.id;
|
|
59
59
|
commit.title = group_from_map.title;
|
|
60
|
+
commit.master_base = group_from_map.master_base;
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
// // capture commit_range for GitReviseTodo test
|
|
@@ -78,21 +79,12 @@ async function run() {
|
|
|
78
79
|
let rebase_merge_base = merge_base;
|
|
79
80
|
let rebase_group_index = 0;
|
|
80
81
|
|
|
81
|
-
|
|
82
|
-
const group = commit_range.group_list[i];
|
|
82
|
+
inplace_order_commit_range_groups(commit_range);
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if (i > 0) {
|
|
89
|
-
const prev_group = commit_range.group_list[i - 1];
|
|
90
|
-
const prev_commit = prev_group.commits[prev_group.commits.length - 1];
|
|
91
|
-
rebase_merge_base = prev_commit.sha;
|
|
92
|
-
rebase_group_index = i;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
break;
|
|
84
|
+
const dirty = find_first_dirty_group(commit_range);
|
|
85
|
+
if (dirty) {
|
|
86
|
+
rebase_merge_base = dirty.sha;
|
|
87
|
+
rebase_group_index = dirty.index;
|
|
96
88
|
}
|
|
97
89
|
|
|
98
90
|
actions.debug(`rebase_merge_base = ${rebase_merge_base}`);
|
|
@@ -185,3 +177,66 @@ async function run() {
|
|
|
185
177
|
);
|
|
186
178
|
}
|
|
187
179
|
}
|
|
180
|
+
|
|
181
|
+
function inplace_order_commit_range_groups(commit_range: CommitMetadata.CommitRange) {
|
|
182
|
+
const state = Store.getState();
|
|
183
|
+
const actions = state.actions;
|
|
184
|
+
|
|
185
|
+
// order groups with group.master_base first
|
|
186
|
+
const group_list_master: CommitGroupList = [];
|
|
187
|
+
const group_list_others: CommitGroupList = [];
|
|
188
|
+
for (const group of commit_range.group_list) {
|
|
189
|
+
if (group.master_base) {
|
|
190
|
+
group_list_master.push(group);
|
|
191
|
+
} else {
|
|
192
|
+
group_list_others.push(group);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const ordered_group_list = [...group_list_master, ...group_list_others];
|
|
197
|
+
|
|
198
|
+
// detect if group list order differs
|
|
199
|
+
let differs = false;
|
|
200
|
+
for (let i = 0; i < commit_range.group_list.length; i++) {
|
|
201
|
+
const original_group = commit_range.group_list[i];
|
|
202
|
+
const ordered_group = ordered_group_list[i];
|
|
203
|
+
if (original_group.id !== ordered_group.id) {
|
|
204
|
+
ordered_group.dirty = true;
|
|
205
|
+
const debug = JSON.stringify({ original_group, ordered_group });
|
|
206
|
+
actions.debug(`inplace_order_commit_range_groups ${debug}`);
|
|
207
|
+
|
|
208
|
+
differs = true;
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (differs) {
|
|
214
|
+
commit_range.group_list = ordered_group_list;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return differs;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function find_first_dirty_group(commit_range: CommitMetadata.CommitRange) {
|
|
221
|
+
for (let i = 0; i < commit_range.group_list.length; i++) {
|
|
222
|
+
const group = commit_range.group_list[i];
|
|
223
|
+
|
|
224
|
+
if (!group.dirty) {
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (i > 0) {
|
|
229
|
+
const prev_group = commit_range.group_list[i - 1];
|
|
230
|
+
const prev_commit = prev_group.commits[prev_group.commits.length - 1];
|
|
231
|
+
const sha = prev_commit.sha;
|
|
232
|
+
const index = i;
|
|
233
|
+
return { sha, index };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
type CommitGroupList = CommitMetadata.CommitRange["group_list"];
|
package/src/app/MultiSelect.tsx
CHANGED
|
@@ -87,6 +87,25 @@ export function MultiSelect<T>(props: Props<T>) {
|
|
|
87
87
|
},
|
|
88
88
|
);
|
|
89
89
|
|
|
90
|
+
// ensure current index is valid and not disabled
|
|
91
|
+
React.useEffect(ensure_selectable_focus, [props.items]);
|
|
92
|
+
function ensure_selectable_focus() {
|
|
93
|
+
const current = props.items[index];
|
|
94
|
+
if (current && !current.disabled) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
for (let i = 0; i < props.items.length; i++) {
|
|
99
|
+
// search items backwards
|
|
100
|
+
const index = props.items.length - 1 - i;
|
|
101
|
+
const item = props.items[index];
|
|
102
|
+
if (!item.disabled) {
|
|
103
|
+
set_index(index);
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
90
109
|
const selectRef = React.useRef(false);
|
|
91
110
|
|
|
92
111
|
React.useEffect(() => {
|
|
@@ -15,6 +15,9 @@ import { short_id } from "~/core/short_id";
|
|
|
15
15
|
import { wrap_index } from "~/core/wrap_index";
|
|
16
16
|
|
|
17
17
|
import type { State } from "~/app/Store";
|
|
18
|
+
import type * as CommitMetadata from "~/core/CommitMetadata";
|
|
19
|
+
|
|
20
|
+
type CommitRangeGroup = NonNullable<Parameters<typeof CommitMetadata.range>[0]>[string];
|
|
18
21
|
|
|
19
22
|
export function SelectCommitRanges() {
|
|
20
23
|
const commit_range = Store.useState((state) => state.commit_range);
|
|
@@ -62,6 +65,23 @@ function SelectCommitRangesInternal(props: Props) {
|
|
|
62
65
|
[],
|
|
63
66
|
);
|
|
64
67
|
|
|
68
|
+
const [group_master_base, set_group_master_base] = React.useReducer(
|
|
69
|
+
(set: Set<string>, group_id: string) => {
|
|
70
|
+
set.has(group_id) ? set.delete(group_id) : set.add(group_id);
|
|
71
|
+
return new Set(set);
|
|
72
|
+
},
|
|
73
|
+
new Set<string>(),
|
|
74
|
+
(set) => {
|
|
75
|
+
for (const group of props.commit_range.group_list) {
|
|
76
|
+
if (group.master_base) {
|
|
77
|
+
set.add(group.id);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return new Set(set);
|
|
82
|
+
},
|
|
83
|
+
);
|
|
84
|
+
|
|
65
85
|
const [commit_map, update_commit_map] = React.useReducer(
|
|
66
86
|
(map: Map<string, null | string>, args: { key: string; value: null | string }) => {
|
|
67
87
|
map.set(args.key, args.value);
|
|
@@ -84,18 +104,18 @@ function SelectCommitRangesInternal(props: Props) {
|
|
|
84
104
|
Ink.useInput((input, key) => {
|
|
85
105
|
const input_lower = input.toLowerCase();
|
|
86
106
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
107
|
+
// do not allow input when inputting group title
|
|
108
|
+
if (group_input) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
92
111
|
|
|
112
|
+
if (input_lower === SYMBOL.s) {
|
|
93
113
|
if (sync_status === "disabled") {
|
|
94
114
|
return;
|
|
95
115
|
}
|
|
96
116
|
|
|
97
117
|
actions.set((state) => {
|
|
98
|
-
const state_commit_map: Record<string,
|
|
118
|
+
const state_commit_map: Record<string, CommitRangeGroup> = {};
|
|
99
119
|
|
|
100
120
|
for (let [sha, id] of commit_map.entries()) {
|
|
101
121
|
// console.debug({ sha, id });
|
|
@@ -104,14 +124,15 @@ function SelectCommitRangesInternal(props: Props) {
|
|
|
104
124
|
if (!id) {
|
|
105
125
|
id = props.commit_range.UNASSIGNED;
|
|
106
126
|
const title = "allow_unassigned";
|
|
107
|
-
state_commit_map[sha] = { id, title };
|
|
127
|
+
state_commit_map[sha] = { id, title, master_base: false };
|
|
108
128
|
continue;
|
|
109
129
|
}
|
|
110
130
|
|
|
111
131
|
const group = group_list.find((g) => g.id === id);
|
|
112
132
|
invariant(group, "group must exist");
|
|
113
133
|
// console.debug({ group });
|
|
114
|
-
|
|
134
|
+
const master_base = group_master_base.has(id);
|
|
135
|
+
state_commit_map[sha] = { ...group, master_base };
|
|
115
136
|
}
|
|
116
137
|
|
|
117
138
|
state.commit_map = state_commit_map;
|
|
@@ -127,6 +148,13 @@ function SelectCommitRangesInternal(props: Props) {
|
|
|
127
148
|
return;
|
|
128
149
|
}
|
|
129
150
|
|
|
151
|
+
// only allow setting base branch when on a created group
|
|
152
|
+
if (group.id !== props.commit_range.UNASSIGNED && input_lower === SYMBOL.m) {
|
|
153
|
+
const group = group_list[current_index];
|
|
154
|
+
set_group_master_base(group.id);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
130
158
|
if (key.leftArrow) {
|
|
131
159
|
const new_index = wrap_index(current_index - 1, group_list);
|
|
132
160
|
const next_group = group_list[new_index];
|
|
@@ -196,6 +224,7 @@ function SelectCommitRangesInternal(props: Props) {
|
|
|
196
224
|
// console.debug({ sync_status });
|
|
197
225
|
|
|
198
226
|
const group = group_list[current_index];
|
|
227
|
+
const is_master_base = group_master_base.has(group.id);
|
|
199
228
|
|
|
200
229
|
const multiselect_disabled = group_input;
|
|
201
230
|
const multiselect_disableSelect = group.id === props.commit_range.UNASSIGNED;
|
|
@@ -281,6 +310,10 @@ function SelectCommitRangesInternal(props: Props) {
|
|
|
281
310
|
<Ink.Text wrap="truncate-end" bold color={colors.white}>
|
|
282
311
|
{group.title}
|
|
283
312
|
</Ink.Text>
|
|
313
|
+
{!is_master_base ? null : (
|
|
314
|
+
// show base master
|
|
315
|
+
<Ink.Text color={colors.yellow}> (base: master)</Ink.Text>
|
|
316
|
+
)}
|
|
284
317
|
</Ink.Box>
|
|
285
318
|
</React.Fragment>
|
|
286
319
|
)}
|
|
@@ -445,6 +478,27 @@ function SelectCommitRangesInternal(props: Props) {
|
|
|
445
478
|
}}
|
|
446
479
|
/>
|
|
447
480
|
</Ink.Box>
|
|
481
|
+
|
|
482
|
+
{group.id === props.commit_range.UNASSIGNED ? null : (
|
|
483
|
+
<Ink.Box>
|
|
484
|
+
<FormatText
|
|
485
|
+
wrapper={<Ink.Text color={colors.gray} />}
|
|
486
|
+
message={
|
|
487
|
+
is_master_base
|
|
488
|
+
? "Press {m} to {reset} current PR base to stack position"
|
|
489
|
+
: "Press {m} to set current PR base to master"
|
|
490
|
+
}
|
|
491
|
+
values={{
|
|
492
|
+
m: (
|
|
493
|
+
<Ink.Text bold color={colors.green}>
|
|
494
|
+
{SYMBOL.m}
|
|
495
|
+
</Ink.Text>
|
|
496
|
+
),
|
|
497
|
+
reset: <Ink.Text color={colors.yellow}>reset</Ink.Text>,
|
|
498
|
+
}}
|
|
499
|
+
/>
|
|
500
|
+
</Ink.Box>
|
|
501
|
+
)}
|
|
448
502
|
</Ink.Box>
|
|
449
503
|
);
|
|
450
504
|
|
|
@@ -518,6 +572,7 @@ const SYMBOL = {
|
|
|
518
572
|
enter: "Enter",
|
|
519
573
|
c: "c",
|
|
520
574
|
s: "s",
|
|
575
|
+
m: "m",
|
|
521
576
|
};
|
|
522
577
|
|
|
523
578
|
const S_TO_SYNC_VALUES = {
|
package/src/app/SyncGithub.tsx
CHANGED
|
@@ -54,6 +54,17 @@ async function run() {
|
|
|
54
54
|
// 2 create PR / edit PR
|
|
55
55
|
// --------------------------------------
|
|
56
56
|
|
|
57
|
+
function create_git_push_command(base: string, target: string) {
|
|
58
|
+
const command = [`${base} push -f origin`];
|
|
59
|
+
|
|
60
|
+
if (argv.verify === false) {
|
|
61
|
+
command.push("--no-verify");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
command.push(target);
|
|
65
|
+
return command;
|
|
66
|
+
}
|
|
67
|
+
|
|
57
68
|
try {
|
|
58
69
|
const before_push_tasks = [];
|
|
59
70
|
for (const group of push_group_list) {
|
|
@@ -62,16 +73,21 @@ async function run() {
|
|
|
62
73
|
|
|
63
74
|
await Promise.all(before_push_tasks);
|
|
64
75
|
|
|
65
|
-
const
|
|
76
|
+
const git_push_target_list: Array<string> = [];
|
|
66
77
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
for (const group of push_group_list) {
|
|
78
|
+
for (let i = 0; i < push_group_list.length; i++) {
|
|
79
|
+
const group = push_group_list[i];
|
|
72
80
|
const last_commit = last(group.commits);
|
|
73
81
|
invariant(last_commit, "last_commit must exist");
|
|
74
82
|
|
|
83
|
+
// push group in isolation if master_base is set
|
|
84
|
+
// for the first group (i > 0) we can skip this
|
|
85
|
+
// since it'll be based off master anyway
|
|
86
|
+
if (group.master_base && i > 0) {
|
|
87
|
+
await push_master_group(group);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
|
|
75
91
|
// explicit refs/heads head branch to avoid push failing
|
|
76
92
|
//
|
|
77
93
|
// ❯ git push -f origin --no-verify f6e249051b4820a03deb957ddebc19acfd7dfd7c:gs-ED2etrzv2
|
|
@@ -90,29 +106,49 @@ async function run() {
|
|
|
90
106
|
// error: failed to push some refs to 'github.com:magus/git-multi-diff-playground.git'
|
|
91
107
|
//
|
|
92
108
|
const target = `${last_commit.sha}:refs/heads/${group.id}`;
|
|
93
|
-
|
|
109
|
+
git_push_target_list.push(target);
|
|
94
110
|
}
|
|
95
111
|
|
|
96
|
-
|
|
112
|
+
if (git_push_target_list.length > 0) {
|
|
113
|
+
const push_target = git_push_target_list.join(" ");
|
|
114
|
+
const git_push_command = create_git_push_command("git", push_target);
|
|
115
|
+
await cli(git_push_command);
|
|
116
|
+
}
|
|
97
117
|
|
|
98
|
-
const
|
|
118
|
+
const pr_url_by_group_id: Record<string, string> = {};
|
|
99
119
|
|
|
100
120
|
const after_push_tasks = [];
|
|
101
121
|
for (const group of push_group_list) {
|
|
102
|
-
after_push_tasks.push(after_push({ group,
|
|
122
|
+
after_push_tasks.push(after_push({ group, pr_url_by_group_id }));
|
|
103
123
|
}
|
|
104
124
|
|
|
105
125
|
await Promise.all(after_push_tasks);
|
|
106
126
|
|
|
107
|
-
// finally, ensure all prs have the updated stack table from updated
|
|
127
|
+
// finally, ensure all prs have the updated stack table from updated pr_url_by_group_id
|
|
108
128
|
// this step must come after the after_push since that step may create new PRs
|
|
109
129
|
// we need the urls for all prs at this step so we run it after the after_push
|
|
130
|
+
const all_pr_groups: Array<CommitMetadataGroup> = [];
|
|
131
|
+
// collect all groups and existing pr urls
|
|
132
|
+
for (const group of commit_range.group_list) {
|
|
133
|
+
if (group.id !== commit_range.UNASSIGNED) {
|
|
134
|
+
// collect all groups
|
|
135
|
+
all_pr_groups.push(group);
|
|
136
|
+
|
|
137
|
+
if (group.pr) {
|
|
138
|
+
pr_url_by_group_id[group.id] = group.pr.url;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// get pr url list for all pr groups
|
|
144
|
+
const pr_url_list = all_pr_groups.map((g) => pr_url_by_group_id[g.id]);
|
|
145
|
+
|
|
146
|
+
// update PR body for all pr groups (not just push_group_list)
|
|
110
147
|
const update_pr_body_tasks = [];
|
|
111
|
-
for (let i = 0; i <
|
|
112
|
-
const group =
|
|
148
|
+
for (let i = 0; i < all_pr_groups.length; i++) {
|
|
149
|
+
const group = all_pr_groups[i];
|
|
113
150
|
|
|
114
|
-
|
|
115
|
-
const selected_url = pr_url_list[i];
|
|
151
|
+
const selected_url = pr_url_by_group_id[group.id];
|
|
116
152
|
|
|
117
153
|
const task = update_pr_body({ group, selected_url, pr_url_list });
|
|
118
154
|
update_pr_body_tasks.push(task);
|
|
@@ -183,7 +219,7 @@ async function run() {
|
|
|
183
219
|
// Unable to sync.
|
|
184
220
|
// ```
|
|
185
221
|
//
|
|
186
|
-
if (
|
|
222
|
+
if (!is_master_base(group)) {
|
|
187
223
|
await github.pr_edit({
|
|
188
224
|
branch: group.id,
|
|
189
225
|
base: master_branch,
|
|
@@ -192,34 +228,20 @@ async function run() {
|
|
|
192
228
|
}
|
|
193
229
|
}
|
|
194
230
|
|
|
195
|
-
async function after_push(args: {
|
|
196
|
-
|
|
231
|
+
async function after_push(args: {
|
|
232
|
+
group: CommitMetadataGroup;
|
|
233
|
+
pr_url_by_group_id: Record<string, string>;
|
|
234
|
+
}) {
|
|
235
|
+
const { group } = args;
|
|
197
236
|
|
|
198
237
|
invariant(group.base, "group.base must exist");
|
|
199
238
|
|
|
200
|
-
const selected_url = get_group_url(group);
|
|
201
|
-
|
|
202
239
|
if (group.pr) {
|
|
203
|
-
if (
|
|
240
|
+
if (!is_master_base(group)) {
|
|
204
241
|
// ensure base matches pr in github
|
|
205
|
-
await github.pr_edit({
|
|
206
|
-
branch: group.id,
|
|
207
|
-
base: group.base,
|
|
208
|
-
body: StackSummaryTable.write({
|
|
209
|
-
body: group.pr.body,
|
|
210
|
-
pr_url_list,
|
|
211
|
-
selected_url,
|
|
212
|
-
}),
|
|
213
|
-
});
|
|
242
|
+
await github.pr_edit({ branch: group.id, base: group.base });
|
|
214
243
|
} else {
|
|
215
|
-
await github.pr_edit({
|
|
216
|
-
branch: group.id,
|
|
217
|
-
body: StackSummaryTable.write({
|
|
218
|
-
body: group.pr.body,
|
|
219
|
-
pr_url_list,
|
|
220
|
-
selected_url,
|
|
221
|
-
}),
|
|
222
|
-
});
|
|
244
|
+
await github.pr_edit({ branch: group.id });
|
|
223
245
|
}
|
|
224
246
|
} else {
|
|
225
247
|
// create pr in github
|
|
@@ -235,13 +257,8 @@ async function run() {
|
|
|
235
257
|
throw new Error("unable to create pr");
|
|
236
258
|
}
|
|
237
259
|
|
|
238
|
-
// update
|
|
239
|
-
|
|
240
|
-
const url = pr_url_list[i];
|
|
241
|
-
if (url === selected_url) {
|
|
242
|
-
pr_url_list[i] = pr_url;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
260
|
+
// update pr_url_by_group_id with created pr_url
|
|
261
|
+
args.pr_url_by_group_id[group.id] = pr_url;
|
|
245
262
|
}
|
|
246
263
|
}
|
|
247
264
|
|
|
@@ -262,10 +279,12 @@ async function run() {
|
|
|
262
279
|
selected_url,
|
|
263
280
|
});
|
|
264
281
|
|
|
282
|
+
const debug_meta = `${group.id} ${selected_url}`;
|
|
283
|
+
|
|
265
284
|
if (update_body === body) {
|
|
266
|
-
actions.debug(`Skipping body update
|
|
285
|
+
actions.debug(`Skipping body update ${debug_meta}`);
|
|
267
286
|
} else {
|
|
268
|
-
actions.debug(`Update body
|
|
287
|
+
actions.debug(`Update body ${debug_meta}`);
|
|
269
288
|
|
|
270
289
|
await github.pr_edit({
|
|
271
290
|
branch: group.id,
|
|
@@ -274,7 +293,40 @@ async function run() {
|
|
|
274
293
|
});
|
|
275
294
|
}
|
|
276
295
|
}
|
|
296
|
+
|
|
297
|
+
function is_master_base(group: CommitMetadataGroup) {
|
|
298
|
+
if (!group.pr) {
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return group.master_base || `origin/${group.pr.baseRefName}` === master_branch;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
async function push_master_group(group: CommitMetadataGroup) {
|
|
306
|
+
const worktree_path = `.git/git-stack-worktrees/push_master_group`;
|
|
307
|
+
|
|
308
|
+
// ensure previous instance of worktree is removed
|
|
309
|
+
await cli(`git worktree remove --force ${worktree_path}`, { ignoreExitCode: true });
|
|
310
|
+
|
|
311
|
+
// create temp worktree at master (or group.base if you prefer)
|
|
312
|
+
await cli(`git worktree add -f ${worktree_path} ${master_branch}`);
|
|
313
|
+
|
|
314
|
+
try {
|
|
315
|
+
// cherry-pick the group commits onto that base
|
|
316
|
+
const cp_commit_list = group.commits.map((c) => c.sha);
|
|
317
|
+
await cli(`git -C ${worktree_path} cherry-pick ${cp_commit_list}`);
|
|
318
|
+
|
|
319
|
+
`git -C ${worktree_path} push -f origin HEAD:refs/heads/${group.id}`;
|
|
320
|
+
|
|
321
|
+
const push_target = `HEAD:refs/heads/${group.id}`;
|
|
322
|
+
const git_push_command = create_git_push_command(`git -C ${worktree_path}`, push_target);
|
|
323
|
+
|
|
324
|
+
await cli(git_push_command);
|
|
325
|
+
} finally {
|
|
326
|
+
// clean up even if push fails
|
|
327
|
+
await cli(`git worktree remove --force ${worktree_path}`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
277
330
|
}
|
|
278
331
|
|
|
279
332
|
type CommitMetadataGroup = CommitMetadata.CommitRange["group_list"][number];
|
|
280
|
-
const get_group_url = (group: CommitMetadataGroup) => group.pr?.url || group.id;
|