git-stack-cli 2.7.0 → 2.7.2
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 +326 -287
- package/package.json +1 -1
- package/scripts/bun-build.ts +2 -1
- package/scripts/core/get_local_iso.ts +20 -0
- package/src/app/AutoUpdate.tsx +203 -176
- package/src/app/SelectCommitRanges.tsx +14 -16
- package/src/commands/Rebase.tsx +4 -0
- package/src/core/CommitMetadata.ts +8 -60
- package/src/core/Metadata.test.ts +3 -0
- package/src/core/Metadata.ts +11 -1
- package/src/core/__snapshots__/git.test.ts.snap +95 -0
- package/src/core/get_timeout_fn.ts +11 -0
- package/src/core/git.test.ts +54 -0
- package/src/core/git.ts +55 -0
- package/src/core/github.tsx +6 -0
package/package.json
CHANGED
package/scripts/bun-build.ts
CHANGED
|
@@ -3,6 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import * as util from "util";
|
|
4
4
|
|
|
5
5
|
import * as file from "~/core/file";
|
|
6
|
+
import { get_local_iso } from "~/core/get_local_iso";
|
|
6
7
|
import { spawn } from "~/core/spawn";
|
|
7
8
|
|
|
8
9
|
const parsed_args = util.parseArgs({
|
|
@@ -25,7 +26,7 @@ const WATCH = parsed_args.values.watch;
|
|
|
25
26
|
const VERBOSE = parsed_args.values.verbose;
|
|
26
27
|
|
|
27
28
|
function log(...args: any[]) {
|
|
28
|
-
const timestamp = new Date()
|
|
29
|
+
const timestamp = get_local_iso(new Date());
|
|
29
30
|
console.debug(`[${timestamp}]`, ...args);
|
|
30
31
|
}
|
|
31
32
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function get_local_iso(date: Date) {
|
|
2
|
+
const d: Record<string, string> = {};
|
|
3
|
+
for (const part of FORMATTER.formatToParts(date)) {
|
|
4
|
+
d[part.type] = part.value;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const ms = String(date.getMilliseconds()).padStart(3, "0");
|
|
8
|
+
const timestamp = `${d.year}-${d.month}-${d.day}T${d.hour}:${d.minute}:${d.second}.${ms}Z`;
|
|
9
|
+
return timestamp;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const FORMATTER = new Intl.DateTimeFormat("en-CA", {
|
|
13
|
+
year: "numeric",
|
|
14
|
+
month: "2-digit",
|
|
15
|
+
day: "2-digit",
|
|
16
|
+
hour: "2-digit",
|
|
17
|
+
minute: "2-digit",
|
|
18
|
+
second: "2-digit",
|
|
19
|
+
hour12: false,
|
|
20
|
+
});
|
package/src/app/AutoUpdate.tsx
CHANGED
|
@@ -5,14 +5,15 @@ import * as Ink from "ink-cjs";
|
|
|
5
5
|
import { Brackets } from "~/app/Brackets";
|
|
6
6
|
import { Command } from "~/app/Command";
|
|
7
7
|
import { FormatText } from "~/app/FormatText";
|
|
8
|
+
import { Url } from "~/app/Url";
|
|
8
9
|
import { YesNoPrompt } from "~/app/YesNoPrompt";
|
|
9
10
|
import { assertNever } from "~/core/assertNever";
|
|
10
11
|
import { cli } from "~/core/cli";
|
|
11
12
|
import { colors } from "~/core/colors";
|
|
12
13
|
import { fetch_json } from "~/core/fetch_json";
|
|
14
|
+
import { get_timeout_fn } from "~/core/get_timeout_fn";
|
|
13
15
|
import { is_finite_value } from "~/core/is_finite_value";
|
|
14
16
|
import { semver_compare } from "~/core/semver_compare";
|
|
15
|
-
import { sleep } from "~/core/sleep";
|
|
16
17
|
|
|
17
18
|
type Props = {
|
|
18
19
|
name: string;
|
|
@@ -26,10 +27,9 @@ type Props = {
|
|
|
26
27
|
};
|
|
27
28
|
|
|
28
29
|
type State = {
|
|
29
|
-
|
|
30
|
+
status: "init" | "prompt" | "install" | "done";
|
|
30
31
|
local_version: null | string;
|
|
31
32
|
latest_version: null | string;
|
|
32
|
-
status: "init" | "prompt" | "install" | "done";
|
|
33
33
|
is_brew_bun_standalone: boolean;
|
|
34
34
|
};
|
|
35
35
|
|
|
@@ -44,24 +44,94 @@ export function AutoUpdate(props: Props) {
|
|
|
44
44
|
const [output, set_output] = React.useState<Array<React.ReactNode>>([]);
|
|
45
45
|
|
|
46
46
|
const [state, patch] = React.useReducer(reducer, {
|
|
47
|
-
|
|
47
|
+
status: "init",
|
|
48
48
|
local_version: null,
|
|
49
49
|
latest_version: null,
|
|
50
|
-
status: "init",
|
|
51
50
|
is_brew_bun_standalone: false,
|
|
51
|
+
|
|
52
|
+
// // debugging
|
|
53
|
+
// status: "prompt",
|
|
54
|
+
// local_version: "2.5.3",
|
|
55
|
+
// latest_version: "2.7.0",
|
|
56
|
+
// is_brew_bun_standalone: true,
|
|
52
57
|
});
|
|
53
58
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
React.useEffect(handle_init_state, []);
|
|
60
|
+
React.useEffect(handle_status, [state.latest_version]);
|
|
61
|
+
React.useEffect(handle_on_done, [state.status]);
|
|
62
|
+
|
|
63
|
+
const status = render_status();
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<React.Fragment>
|
|
67
|
+
{output}
|
|
68
|
+
{status}
|
|
69
|
+
</React.Fragment>
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
function render_status() {
|
|
73
|
+
switch (state.status) {
|
|
74
|
+
case "init":
|
|
75
|
+
return null;
|
|
76
|
+
|
|
77
|
+
case "install":
|
|
78
|
+
return null;
|
|
79
|
+
|
|
80
|
+
case "done":
|
|
81
|
+
return props.children;
|
|
82
|
+
|
|
83
|
+
case "prompt": {
|
|
84
|
+
let install_command = "";
|
|
85
|
+
if (state.is_brew_bun_standalone) {
|
|
86
|
+
install_command = "brew install magus/git-stack/git-stack";
|
|
87
|
+
} else {
|
|
88
|
+
install_command = `npm install -g ${props.name}@latest`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<YesNoPrompt
|
|
93
|
+
message={
|
|
94
|
+
<Ink.Box flexDirection="column" gap={1}>
|
|
95
|
+
<Command>{install_command}</Command>
|
|
96
|
+
<FormatText
|
|
97
|
+
wrapper={<Ink.Text color={colors.yellow} />}
|
|
98
|
+
message="Would you like to run the above command to update?"
|
|
99
|
+
/>
|
|
100
|
+
</Ink.Box>
|
|
101
|
+
}
|
|
102
|
+
onNo={() => {
|
|
103
|
+
patch({ status: "done" });
|
|
104
|
+
}}
|
|
105
|
+
onYes={async () => {
|
|
106
|
+
info(<Command>{install_command}</Command>);
|
|
107
|
+
|
|
108
|
+
patch({ status: "install" });
|
|
109
|
+
|
|
110
|
+
await cli(install_command, {
|
|
111
|
+
env: {
|
|
112
|
+
...process.env,
|
|
113
|
+
HOMEBREW_COLOR: "1",
|
|
114
|
+
},
|
|
115
|
+
onOutput: (data: string) => {
|
|
116
|
+
info(<Ink.Text>{data}</Ink.Text>);
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
info(
|
|
121
|
+
<Ink.Text key="done">
|
|
122
|
+
✅ Installed <Brackets>{state.latest_version}</Brackets>
|
|
123
|
+
</Ink.Text>,
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
patch({ status: "done" });
|
|
127
|
+
}}
|
|
128
|
+
/>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
61
131
|
}
|
|
62
132
|
}
|
|
63
133
|
|
|
64
|
-
|
|
134
|
+
function handle_on_done() {
|
|
65
135
|
switch (state.status) {
|
|
66
136
|
case "init":
|
|
67
137
|
case "prompt":
|
|
@@ -76,199 +146,156 @@ export function AutoUpdate(props: Props) {
|
|
|
76
146
|
default:
|
|
77
147
|
assertNever(state.status);
|
|
78
148
|
}
|
|
79
|
-
}
|
|
149
|
+
}
|
|
80
150
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
let latest_version: string | null = null;
|
|
84
|
-
let is_brew_bun_standalone = false;
|
|
151
|
+
function handle_init_state() {
|
|
152
|
+
init_state().catch(abort);
|
|
85
153
|
|
|
86
|
-
|
|
87
|
-
|
|
154
|
+
async function init_state() {
|
|
155
|
+
if (state.latest_version !== null) return;
|
|
88
156
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
157
|
+
const local_version = process.env.CLI_VERSION;
|
|
158
|
+
const latest_version = await get_latest_version();
|
|
159
|
+
const is_brew_bun_standalone = get_is_brew_bun_standalone();
|
|
160
|
+
patch({ local_version, latest_version, is_brew_bun_standalone });
|
|
161
|
+
}
|
|
93
162
|
|
|
163
|
+
async function get_latest_version() {
|
|
94
164
|
const timeout_ms = is_finite_value(props.timeoutMs) ? props.timeoutMs : 2 * 1000;
|
|
165
|
+
const timeout = get_timeout_fn(timeout_ms, "AutoUpdate timeout");
|
|
166
|
+
const npm_json = await timeout(fetch_json(`https://registry.npmjs.org/${props.name}`));
|
|
167
|
+
const maybe_version = npm_json?.["dist-tags"]?.latest;
|
|
168
|
+
if (typeof maybe_version === "string") {
|
|
169
|
+
return maybe_version;
|
|
170
|
+
}
|
|
171
|
+
throw new Error("Unable to retrieve latest version from npm");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function get_is_brew_bun_standalone() {
|
|
175
|
+
const binary_path = process.argv[1];
|
|
176
|
+
debug(<Ink.Text dimColor>{JSON.stringify({ binary_path })}</Ink.Text>);
|
|
177
|
+
|
|
178
|
+
const is_bunfs_path = binary_path.startsWith("/$bunfs");
|
|
179
|
+
debug(
|
|
180
|
+
<Ink.Text dimColor>
|
|
181
|
+
{is_bunfs_path
|
|
182
|
+
? "brew install detected (compiled bun standalone)"
|
|
183
|
+
: "npm install detected"}
|
|
184
|
+
</Ink.Text>,
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
return is_bunfs_path;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
95
190
|
|
|
96
|
-
|
|
97
|
-
|
|
191
|
+
function handle_status() {
|
|
192
|
+
const latest_version = state.latest_version;
|
|
98
193
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
]);
|
|
194
|
+
if (latest_version === null) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
103
197
|
|
|
104
|
-
|
|
198
|
+
const local_version = state.local_version;
|
|
105
199
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
200
|
+
if (!local_version) {
|
|
201
|
+
throw new Error("Auto update requires process.env.CLI_VERSION to be set");
|
|
202
|
+
}
|
|
109
203
|
|
|
110
|
-
|
|
204
|
+
debug(
|
|
205
|
+
<FormatText
|
|
206
|
+
key="versions"
|
|
207
|
+
wrapper={<Ink.Text dimColor />}
|
|
208
|
+
message="Auto update found latest version {latest_version} and current local version {local_version}"
|
|
209
|
+
values={{
|
|
210
|
+
latest_version: <Brackets>{latest_version}</Brackets>,
|
|
211
|
+
local_version: <Brackets>{local_version}</Brackets>,
|
|
212
|
+
}}
|
|
213
|
+
/>,
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
const semver_result = semver_compare(latest_version, local_version);
|
|
217
|
+
debug(<Ink.Text dimColor>{JSON.stringify({ semver_result })}</Ink.Text>);
|
|
218
|
+
|
|
219
|
+
switch (semver_result) {
|
|
220
|
+
case 0: {
|
|
221
|
+
info(
|
|
222
|
+
<Ink.Text>
|
|
223
|
+
✅ Everything up to date. <Brackets>{latest_version}</Brackets>
|
|
224
|
+
</Ink.Text>,
|
|
225
|
+
);
|
|
111
226
|
|
|
112
|
-
|
|
113
|
-
handle_output(<Ink.Text dimColor>{JSON.stringify({ binary_path })}</Ink.Text>);
|
|
227
|
+
return patch({ status: "done" });
|
|
114
228
|
}
|
|
115
229
|
|
|
116
|
-
|
|
230
|
+
case 1: {
|
|
231
|
+
const old_tag = local_version;
|
|
232
|
+
const new_tag = state.latest_version;
|
|
233
|
+
const url = `https://github.com/magus/git-stack-cli/compare/${old_tag}...${new_tag}`;
|
|
117
234
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
235
|
+
info(
|
|
236
|
+
<Ink.Box flexDirection="column" gap={1} paddingTop={1} paddingBottom={1}>
|
|
237
|
+
<Ink.Text>
|
|
238
|
+
🆕 New version available! <Brackets>{latest_version}</Brackets>
|
|
239
|
+
</Ink.Text>
|
|
240
|
+
<Ink.Box flexDirection="column">
|
|
241
|
+
<Ink.Text dimColor>Changelog</Ink.Text>
|
|
242
|
+
<Url>{url}</Url>
|
|
243
|
+
</Ink.Box>
|
|
244
|
+
</Ink.Box>,
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
return patch({ status: "prompt" });
|
|
126
248
|
}
|
|
127
249
|
|
|
128
|
-
|
|
129
|
-
|
|
250
|
+
case -1: {
|
|
251
|
+
info(
|
|
130
252
|
<FormatText
|
|
131
|
-
|
|
132
|
-
wrapper={<Ink.Text />}
|
|
133
|
-
message="Auto update found latest version {latest_version} and current local version {local_version}"
|
|
253
|
+
message="⚠️ Local version {local_version} is newer than latest version {latest_version}"
|
|
134
254
|
values={{
|
|
135
|
-
latest_version: <Brackets>{latest_version}</Brackets>,
|
|
136
255
|
local_version: <Brackets>{local_version}</Brackets>,
|
|
256
|
+
latest_version: <Brackets>{latest_version}</Brackets>,
|
|
137
257
|
}}
|
|
138
258
|
/>,
|
|
139
259
|
);
|
|
140
|
-
}
|
|
141
260
|
|
|
142
|
-
|
|
143
|
-
if (props_ref.current.verbose) {
|
|
144
|
-
handle_output(<Ink.Text dimColor>{JSON.stringify({ semver_result })}</Ink.Text>);
|
|
261
|
+
return patch({ status: "done" });
|
|
145
262
|
}
|
|
146
263
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
if (is_output) {
|
|
151
|
-
handle_output(
|
|
152
|
-
<Ink.Text>
|
|
153
|
-
✅ Everything up to date. <Brackets>{latest_version}</Brackets>
|
|
154
|
-
</Ink.Text>,
|
|
155
|
-
);
|
|
156
|
-
}
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if (semver_result === 1) {
|
|
161
|
-
// trigger yes no prompt
|
|
162
|
-
status = "prompt";
|
|
264
|
+
default: {
|
|
265
|
+
assertNever(semver_result);
|
|
266
|
+
abort(new Error("AutoUpdate failed"));
|
|
163
267
|
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
164
270
|
|
|
165
|
-
|
|
271
|
+
function info(node: React.ReactNode) {
|
|
272
|
+
if (props_ref.current.verbose || props_ref.current.force) {
|
|
273
|
+
handle_output(node);
|
|
166
274
|
}
|
|
275
|
+
}
|
|
167
276
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
277
|
+
function debug(node: React.ReactNode) {
|
|
278
|
+
if (props_ref.current.verbose) {
|
|
279
|
+
handle_output(node);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
function abort(error: Error) {
|
|
283
|
+
info(
|
|
284
|
+
<Ink.Text key="error" color={colors.red}>
|
|
285
|
+
{error.message}
|
|
286
|
+
</Ink.Text>,
|
|
287
|
+
);
|
|
288
|
+
patch({ status: "done" });
|
|
289
|
+
props_ref.current.onError?.(error);
|
|
290
|
+
}
|
|
182
291
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
292
|
+
function handle_output(node: React.ReactNode) {
|
|
293
|
+
if (typeof props.onOutput === "function") {
|
|
294
|
+
props.onOutput(node);
|
|
295
|
+
} else {
|
|
296
|
+
set_output((current) => {
|
|
297
|
+
return [...current, node];
|
|
187
298
|
});
|
|
188
|
-
}, []);
|
|
189
|
-
|
|
190
|
-
const status = (function render_status() {
|
|
191
|
-
switch (state.status) {
|
|
192
|
-
case "init":
|
|
193
|
-
return null;
|
|
194
|
-
|
|
195
|
-
case "prompt": {
|
|
196
|
-
let install_command = "";
|
|
197
|
-
if (state.is_brew_bun_standalone) {
|
|
198
|
-
install_command = "brew install magus/git-stack/git-stack";
|
|
199
|
-
} else {
|
|
200
|
-
install_command = `npm install -g ${props.name}@latest`;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
return (
|
|
204
|
-
<YesNoPrompt
|
|
205
|
-
message={
|
|
206
|
-
<Ink.Box flexDirection="column">
|
|
207
|
-
<Ink.Box flexDirection="column">
|
|
208
|
-
<Ink.Text color={colors.yellow}>
|
|
209
|
-
<FormatText
|
|
210
|
-
wrapper={<Ink.Text />}
|
|
211
|
-
message="New version available {latest_version}"
|
|
212
|
-
values={{
|
|
213
|
-
latest_version: <Brackets>{state.latest_version}</Brackets>,
|
|
214
|
-
}}
|
|
215
|
-
/>
|
|
216
|
-
,
|
|
217
|
-
</Ink.Text>
|
|
218
|
-
<Ink.Text> </Ink.Text>
|
|
219
|
-
<Command>{install_command}</Command>
|
|
220
|
-
<Ink.Text> </Ink.Text>
|
|
221
|
-
</Ink.Box>
|
|
222
|
-
<Ink.Box>
|
|
223
|
-
<FormatText
|
|
224
|
-
wrapper={<Ink.Text color={colors.yellow} />}
|
|
225
|
-
message="Would you like to run the above command to update?"
|
|
226
|
-
/>
|
|
227
|
-
</Ink.Box>
|
|
228
|
-
</Ink.Box>
|
|
229
|
-
}
|
|
230
|
-
onYes={async () => {
|
|
231
|
-
handle_output(<Command>{install_command}</Command>);
|
|
232
|
-
|
|
233
|
-
patch({ status: "install" });
|
|
234
|
-
|
|
235
|
-
await cli(install_command, {
|
|
236
|
-
env: {
|
|
237
|
-
...process.env,
|
|
238
|
-
HOMEBREW_COLOR: "1",
|
|
239
|
-
},
|
|
240
|
-
onOutput: (data: string) => {
|
|
241
|
-
handle_output(<Ink.Text>{data}</Ink.Text>);
|
|
242
|
-
},
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
handle_output(
|
|
246
|
-
<Ink.Text key="done">
|
|
247
|
-
✅ Installed <Brackets>{state.latest_version}</Brackets>
|
|
248
|
-
</Ink.Text>,
|
|
249
|
-
);
|
|
250
|
-
|
|
251
|
-
patch({ status: "done" });
|
|
252
|
-
}}
|
|
253
|
-
onNo={() => {
|
|
254
|
-
patch({ status: "done" });
|
|
255
|
-
}}
|
|
256
|
-
/>
|
|
257
|
-
);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
case "install":
|
|
261
|
-
return null;
|
|
262
|
-
|
|
263
|
-
case "done":
|
|
264
|
-
return props.children;
|
|
265
299
|
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
return (
|
|
269
|
-
<React.Fragment>
|
|
270
|
-
{output}
|
|
271
|
-
{status}
|
|
272
|
-
</React.Fragment>
|
|
273
|
-
);
|
|
300
|
+
}
|
|
274
301
|
}
|
|
@@ -380,7 +380,11 @@ function SelectCommitRangesInternal(props: Props) {
|
|
|
380
380
|
{sync_status !== "allow_unassigned" ? null : (
|
|
381
381
|
<FormatText
|
|
382
382
|
wrapper={<Ink.Text color={colors.gray} />}
|
|
383
|
-
message=
|
|
383
|
+
message={
|
|
384
|
+
argv.sync
|
|
385
|
+
? "Press {s} to {sync} the {count} assigned commits to Github"
|
|
386
|
+
: "Press {s} to {sync} the {count} assigned commits locally"
|
|
387
|
+
}
|
|
384
388
|
values={{
|
|
385
389
|
...S_TO_SYNC_VALUES,
|
|
386
390
|
count: (
|
|
@@ -393,21 +397,15 @@ function SelectCommitRangesInternal(props: Props) {
|
|
|
393
397
|
)}
|
|
394
398
|
</React.Fragment>
|
|
395
399
|
) : (
|
|
396
|
-
<
|
|
397
|
-
{
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
wrapper={<Ink.Text />}
|
|
406
|
-
message="🎉 Done! Press {s} to {save} the commits locally"
|
|
407
|
-
values={S_TO_SYNC_VALUES}
|
|
408
|
-
/>
|
|
409
|
-
)}
|
|
410
|
-
</React.Fragment>
|
|
400
|
+
<FormatText
|
|
401
|
+
wrapper={<Ink.Text />}
|
|
402
|
+
message={
|
|
403
|
+
argv.sync
|
|
404
|
+
? "🎉 Done! Press {s} to {sync} the PRs to Github"
|
|
405
|
+
: "🎉 Done! Press {s} to {sync} the PRs locally"
|
|
406
|
+
}
|
|
407
|
+
values={S_TO_SYNC_VALUES}
|
|
408
|
+
/>
|
|
411
409
|
)}
|
|
412
410
|
|
|
413
411
|
<Ink.Box>
|
package/src/commands/Rebase.tsx
CHANGED
|
@@ -78,7 +78,9 @@ Rebase.run = async function run(props: Props) {
|
|
|
78
78
|
actions.exit(20);
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
actions.debug("start CommitMetadata.range");
|
|
81
82
|
const next_commit_range = await CommitMetadata.range();
|
|
83
|
+
actions.debug("end CommitMetadata.range");
|
|
82
84
|
|
|
83
85
|
actions.output(
|
|
84
86
|
<FormatText
|
|
@@ -167,7 +169,9 @@ Rebase.run = async function run(props: Props) {
|
|
|
167
169
|
// of original branch to the newly created temporary branch
|
|
168
170
|
await cli(`git branch -f ${branch_name} ${temp_branch_name}`);
|
|
169
171
|
|
|
172
|
+
actions.debug("start restore_git()");
|
|
170
173
|
restore_git();
|
|
174
|
+
actions.debug("end restore_git()");
|
|
171
175
|
}
|
|
172
176
|
|
|
173
177
|
// cleanup git operations if cancelled during manual rebase
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { Store } from "~/app/Store";
|
|
2
|
-
import * as
|
|
3
|
-
import { cli } from "~/core/cli";
|
|
2
|
+
import * as git from "~/core/git";
|
|
4
3
|
import * as github from "~/core/github";
|
|
5
4
|
|
|
6
|
-
export type CommitMetadata = Awaited<ReturnType<typeof commit>>;
|
|
7
5
|
export type CommitRange = Awaited<ReturnType<typeof range>>;
|
|
8
6
|
|
|
9
7
|
type GithubPRStatus = ReturnType<typeof github.pr_status>;
|
|
@@ -15,24 +13,25 @@ type CommitGroup = {
|
|
|
15
13
|
pr: null | PullRequest;
|
|
16
14
|
base: null | string;
|
|
17
15
|
dirty: boolean;
|
|
18
|
-
commits: Array<
|
|
16
|
+
commits: Array<git.Commit>;
|
|
19
17
|
};
|
|
20
18
|
|
|
21
19
|
export type SimpleGroup = { id: string; title: string };
|
|
22
20
|
type CommitGroupMap = { [sha: string]: SimpleGroup };
|
|
23
21
|
|
|
24
22
|
export async function range(commit_group_map?: CommitGroupMap) {
|
|
25
|
-
const master_branch = Store.getState().master_branch;
|
|
26
|
-
|
|
27
23
|
// gather all open prs in repo first
|
|
28
24
|
// cheaper query to populate cache
|
|
29
25
|
await github.pr_list();
|
|
30
26
|
|
|
31
|
-
const
|
|
27
|
+
const master_branch = Store.getState().master_branch;
|
|
28
|
+
const commit_list = await git.get_commits(`${master_branch}..HEAD`);
|
|
32
29
|
|
|
33
30
|
const pr_lookup: Record<string, void | PullRequest> = {};
|
|
34
31
|
|
|
35
32
|
let invalid = false;
|
|
33
|
+
let last_group_id: null | string = null;
|
|
34
|
+
|
|
36
35
|
const group_map = new Map<string, CommitGroup>();
|
|
37
36
|
|
|
38
37
|
for (const commit of commit_list) {
|
|
@@ -57,10 +56,7 @@ export async function range(commit_group_map?: CommitGroupMap) {
|
|
|
57
56
|
}
|
|
58
57
|
|
|
59
58
|
if (id) {
|
|
60
|
-
|
|
61
|
-
const last_key = group_key_list[group_key_list.length - 1];
|
|
62
|
-
|
|
63
|
-
if (group_map.has(id) && last_key !== id) {
|
|
59
|
+
if (group_map.has(id) && last_group_id !== id) {
|
|
64
60
|
// if we've seen this id before and it's not
|
|
65
61
|
// the last added key then we are out of order
|
|
66
62
|
// console.debug("INVALID", "OUT OF ORDER");
|
|
@@ -87,6 +83,7 @@ export async function range(commit_group_map?: CommitGroupMap) {
|
|
|
87
83
|
|
|
88
84
|
group.commits.push(commit);
|
|
89
85
|
group_map.set(id, group);
|
|
86
|
+
last_group_id = id;
|
|
90
87
|
}
|
|
91
88
|
|
|
92
89
|
// check each group for dirty state and base
|
|
@@ -183,53 +180,4 @@ export async function range(commit_group_map?: CommitGroupMap) {
|
|
|
183
180
|
return { invalid, group_list, commit_list, pr_lookup, UNASSIGNED };
|
|
184
181
|
}
|
|
185
182
|
|
|
186
|
-
async function get_commit_list() {
|
|
187
|
-
const master_branch = Store.getState().master_branch;
|
|
188
|
-
const log_result = await cli(
|
|
189
|
-
`git log ${master_branch}..HEAD --oneline --format=%H --color=never`,
|
|
190
|
-
);
|
|
191
|
-
|
|
192
|
-
if (!log_result.stdout) {
|
|
193
|
-
return [];
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
const sha_list = lines(log_result.stdout).reverse();
|
|
197
|
-
|
|
198
|
-
const commit_metadata_list = [];
|
|
199
|
-
|
|
200
|
-
for (let i = 0; i < sha_list.length; i++) {
|
|
201
|
-
const sha = sha_list[i];
|
|
202
|
-
const commit_metadata = await commit(sha);
|
|
203
|
-
commit_metadata_list.push(commit_metadata);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return commit_metadata_list;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
export async function commit(sha: string) {
|
|
210
|
-
const full_message = (await cli(`git show -s --format=%B ${sha}`)).stdout;
|
|
211
|
-
const metadata = await Metadata.read(full_message);
|
|
212
|
-
const branch_id = metadata?.id;
|
|
213
|
-
const subject_line = get_subject_line(full_message);
|
|
214
|
-
const title = metadata?.title;
|
|
215
|
-
|
|
216
|
-
return {
|
|
217
|
-
sha,
|
|
218
|
-
full_message,
|
|
219
|
-
subject_line,
|
|
220
|
-
branch_id,
|
|
221
|
-
title,
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
function get_subject_line(message: string) {
|
|
226
|
-
const line_list = lines(message);
|
|
227
|
-
const first_line = line_list[0];
|
|
228
|
-
return Metadata.remove(first_line);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
function lines(value: string) {
|
|
232
|
-
return value.split("\n");
|
|
233
|
-
}
|
|
234
|
-
|
|
235
183
|
export const UNASSIGNED = "unassigned";
|