git-stack-cli 2.9.2 → 2.9.4
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 +78 -78
- package/package.json +1 -1
- package/src/app/App.tsx +13 -8
- package/src/app/DebugOutput.tsx +40 -0
- package/src/app/FetchPullRequests.tsx +40 -0
- package/src/app/GithubApiError.tsx +31 -28
- package/src/app/ManualRebase.tsx +6 -1
- package/src/app/Output.tsx +8 -17
- package/src/app/Store.tsx +31 -72
- package/src/app/SyncGithub.tsx +44 -32
- package/src/app/VerboseDebugInfo.tsx +8 -0
- package/src/core/CommitMetadata.ts +90 -65
- package/src/core/cli.ts +12 -5
- package/src/core/git.ts +1 -1
- package/src/core/github.tsx +135 -46
- package/src/index.tsx +0 -5
- package/src/types/global.d.ts +1 -0
- package/src/app/LogTimestamp.tsx +0 -8
package/package.json
CHANGED
package/src/app/App.tsx
CHANGED
|
@@ -5,6 +5,7 @@ import { CherryPickCheck } from "~/app/CherryPickCheck";
|
|
|
5
5
|
import { DependencyCheck } from "~/app/DependencyCheck";
|
|
6
6
|
import { DetectInitialPR } from "~/app/DetectInitialPR";
|
|
7
7
|
import { DirtyCheck } from "~/app/DirtyCheck";
|
|
8
|
+
import { FetchPullRequests } from "~/app/FetchPullRequests";
|
|
8
9
|
import { GatherMetadata } from "~/app/GatherMetadata";
|
|
9
10
|
import { GithubApiError } from "~/app/GithubApiError";
|
|
10
11
|
import { HandleCtrlCSigint } from "~/app/HandleCtrlCSigint";
|
|
@@ -100,9 +101,11 @@ function MaybeMain() {
|
|
|
100
101
|
<DependencyCheck>
|
|
101
102
|
<DirtyCheck>
|
|
102
103
|
<GatherMetadata>
|
|
103
|
-
<
|
|
104
|
-
<
|
|
105
|
-
|
|
104
|
+
<FetchPullRequests>
|
|
105
|
+
<LocalCommitStatus>
|
|
106
|
+
<Rebase />
|
|
107
|
+
</LocalCommitStatus>
|
|
108
|
+
</FetchPullRequests>
|
|
106
109
|
</GatherMetadata>
|
|
107
110
|
</DirtyCheck>
|
|
108
111
|
</DependencyCheck>
|
|
@@ -117,11 +120,13 @@ function MaybeMain() {
|
|
|
117
120
|
<DirtyCheck>
|
|
118
121
|
<GatherMetadata>
|
|
119
122
|
<RequireBranch>
|
|
120
|
-
<
|
|
121
|
-
<
|
|
122
|
-
<
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
<FetchPullRequests>
|
|
124
|
+
<LocalCommitStatus>
|
|
125
|
+
<DetectInitialPR>
|
|
126
|
+
<Main />
|
|
127
|
+
</DetectInitialPR>
|
|
128
|
+
</LocalCommitStatus>
|
|
129
|
+
</FetchPullRequests>
|
|
125
130
|
</RequireBranch>
|
|
126
131
|
</GatherMetadata>
|
|
127
132
|
</DirtyCheck>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import * as Ink from "ink-cjs";
|
|
4
|
+
import { DateTime } from "luxon";
|
|
5
|
+
|
|
6
|
+
type Props = {
|
|
7
|
+
node: React.ReactNode;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function DebugOutput(props: Props) {
|
|
11
|
+
const { stdout } = Ink.useStdout();
|
|
12
|
+
const available_width = stdout.columns;
|
|
13
|
+
|
|
14
|
+
const timestamp = DateTime.now().toFormat("yyyy-MM-dd HH:mm:ss.SSS");
|
|
15
|
+
const content_width = available_width - timestamp.length - 2;
|
|
16
|
+
|
|
17
|
+
const content = (function () {
|
|
18
|
+
switch (typeof props.node) {
|
|
19
|
+
case "boolean":
|
|
20
|
+
case "number":
|
|
21
|
+
case "string": {
|
|
22
|
+
return <Ink.Text dimColor>{String(props.node)}</Ink.Text>;
|
|
23
|
+
}
|
|
24
|
+
default:
|
|
25
|
+
return props.node;
|
|
26
|
+
}
|
|
27
|
+
})();
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<Ink.Box flexDirection="column">
|
|
31
|
+
<Ink.Box flexDirection="row" gap={1} width={available_width}>
|
|
32
|
+
<Ink.Box width={timestamp.length} flexDirection="column">
|
|
33
|
+
<Ink.Text dimColor>{timestamp}</Ink.Text>
|
|
34
|
+
</Ink.Box>
|
|
35
|
+
|
|
36
|
+
<Ink.Box width={content_width}>{content}</Ink.Box>
|
|
37
|
+
</Ink.Box>
|
|
38
|
+
</Ink.Box>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import * as Ink from "ink-cjs";
|
|
4
|
+
|
|
5
|
+
import { Await } from "~/app/Await";
|
|
6
|
+
import { Store } from "~/app/Store";
|
|
7
|
+
import { colors } from "~/core/colors";
|
|
8
|
+
import * as github from "~/core/github";
|
|
9
|
+
|
|
10
|
+
type Props = {
|
|
11
|
+
children: React.ReactNode;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function FetchPullRequests(props: Props) {
|
|
15
|
+
const fallback = <Ink.Text color={colors.yellow}>Fetching pull requests…</Ink.Text>;
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<Await fallback={fallback} function={run}>
|
|
19
|
+
{props.children}
|
|
20
|
+
</Await>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function run() {
|
|
25
|
+
const actions = Store.getState().actions;
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
// gather all open prs in repo at once
|
|
29
|
+
// cheaper query to populate cache
|
|
30
|
+
await github.pr_list();
|
|
31
|
+
} catch (err) {
|
|
32
|
+
actions.error("Unable to fetch pull requests.");
|
|
33
|
+
|
|
34
|
+
if (err instanceof Error) {
|
|
35
|
+
actions.error(err.message);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
actions.exit(24);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -4,6 +4,7 @@ import * as Ink from "ink-cjs";
|
|
|
4
4
|
|
|
5
5
|
import { Await } from "~/app/Await";
|
|
6
6
|
import { Brackets } from "~/app/Brackets";
|
|
7
|
+
import { FormatText } from "~/app/FormatText";
|
|
7
8
|
import { Parens } from "~/app/Parens";
|
|
8
9
|
import { Store } from "~/app/Store";
|
|
9
10
|
import { cli } from "~/core/cli";
|
|
@@ -51,34 +52,36 @@ async function run(props: Props) {
|
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
actions.output(
|
|
54
|
-
<
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
55
|
+
<FormatText
|
|
56
|
+
message="Github {graphql} API rate limit {ratio} will reset at {reset_time} {time_until}"
|
|
57
|
+
values={{
|
|
58
|
+
graphql: <Brackets>graphql</Brackets>,
|
|
59
|
+
ratio: (
|
|
60
|
+
<Brackets>
|
|
61
|
+
<FormatText message="{used}/{limit}" values={{ used, limit }} />
|
|
62
|
+
</Brackets>
|
|
63
|
+
),
|
|
64
|
+
reset_time: (
|
|
65
|
+
<Ink.Text bold color={colors.yellow}>
|
|
66
|
+
{reset_time}
|
|
67
|
+
</Ink.Text>
|
|
68
|
+
),
|
|
69
|
+
time_until: (
|
|
70
|
+
<Parens>
|
|
71
|
+
<FormatText
|
|
72
|
+
message="in {time_until}"
|
|
73
|
+
values={{
|
|
74
|
+
time_until: (
|
|
75
|
+
<Ink.Text bold color={colors.yellow}>
|
|
76
|
+
{time_until}
|
|
77
|
+
</Ink.Text>
|
|
78
|
+
),
|
|
79
|
+
}}
|
|
80
|
+
/>
|
|
81
|
+
</Parens>
|
|
82
|
+
),
|
|
83
|
+
}}
|
|
84
|
+
/>,
|
|
82
85
|
);
|
|
83
86
|
|
|
84
87
|
if (props.exit) {
|
package/src/app/ManualRebase.tsx
CHANGED
|
@@ -47,6 +47,11 @@ async function run() {
|
|
|
47
47
|
// get latest merge_base relative to local master
|
|
48
48
|
const merge_base = (await cli(`git merge-base HEAD ${master_branch}`)).stdout;
|
|
49
49
|
|
|
50
|
+
// ensure merge_base is updated
|
|
51
|
+
actions.set((state) => {
|
|
52
|
+
state.merge_base = merge_base;
|
|
53
|
+
});
|
|
54
|
+
|
|
50
55
|
// immediately paint all commit to preserve selected commit ranges
|
|
51
56
|
let commit_range = await CommitMetadata.range(commit_map);
|
|
52
57
|
|
|
@@ -116,7 +121,7 @@ async function run() {
|
|
|
116
121
|
if (argv.sync) {
|
|
117
122
|
actions.set((state) => {
|
|
118
123
|
state.step = "sync-github";
|
|
119
|
-
state.sync_github = { commit_range
|
|
124
|
+
state.sync_github = { commit_range };
|
|
120
125
|
});
|
|
121
126
|
} else {
|
|
122
127
|
actions.set((state) => {
|
package/src/app/Output.tsx
CHANGED
|
@@ -2,35 +2,26 @@ import * as React from "react";
|
|
|
2
2
|
|
|
3
3
|
import * as Ink from "ink-cjs";
|
|
4
4
|
|
|
5
|
+
import { DebugOutput } from "~/app/DebugOutput";
|
|
5
6
|
import { Store } from "~/app/Store";
|
|
6
7
|
|
|
7
8
|
export function Output() {
|
|
8
9
|
const output = Store.useState((state) => state.output);
|
|
9
10
|
const pending_output = Store.useState((state) => state.pending_output);
|
|
10
|
-
const pending_output_items = Object.values(pending_output);
|
|
11
11
|
|
|
12
12
|
return (
|
|
13
13
|
<React.Fragment>
|
|
14
14
|
<Ink.Static items={output}>
|
|
15
|
-
{(
|
|
16
|
-
|
|
15
|
+
{(entry) => {
|
|
16
|
+
const [id, node] = entry;
|
|
17
|
+
return <Ink.Box key={id}>{node}</Ink.Box>;
|
|
17
18
|
}}
|
|
18
19
|
</Ink.Static>
|
|
19
20
|
|
|
20
|
-
{
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
{node_list.map((text, j) => {
|
|
25
|
-
return (
|
|
26
|
-
<React.Fragment key={j}>
|
|
27
|
-
<Ink.Text>{text}</Ink.Text>
|
|
28
|
-
</React.Fragment>
|
|
29
|
-
);
|
|
30
|
-
})}
|
|
31
|
-
</Ink.Text>
|
|
32
|
-
</Ink.Box>
|
|
33
|
-
);
|
|
21
|
+
{Object.entries(pending_output).map((entry) => {
|
|
22
|
+
const [id, content_list] = entry;
|
|
23
|
+
const content = content_list.join("");
|
|
24
|
+
return <DebugOutput key={id} node={content} />;
|
|
34
25
|
})}
|
|
35
26
|
</React.Fragment>
|
|
36
27
|
);
|
package/src/app/Store.tsx
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
|
|
3
|
+
import crypto from "node:crypto";
|
|
4
|
+
|
|
3
5
|
import * as Ink from "ink-cjs";
|
|
4
6
|
import { createStore, useStore } from "zustand";
|
|
5
7
|
import { immer } from "zustand/middleware/immer";
|
|
6
8
|
|
|
9
|
+
import { DebugOutput } from "~/app/DebugOutput";
|
|
7
10
|
import { Exit } from "~/app/Exit";
|
|
8
|
-
import { LogTimestamp } from "~/app/LogTimestamp";
|
|
9
11
|
import { colors } from "~/core/colors";
|
|
10
12
|
import { pretty_json } from "~/core/pretty_json";
|
|
11
13
|
|
|
@@ -21,13 +23,10 @@ type CommitMap = Parameters<typeof CommitMetadata.range>[0];
|
|
|
21
23
|
type MutateOutputArgs = {
|
|
22
24
|
node: React.ReactNode;
|
|
23
25
|
id?: string;
|
|
24
|
-
debug?: boolean;
|
|
25
|
-
withoutTimestamp?: boolean;
|
|
26
26
|
};
|
|
27
27
|
|
|
28
28
|
type SyncGithubState = {
|
|
29
29
|
commit_range: CommitMetadata.CommitRange;
|
|
30
|
-
rebase_group_index: number;
|
|
31
30
|
};
|
|
32
31
|
|
|
33
32
|
// async function that returns exit code
|
|
@@ -73,12 +72,12 @@ export type State = {
|
|
|
73
72
|
| "sync-github"
|
|
74
73
|
| "post-rebase-status";
|
|
75
74
|
|
|
76
|
-
output: Array<React.ReactNode>;
|
|
77
|
-
pending_output: Record<string, Array<
|
|
75
|
+
output: Array<[string, React.ReactNode]>;
|
|
76
|
+
pending_output: Record<string, Array<string>>;
|
|
78
77
|
|
|
79
78
|
// cache
|
|
80
79
|
pr: { [branch: string]: PullRequest };
|
|
81
|
-
|
|
80
|
+
cache_gh_cli_by_branch: { [branch: string]: { [command: string]: string } };
|
|
82
81
|
|
|
83
82
|
actions: {
|
|
84
83
|
exit(code: number, args?: ExitArgs): void;
|
|
@@ -88,7 +87,9 @@ export type State = {
|
|
|
88
87
|
json(value: pretty_json.JSONValue): void;
|
|
89
88
|
error(error: unknown): void;
|
|
90
89
|
output(node: React.ReactNode): void;
|
|
91
|
-
debug(node: React.ReactNode
|
|
90
|
+
debug(node: React.ReactNode): void;
|
|
91
|
+
debug_pending(id: string, content: string): void;
|
|
92
|
+
debug_pending_end(id: string): void;
|
|
92
93
|
|
|
93
94
|
isDebug(): boolean;
|
|
94
95
|
|
|
@@ -100,8 +101,6 @@ export type State = {
|
|
|
100
101
|
|
|
101
102
|
mutate: {
|
|
102
103
|
output(state: State, args: MutateOutputArgs): void;
|
|
103
|
-
pending_output(state: State, args: MutateOutputArgs): void;
|
|
104
|
-
end_pending_output(state: State, id: string): void;
|
|
105
104
|
};
|
|
106
105
|
|
|
107
106
|
select: {
|
|
@@ -139,7 +138,7 @@ const BaseStore = createStore<State>()(
|
|
|
139
138
|
pending_output: {},
|
|
140
139
|
|
|
141
140
|
pr: {},
|
|
142
|
-
|
|
141
|
+
cache_gh_cli_by_branch: {},
|
|
143
142
|
|
|
144
143
|
actions: {
|
|
145
144
|
exit(code, args) {
|
|
@@ -179,7 +178,7 @@ const BaseStore = createStore<State>()(
|
|
|
179
178
|
json(value) {
|
|
180
179
|
set((state) => {
|
|
181
180
|
const node = pretty_json(value);
|
|
182
|
-
state.
|
|
181
|
+
state.actions.debug(node);
|
|
183
182
|
});
|
|
184
183
|
},
|
|
185
184
|
|
|
@@ -211,20 +210,32 @@ const BaseStore = createStore<State>()(
|
|
|
211
210
|
});
|
|
212
211
|
},
|
|
213
212
|
|
|
214
|
-
debug(node
|
|
213
|
+
debug(node) {
|
|
215
214
|
if (get().actions.isDebug()) {
|
|
216
|
-
|
|
215
|
+
set((state) => {
|
|
216
|
+
state.mutate.output(state, { node: <DebugOutput node={node} /> });
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
},
|
|
217
220
|
|
|
221
|
+
debug_pending(id, content) {
|
|
222
|
+
if (get().actions.isDebug()) {
|
|
218
223
|
set((state) => {
|
|
219
|
-
if (id) {
|
|
220
|
-
state.
|
|
221
|
-
} else {
|
|
222
|
-
state.mutate.output(state, { node, debug });
|
|
224
|
+
if (!state.pending_output[id]) {
|
|
225
|
+
state.pending_output[id] = [];
|
|
223
226
|
}
|
|
227
|
+
|
|
228
|
+
state.pending_output[id].push(content);
|
|
224
229
|
});
|
|
225
230
|
}
|
|
226
231
|
},
|
|
227
232
|
|
|
233
|
+
debug_pending_end(id) {
|
|
234
|
+
set((state) => {
|
|
235
|
+
delete state.pending_output[id];
|
|
236
|
+
});
|
|
237
|
+
},
|
|
238
|
+
|
|
228
239
|
isDebug() {
|
|
229
240
|
const state = get();
|
|
230
241
|
return state.select.debug(state);
|
|
@@ -251,38 +262,8 @@ const BaseStore = createStore<State>()(
|
|
|
251
262
|
|
|
252
263
|
mutate: {
|
|
253
264
|
output(state, args) {
|
|
254
|
-
const
|
|
255
|
-
state.output.push(
|
|
256
|
-
},
|
|
257
|
-
|
|
258
|
-
pending_output(state, args) {
|
|
259
|
-
const { id } = args;
|
|
260
|
-
|
|
261
|
-
if (!id) {
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// set `withoutTimestamp` to skip <LogTimestamp> for all subsequent pending outputs
|
|
266
|
-
// we only want to timestamp for the first part (when we initialize the [])
|
|
267
|
-
// if we have many incremental outputs on the same line we do not want multiple timestamps
|
|
268
|
-
//
|
|
269
|
-
// await Promise.all([
|
|
270
|
-
// cli(`for i in $(seq 1 5); do echo $i; sleep 1; done`),
|
|
271
|
-
// cli(`for i in $(seq 5 1); do printf "$i "; sleep 1; done; echo`),
|
|
272
|
-
// ]);
|
|
273
|
-
//
|
|
274
|
-
let withoutTimestamp = true;
|
|
275
|
-
if (!state.pending_output[id]) {
|
|
276
|
-
withoutTimestamp = false;
|
|
277
|
-
state.pending_output[id] = [];
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
const renderOutput = renderOutputArgs({ ...args, withoutTimestamp });
|
|
281
|
-
state.pending_output[id].push(renderOutput);
|
|
282
|
-
},
|
|
283
|
-
|
|
284
|
-
end_pending_output(state, id) {
|
|
285
|
-
delete state.pending_output[id];
|
|
265
|
+
const id = crypto.randomUUID();
|
|
266
|
+
state.output.push([id, args.node]);
|
|
286
267
|
},
|
|
287
268
|
},
|
|
288
269
|
|
|
@@ -294,28 +275,6 @@ const BaseStore = createStore<State>()(
|
|
|
294
275
|
})),
|
|
295
276
|
);
|
|
296
277
|
|
|
297
|
-
function renderOutputArgs(args: MutateOutputArgs) {
|
|
298
|
-
let output = args.node;
|
|
299
|
-
|
|
300
|
-
switch (typeof args.node) {
|
|
301
|
-
case "boolean":
|
|
302
|
-
case "number":
|
|
303
|
-
case "string":
|
|
304
|
-
output = <Ink.Text dimColor={args.debug}>{String(args.node)}</Ink.Text>;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
if (args.debug) {
|
|
308
|
-
return (
|
|
309
|
-
<React.Fragment>
|
|
310
|
-
{args.withoutTimestamp ? null : <LogTimestamp />}
|
|
311
|
-
{output}
|
|
312
|
-
</React.Fragment>
|
|
313
|
-
);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
return output;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
278
|
function useState<R>(selector: (state: State) => R): R {
|
|
320
279
|
return useStore(BaseStore, selector);
|
|
321
280
|
}
|
package/src/app/SyncGithub.tsx
CHANGED
|
@@ -13,6 +13,7 @@ import { colors } from "~/core/colors";
|
|
|
13
13
|
import * as github from "~/core/github";
|
|
14
14
|
import { invariant } from "~/core/invariant";
|
|
15
15
|
import { safe_exists } from "~/core/safe_exists";
|
|
16
|
+
import { sleep } from "~/core/sleep";
|
|
16
17
|
|
|
17
18
|
import type * as CommitMetadata from "~/core/CommitMetadata";
|
|
18
19
|
|
|
@@ -25,18 +26,18 @@ async function run() {
|
|
|
25
26
|
const actions = state.actions;
|
|
26
27
|
const argv = state.argv;
|
|
27
28
|
const branch_name = state.branch_name;
|
|
29
|
+
const merge_base = state.merge_base;
|
|
28
30
|
const commit_map = state.commit_map;
|
|
29
31
|
const master_branch = state.master_branch;
|
|
30
|
-
const
|
|
32
|
+
const repo_path = state.repo_path;
|
|
31
33
|
const sync_github = state.sync_github;
|
|
32
34
|
|
|
33
35
|
invariant(branch_name, "branch_name must exist");
|
|
34
36
|
invariant(commit_map, "commit_map must exist");
|
|
35
|
-
invariant(
|
|
37
|
+
invariant(repo_path, "repo_path must exist");
|
|
36
38
|
invariant(sync_github, "sync_github must exist");
|
|
37
39
|
|
|
38
40
|
const commit_range = sync_github.commit_range;
|
|
39
|
-
const rebase_group_index = sync_github.rebase_group_index;
|
|
40
41
|
|
|
41
42
|
let DEFAULT_PR_BODY = "";
|
|
42
43
|
if (state.pr_template_body) {
|
|
@@ -45,9 +46,6 @@ async function run() {
|
|
|
45
46
|
|
|
46
47
|
const push_group_list = get_push_group_list();
|
|
47
48
|
|
|
48
|
-
// console.debug({ push_group_list });
|
|
49
|
-
// throw new Error("STOP");
|
|
50
|
-
|
|
51
49
|
// for all push targets in push_group_list
|
|
52
50
|
// things that can be done in parallel are grouped by numbers
|
|
53
51
|
//
|
|
@@ -159,16 +157,26 @@ async function run() {
|
|
|
159
157
|
|
|
160
158
|
actions.unregister_abort_handler();
|
|
161
159
|
|
|
160
|
+
// invalidate cache for PRs we pushed
|
|
162
161
|
actions.set((state) => {
|
|
163
|
-
// invalidate cache for PRs we pushed
|
|
164
162
|
for (const group of push_group_list) {
|
|
165
163
|
if (group.pr) {
|
|
166
164
|
delete state.pr[group.pr.headRefName];
|
|
167
|
-
delete state.
|
|
165
|
+
delete state.cache_gh_cli_by_branch[group.pr.headRefName];
|
|
168
166
|
}
|
|
169
167
|
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// wait a bit for github to settle after push / edits above
|
|
171
|
+
// we github.pr_list returns outdated information if called too quickly
|
|
172
|
+
await sleep(400);
|
|
170
173
|
|
|
171
|
-
|
|
174
|
+
// gather all open prs in repo at once
|
|
175
|
+
// cheaper query to populate cache
|
|
176
|
+
await github.pr_list();
|
|
177
|
+
|
|
178
|
+
// move to next step
|
|
179
|
+
actions.set((state) => {
|
|
172
180
|
state.step = "post-rebase-status";
|
|
173
181
|
});
|
|
174
182
|
} catch (err) {
|
|
@@ -181,24 +189,14 @@ async function run() {
|
|
|
181
189
|
}
|
|
182
190
|
|
|
183
191
|
function get_push_group_list() {
|
|
184
|
-
// start from HEAD and work backward to rebase_group_index
|
|
185
192
|
const push_group_list = [];
|
|
186
193
|
|
|
187
|
-
for (let
|
|
188
|
-
const index = commit_range.group_list.length - 1 - i;
|
|
189
|
-
|
|
190
|
-
// do not go past rebase_group_index
|
|
191
|
-
if (index < rebase_group_index) {
|
|
192
|
-
break;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const group = commit_range.group_list[index];
|
|
196
|
-
|
|
194
|
+
for (let group of commit_range.group_list) {
|
|
197
195
|
// skip the unassigned commits group
|
|
198
196
|
if (group.id === commit_range.UNASSIGNED) continue;
|
|
199
197
|
|
|
200
|
-
// if not --force, skip non-dirty
|
|
201
|
-
if (
|
|
198
|
+
// if not --force, skip non-dirty groups
|
|
199
|
+
if (!group.dirty && !argv.force) continue;
|
|
202
200
|
|
|
203
201
|
push_group_list.unshift(group);
|
|
204
202
|
}
|
|
@@ -229,7 +227,7 @@ async function run() {
|
|
|
229
227
|
// Unable to sync.
|
|
230
228
|
// ```
|
|
231
229
|
//
|
|
232
|
-
if (!
|
|
230
|
+
if (!is_pr_master_base(group)) {
|
|
233
231
|
await github.pr_edit({
|
|
234
232
|
branch: group.id,
|
|
235
233
|
base: master_branch,
|
|
@@ -247,7 +245,15 @@ async function run() {
|
|
|
247
245
|
invariant(group.base, "group.base must exist");
|
|
248
246
|
|
|
249
247
|
if (group.pr) {
|
|
250
|
-
|
|
248
|
+
// there are two scenarios where we should restore the base after push
|
|
249
|
+
// 1. if we aren't master base and pr is master base we should fix it
|
|
250
|
+
const base_mismatch = !group.master_base && is_pr_master_base(group);
|
|
251
|
+
// 2. if group pr was not master before the push we set it to master before pushing
|
|
252
|
+
// now we need to restore it back to how it was before the before_push
|
|
253
|
+
const was_modified_before_push = !is_pr_master_base(group);
|
|
254
|
+
|
|
255
|
+
const needs_base_fix = base_mismatch || was_modified_before_push;
|
|
256
|
+
if (needs_base_fix) {
|
|
251
257
|
// ensure base matches pr in github
|
|
252
258
|
await github.pr_edit({ branch: group.id, base: group.base });
|
|
253
259
|
}
|
|
@@ -302,25 +308,31 @@ async function run() {
|
|
|
302
308
|
}
|
|
303
309
|
}
|
|
304
310
|
|
|
305
|
-
function
|
|
311
|
+
function is_pr_master_base(group: CommitMetadataGroup) {
|
|
306
312
|
if (!group.pr) {
|
|
307
313
|
return false;
|
|
308
314
|
}
|
|
309
315
|
|
|
310
|
-
return
|
|
316
|
+
return `origin/${group.pr.baseRefName}` === master_branch;
|
|
311
317
|
}
|
|
312
318
|
|
|
313
319
|
async function push_master_group(group: CommitMetadataGroup) {
|
|
314
|
-
invariant(
|
|
320
|
+
invariant(repo_path, "repo_path must exist");
|
|
315
321
|
|
|
316
|
-
const
|
|
317
|
-
|
|
322
|
+
const worktree_path = path.join(
|
|
323
|
+
process.env.HOME,
|
|
324
|
+
".cache",
|
|
325
|
+
"git-stack",
|
|
326
|
+
"worktrees",
|
|
327
|
+
repo_path,
|
|
328
|
+
"push_master_group",
|
|
329
|
+
);
|
|
318
330
|
|
|
319
331
|
// ensure worktree for pushing master groups
|
|
320
332
|
if (!(await safe_exists(worktree_path))) {
|
|
321
333
|
actions.output(
|
|
322
334
|
<Ink.Text color={colors.white}>
|
|
323
|
-
Creating <Ink.Text color={colors.yellow}>{
|
|
335
|
+
Creating <Ink.Text color={colors.yellow}>{worktree_path}</Ink.Text>
|
|
324
336
|
</Ink.Text>,
|
|
325
337
|
);
|
|
326
338
|
actions.output(
|
|
@@ -331,13 +343,13 @@ async function run() {
|
|
|
331
343
|
|
|
332
344
|
// ensure worktree is clean + on the right base before applying commits
|
|
333
345
|
// - abort any in-progress cherry-pick/rebase
|
|
334
|
-
// - drop local changes/untracked files
|
|
346
|
+
// - drop local changes/untracked files to fresh state
|
|
335
347
|
// - reset to the desired base
|
|
336
348
|
await cli(`git -C ${worktree_path} cherry-pick --abort`, { ignoreExitCode: true });
|
|
337
349
|
await cli(`git -C ${worktree_path} rebase --abort`, { ignoreExitCode: true });
|
|
338
350
|
await cli(`git -C ${worktree_path} merge --abort`, { ignoreExitCode: true });
|
|
339
351
|
await cli(`git -C ${worktree_path} checkout -f ${master_branch}`);
|
|
340
|
-
await cli(`git -C ${worktree_path} reset --hard ${
|
|
352
|
+
await cli(`git -C ${worktree_path} reset --hard ${merge_base}`);
|
|
341
353
|
await cli(`git -C ${worktree_path} clean -fd`);
|
|
342
354
|
|
|
343
355
|
// cherry-pick the group commits onto that base
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
3
5
|
import * as Ink from "ink-cjs";
|
|
4
6
|
|
|
5
7
|
import { Await } from "~/app/Await";
|
|
6
8
|
import { Store } from "~/app/Store";
|
|
7
9
|
import { cli } from "~/core/cli";
|
|
8
10
|
import { colors } from "~/core/colors";
|
|
11
|
+
import { pretty_json } from "~/core/pretty_json";
|
|
9
12
|
|
|
10
13
|
type Props = {
|
|
11
14
|
children: React.ReactNode;
|
|
@@ -29,6 +32,11 @@ async function run() {
|
|
|
29
32
|
await cli(`echo USER=$USER`);
|
|
30
33
|
await cli(`echo GIT_AUTHOR_NAME=$GIT_AUTHOR_NAME`);
|
|
31
34
|
await cli(`echo GIT_AUTHOR_EMAIL=$GIT_AUTHOR_EMAIL`);
|
|
35
|
+
|
|
36
|
+
const PATH = process.env["PATH"];
|
|
37
|
+
const PATH_LIST = pretty_json(PATH.split(path.delimiter));
|
|
38
|
+
actions.debug(`process.env.PATH ${PATH_LIST}`);
|
|
39
|
+
|
|
32
40
|
await cli(`git config --list --show-origin`);
|
|
33
41
|
} catch (err) {
|
|
34
42
|
actions.error("Unable to log verbose debug information.");
|