git-stack-cli 2.9.4 → 2.9.6
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 +74 -74
- package/package.json +1 -1
- package/src/app/ManualRebase.tsx +1 -0
- package/src/app/Store.tsx +5 -0
- package/src/app/SyncGithub.tsx +3 -40
- package/src/commands/Rebase.tsx +1 -0
- package/src/core/CommitMetadata.ts +8 -1
- package/src/core/cache_message.tsx +36 -0
- package/src/core/git.tsx +193 -0
- package/src/core/github.tsx +1 -30
- package/src/core/git.ts +0 -83
package/package.json
CHANGED
package/src/app/ManualRebase.tsx
CHANGED
|
@@ -147,6 +147,7 @@ async function run() {
|
|
|
147
147
|
// always hard reset and clean to allow subsequent checkout
|
|
148
148
|
// if there are files checkout will fail and cascade fail subsequent commands
|
|
149
149
|
cli.sync(`git reset --hard`, spawn_options);
|
|
150
|
+
cli.sync(`git cherry-pick --abort`, spawn_options);
|
|
150
151
|
cli.sync(`git clean -fd`, spawn_options);
|
|
151
152
|
|
|
152
153
|
// always put self back in original branch
|
package/src/app/Store.tsx
CHANGED
|
@@ -78,6 +78,7 @@ export type State = {
|
|
|
78
78
|
// cache
|
|
79
79
|
pr: { [branch: string]: PullRequest };
|
|
80
80
|
cache_gh_cli_by_branch: { [branch: string]: { [command: string]: string } };
|
|
81
|
+
cache_diff: { [key: string]: string };
|
|
81
82
|
|
|
82
83
|
actions: {
|
|
83
84
|
exit(code: number, args?: ExitArgs): void;
|
|
@@ -139,6 +140,7 @@ const BaseStore = createStore<State>()(
|
|
|
139
140
|
|
|
140
141
|
pr: {},
|
|
141
142
|
cache_gh_cli_by_branch: {},
|
|
143
|
+
cache_diff: {},
|
|
142
144
|
|
|
143
145
|
actions: {
|
|
144
146
|
exit(code, args) {
|
|
@@ -206,6 +208,9 @@ const BaseStore = createStore<State>()(
|
|
|
206
208
|
|
|
207
209
|
output(node) {
|
|
208
210
|
set((state) => {
|
|
211
|
+
if (typeof node === "string") {
|
|
212
|
+
node = <Ink.Text>{node}</Ink.Text>;
|
|
213
|
+
}
|
|
209
214
|
state.mutate.output(state, { node });
|
|
210
215
|
});
|
|
211
216
|
},
|
package/src/app/SyncGithub.tsx
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
|
|
5
3
|
import * as Ink from "ink-cjs";
|
|
6
4
|
import last from "lodash/last";
|
|
7
5
|
|
|
@@ -10,9 +8,9 @@ import { Store } from "~/app/Store";
|
|
|
10
8
|
import * as StackSummaryTable from "~/core/StackSummaryTable";
|
|
11
9
|
import { cli } from "~/core/cli";
|
|
12
10
|
import { colors } from "~/core/colors";
|
|
11
|
+
import * as git from "~/core/git";
|
|
13
12
|
import * as github from "~/core/github";
|
|
14
13
|
import { invariant } from "~/core/invariant";
|
|
15
|
-
import { safe_exists } from "~/core/safe_exists";
|
|
16
14
|
import { sleep } from "~/core/sleep";
|
|
17
15
|
|
|
18
16
|
import type * as CommitMetadata from "~/core/CommitMetadata";
|
|
@@ -26,7 +24,6 @@ async function run() {
|
|
|
26
24
|
const actions = state.actions;
|
|
27
25
|
const argv = state.argv;
|
|
28
26
|
const branch_name = state.branch_name;
|
|
29
|
-
const merge_base = state.merge_base;
|
|
30
27
|
const commit_map = state.commit_map;
|
|
31
28
|
const master_branch = state.master_branch;
|
|
32
29
|
const repo_path = state.repo_path;
|
|
@@ -319,42 +316,8 @@ async function run() {
|
|
|
319
316
|
async function push_master_group(group: CommitMetadataGroup) {
|
|
320
317
|
invariant(repo_path, "repo_path must exist");
|
|
321
318
|
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
".cache",
|
|
325
|
-
"git-stack",
|
|
326
|
-
"worktrees",
|
|
327
|
-
repo_path,
|
|
328
|
-
"push_master_group",
|
|
329
|
-
);
|
|
330
|
-
|
|
331
|
-
// ensure worktree for pushing master groups
|
|
332
|
-
if (!(await safe_exists(worktree_path))) {
|
|
333
|
-
actions.output(
|
|
334
|
-
<Ink.Text color={colors.white}>
|
|
335
|
-
Creating <Ink.Text color={colors.yellow}>{worktree_path}</Ink.Text>
|
|
336
|
-
</Ink.Text>,
|
|
337
|
-
);
|
|
338
|
-
actions.output(
|
|
339
|
-
<Ink.Text color={colors.gray}>(this may take a moment the first time…)</Ink.Text>,
|
|
340
|
-
);
|
|
341
|
-
await cli(`git worktree add -f ${worktree_path} ${master_branch}`);
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
// ensure worktree is clean + on the right base before applying commits
|
|
345
|
-
// - abort any in-progress cherry-pick/rebase
|
|
346
|
-
// - drop local changes/untracked files to fresh state
|
|
347
|
-
// - reset to the desired base
|
|
348
|
-
await cli(`git -C ${worktree_path} cherry-pick --abort`, { ignoreExitCode: true });
|
|
349
|
-
await cli(`git -C ${worktree_path} rebase --abort`, { ignoreExitCode: true });
|
|
350
|
-
await cli(`git -C ${worktree_path} merge --abort`, { ignoreExitCode: true });
|
|
351
|
-
await cli(`git -C ${worktree_path} checkout -f ${master_branch}`);
|
|
352
|
-
await cli(`git -C ${worktree_path} reset --hard ${merge_base}`);
|
|
353
|
-
await cli(`git -C ${worktree_path} clean -fd`);
|
|
354
|
-
|
|
355
|
-
// cherry-pick the group commits onto that base
|
|
356
|
-
const cp_commit_list = group.commits.map((c) => c.sha).join(" ");
|
|
357
|
-
await cli(`git -C ${worktree_path} cherry-pick ${cp_commit_list}`);
|
|
319
|
+
const commit_list = group.commits;
|
|
320
|
+
const { worktree_path } = await git.worktree_add({ commit_list });
|
|
358
321
|
|
|
359
322
|
const push_target = `HEAD:refs/heads/${group.id}`;
|
|
360
323
|
const git_push_command = create_git_push_command(`git -C ${worktree_path}`, push_target);
|
package/src/commands/Rebase.tsx
CHANGED
|
@@ -184,6 +184,7 @@ Rebase.run = async function run(props: Props) {
|
|
|
184
184
|
// always hard reset and clean to allow subsequent checkout
|
|
185
185
|
// if there are files checkout will fail and cascade fail subsequent commands
|
|
186
186
|
cli.sync(`git reset --hard`, spawn_options);
|
|
187
|
+
cli.sync(`git cherry-pick --abort`, spawn_options);
|
|
187
188
|
cli.sync(`git clean -fd`, spawn_options);
|
|
188
189
|
|
|
189
190
|
// always put self back in original branch
|
|
@@ -215,7 +215,7 @@ export async function range(commit_group_map?: CommitGroupMap) {
|
|
|
215
215
|
let diff_github = await github.pr_diff(group.pr.headRefName);
|
|
216
216
|
diff_github = normalize_diff(diff_github);
|
|
217
217
|
|
|
218
|
-
let diff_local = await git.
|
|
218
|
+
let diff_local = await git.diff_commits(group.commits);
|
|
219
219
|
diff_local = normalize_diff(diff_local);
|
|
220
220
|
|
|
221
221
|
// find the first differing character index
|
|
@@ -260,14 +260,21 @@ export async function range(commit_group_map?: CommitGroupMap) {
|
|
|
260
260
|
const all_commits: Array<git.Commit> = [];
|
|
261
261
|
const previous_groups = group_value_list.slice(0, i);
|
|
262
262
|
for (const g of previous_groups) {
|
|
263
|
+
actions.debug(` BOUNDARY_COMMIT group=${g.title}`);
|
|
264
|
+
|
|
263
265
|
for (const c of g.commits) {
|
|
266
|
+
actions.debug(` BOUNDARY_COMMIT commit=${c.subject_line}`);
|
|
264
267
|
all_commits.push(c);
|
|
265
268
|
}
|
|
266
269
|
}
|
|
267
270
|
for (const c of group.commits) {
|
|
271
|
+
actions.debug(` BOUNDARY_COMMIT commit=${c.subject_line}`);
|
|
268
272
|
all_commits.push(c);
|
|
269
273
|
}
|
|
270
274
|
|
|
275
|
+
actions.debug(` BOUNDARY_COMMIT group.pr.commits.length=${group.pr.commits.length}`);
|
|
276
|
+
actions.debug(` BOUNDARY_COMMIT all_commits=${all_commits.length}`);
|
|
277
|
+
|
|
271
278
|
// compare all commits against pr commits
|
|
272
279
|
if (group.pr.commits.length !== all_commits.length) {
|
|
273
280
|
actions.debug(" BOUNDARY_COMMIT_LENGTH_MISMATCH");
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import * as Ink from "ink-cjs";
|
|
4
|
+
|
|
5
|
+
import { FormatText } from "~/app/FormatText";
|
|
6
|
+
import { colors } from "~/core/colors";
|
|
7
|
+
|
|
8
|
+
type CacheMessageArgs = {
|
|
9
|
+
hit: boolean;
|
|
10
|
+
message: React.ReactNode;
|
|
11
|
+
extra: React.ReactNode;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function cache_message(args: CacheMessageArgs) {
|
|
15
|
+
const status = args.hit ? (
|
|
16
|
+
<Ink.Text bold color={colors.green}>
|
|
17
|
+
HIT
|
|
18
|
+
</Ink.Text>
|
|
19
|
+
) : (
|
|
20
|
+
<Ink.Text bold color={colors.red}>
|
|
21
|
+
MISS
|
|
22
|
+
</Ink.Text>
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<FormatText
|
|
27
|
+
wrapper={<Ink.Text dimColor />}
|
|
28
|
+
message="{message} {status} {extra}"
|
|
29
|
+
values={{
|
|
30
|
+
message: args.message,
|
|
31
|
+
status,
|
|
32
|
+
extra: args.extra,
|
|
33
|
+
}}
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
}
|
package/src/core/git.tsx
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
import * as Ink from "ink-cjs";
|
|
6
|
+
|
|
7
|
+
import { Store } from "~/app/Store";
|
|
8
|
+
import * as Metadata from "~/core/Metadata";
|
|
9
|
+
import { cache_message } from "~/core/cache_message";
|
|
10
|
+
import { cli } from "~/core/cli";
|
|
11
|
+
import { colors } from "~/core/colors";
|
|
12
|
+
import { invariant } from "~/core/invariant";
|
|
13
|
+
import { safe_exists } from "~/core/safe_exists";
|
|
14
|
+
|
|
15
|
+
type CommitList = Awaited<ReturnType<typeof get_commits>>;
|
|
16
|
+
|
|
17
|
+
export type Commit = CommitList[0];
|
|
18
|
+
|
|
19
|
+
export async function get_commits(dot_range: string) {
|
|
20
|
+
const log_result = await cli(`git log ${dot_range} --format=${FORMAT} --color=never`);
|
|
21
|
+
|
|
22
|
+
if (!log_result.stdout) {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const commit_list = [];
|
|
27
|
+
|
|
28
|
+
for (let record of log_result.stdout.split(SEP.record)) {
|
|
29
|
+
record = record.replace(/^\n/, "");
|
|
30
|
+
record = record.replace(/\n$/, "");
|
|
31
|
+
|
|
32
|
+
if (!record) continue;
|
|
33
|
+
|
|
34
|
+
const [sha, full_message] = record.split(SEP.field);
|
|
35
|
+
|
|
36
|
+
// ensure sha is a hex string, otherwise we should throw an error
|
|
37
|
+
if (!RE.git_sha.test(sha)) {
|
|
38
|
+
const actions = Store.getState().actions;
|
|
39
|
+
const sep_values = JSON.stringify(Object.values(SEP));
|
|
40
|
+
const message = `unable to parse git commits, maybe commit message contained ${sep_values}`;
|
|
41
|
+
actions.error(message);
|
|
42
|
+
actions.exit(19);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const metadata = Metadata.read(full_message);
|
|
46
|
+
const branch_id = metadata.id;
|
|
47
|
+
const subject_line = metadata.subject || "";
|
|
48
|
+
const title = metadata.title;
|
|
49
|
+
const master_base = metadata.base === "master";
|
|
50
|
+
|
|
51
|
+
const commit = {
|
|
52
|
+
sha,
|
|
53
|
+
full_message,
|
|
54
|
+
subject_line,
|
|
55
|
+
branch_id,
|
|
56
|
+
title,
|
|
57
|
+
master_base,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
commit_list.push(commit);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
commit_list.reverse();
|
|
64
|
+
|
|
65
|
+
return commit_list;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
type WorktreeAddArgs = {
|
|
69
|
+
name?: string;
|
|
70
|
+
commit_list: CommitList;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
type WorktreeAddResult = {
|
|
74
|
+
worktree_path: string;
|
|
75
|
+
merge_base: string;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export async function worktree_add(args: WorktreeAddArgs): Promise<WorktreeAddResult> {
|
|
79
|
+
const state = Store.getState();
|
|
80
|
+
const actions = state.actions;
|
|
81
|
+
const merge_base = state.merge_base;
|
|
82
|
+
invariant(merge_base, "merge_base must exist");
|
|
83
|
+
const repo_path = state.repo_path;
|
|
84
|
+
invariant(repo_path, "repo_path must exist");
|
|
85
|
+
|
|
86
|
+
const worktree_name = args.name || "push_master_group";
|
|
87
|
+
const worktree_path = path.join(
|
|
88
|
+
process.env.HOME,
|
|
89
|
+
".cache",
|
|
90
|
+
"git-stack",
|
|
91
|
+
"worktrees",
|
|
92
|
+
repo_path,
|
|
93
|
+
worktree_name,
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
if (!(await safe_exists(worktree_path))) {
|
|
97
|
+
actions.output(
|
|
98
|
+
<Ink.Text color={colors.white}>
|
|
99
|
+
Creating <Ink.Text color={colors.yellow}>{worktree_path}</Ink.Text>
|
|
100
|
+
</Ink.Text>,
|
|
101
|
+
);
|
|
102
|
+
actions.output(
|
|
103
|
+
<Ink.Text color={colors.gray}>(this may take a moment the first time…)</Ink.Text>,
|
|
104
|
+
);
|
|
105
|
+
await cli(`git worktree add -f --detach ${worktree_path} ${merge_base}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ensure worktree is clean + on the right base before applying commits
|
|
109
|
+
// - abort any in-progress cherry-pick/rebase
|
|
110
|
+
// - drop local changes/untracked files to fresh state
|
|
111
|
+
const quiet_ignore = { quiet: true, ignoreExitCode: true };
|
|
112
|
+
await cli(`git -C ${worktree_path} cherry-pick --abort`, quiet_ignore);
|
|
113
|
+
await cli(`git -C ${worktree_path} rebase --abort`, quiet_ignore);
|
|
114
|
+
await cli(`git -C ${worktree_path} merge --abort`, quiet_ignore);
|
|
115
|
+
await cli(`git -C ${worktree_path} checkout -f --detach ${merge_base}`);
|
|
116
|
+
await cli(`git -C ${worktree_path} clean -fd`);
|
|
117
|
+
|
|
118
|
+
// cherry-pick the group commits onto that base
|
|
119
|
+
const cp_commit_list = args.commit_list.map((c) => c.sha).join(" ");
|
|
120
|
+
await cli(`git -C ${worktree_path} cherry-pick ${cp_commit_list}`);
|
|
121
|
+
|
|
122
|
+
return { worktree_path, merge_base };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export async function diff_commits(commit_list: CommitList) {
|
|
126
|
+
const state = Store.getState();
|
|
127
|
+
const actions = state.actions;
|
|
128
|
+
const merge_base = state.merge_base;
|
|
129
|
+
invariant(merge_base, "merge_base must exist");
|
|
130
|
+
|
|
131
|
+
const cache_key = `${merge_base}:${commit_list.map((c) => c.sha).join(",")}`;
|
|
132
|
+
|
|
133
|
+
const cache = state.cache_diff[cache_key];
|
|
134
|
+
|
|
135
|
+
if (cache) {
|
|
136
|
+
if (actions.isDebug()) {
|
|
137
|
+
actions.debug(
|
|
138
|
+
cache_message({
|
|
139
|
+
hit: true,
|
|
140
|
+
message: "git diff_commits cache",
|
|
141
|
+
extra: cache_key,
|
|
142
|
+
}),
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return cache;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (actions.isDebug()) {
|
|
150
|
+
actions.debug(
|
|
151
|
+
cache_message({
|
|
152
|
+
hit: false,
|
|
153
|
+
message: "git diff_commits cache",
|
|
154
|
+
extra: cache_key,
|
|
155
|
+
}),
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const { worktree_path } = await worktree_add({ commit_list });
|
|
160
|
+
const cli_result = await cli(
|
|
161
|
+
`git -C ${worktree_path} --no-pager diff --color=never ${merge_base}`,
|
|
162
|
+
{ quiet: true },
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
if (cli_result.code !== 0) {
|
|
166
|
+
throw new Error(cli_result.output);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const diff = cli_result.stdout;
|
|
170
|
+
|
|
171
|
+
actions.set((state) => {
|
|
172
|
+
state.cache_diff[cache_key] = diff;
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
return diff;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Why these separators?
|
|
179
|
+
// - Rare in human written text
|
|
180
|
+
// - Supported in git %xNN to write bytes
|
|
181
|
+
// - Supported in javascript \xNN to write bytes
|
|
182
|
+
// - Used historically as separators in unicode
|
|
183
|
+
// https://en.wikipedia.org/wiki/C0_and_C1_control_codes#Field_separators
|
|
184
|
+
const SEP = {
|
|
185
|
+
record: "\x1e",
|
|
186
|
+
field: "\x1f",
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const FORMAT = `%H${SEP.field}%B${SEP.record}`;
|
|
190
|
+
|
|
191
|
+
const RE = {
|
|
192
|
+
git_sha: /^[0-9a-fA-F]{40}$/,
|
|
193
|
+
};
|
package/src/core/github.tsx
CHANGED
|
@@ -10,6 +10,7 @@ import { Brackets } from "~/app/Brackets";
|
|
|
10
10
|
import { FormatText } from "~/app/FormatText";
|
|
11
11
|
import { Store } from "~/app/Store";
|
|
12
12
|
import { Timer } from "~/core/Timer";
|
|
13
|
+
import { cache_message } from "~/core/cache_message";
|
|
13
14
|
import { cli } from "~/core/cli";
|
|
14
15
|
import { colors } from "~/core/colors";
|
|
15
16
|
import { get_tmp_dir } from "~/core/get_tmp_dir";
|
|
@@ -408,36 +409,6 @@ function safe_filename(value: string): string {
|
|
|
408
409
|
return value.replace(RE.non_alphanumeric_dash, "-");
|
|
409
410
|
}
|
|
410
411
|
|
|
411
|
-
type CacheMessageArgs = {
|
|
412
|
-
hit: boolean;
|
|
413
|
-
message: React.ReactNode;
|
|
414
|
-
extra: React.ReactNode;
|
|
415
|
-
};
|
|
416
|
-
|
|
417
|
-
function cache_message(args: CacheMessageArgs) {
|
|
418
|
-
const status = args.hit ? (
|
|
419
|
-
<Ink.Text bold color={colors.green}>
|
|
420
|
-
HIT
|
|
421
|
-
</Ink.Text>
|
|
422
|
-
) : (
|
|
423
|
-
<Ink.Text bold color={colors.red}>
|
|
424
|
-
MISS
|
|
425
|
-
</Ink.Text>
|
|
426
|
-
);
|
|
427
|
-
|
|
428
|
-
return (
|
|
429
|
-
<FormatText
|
|
430
|
-
wrapper={<Ink.Text dimColor />}
|
|
431
|
-
message="{message} {status} {extra}"
|
|
432
|
-
values={{
|
|
433
|
-
message: args.message,
|
|
434
|
-
status,
|
|
435
|
-
extra: args.extra,
|
|
436
|
-
}}
|
|
437
|
-
/>
|
|
438
|
-
);
|
|
439
|
-
}
|
|
440
|
-
|
|
441
412
|
type Commit = {
|
|
442
413
|
authoredDate: string; // "2023-10-22T23:13:35Z"
|
|
443
414
|
authors: [
|
package/src/core/git.ts
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { Store } from "~/app/Store";
|
|
2
|
-
import * as Metadata from "~/core/Metadata";
|
|
3
|
-
import { cli } from "~/core/cli";
|
|
4
|
-
import { invariant } from "~/core/invariant";
|
|
5
|
-
|
|
6
|
-
type CommitList = Awaited<ReturnType<typeof get_commits>>;
|
|
7
|
-
|
|
8
|
-
export type Commit = CommitList[0];
|
|
9
|
-
|
|
10
|
-
export async function get_commits(dot_range: string) {
|
|
11
|
-
const log_result = await cli(`git log ${dot_range} --format=${FORMAT} --color=never`);
|
|
12
|
-
|
|
13
|
-
if (!log_result.stdout) {
|
|
14
|
-
return [];
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const commit_list = [];
|
|
18
|
-
|
|
19
|
-
for (let record of log_result.stdout.split(SEP.record)) {
|
|
20
|
-
record = record.replace(/^\n/, "");
|
|
21
|
-
record = record.replace(/\n$/, "");
|
|
22
|
-
|
|
23
|
-
if (!record) continue;
|
|
24
|
-
|
|
25
|
-
const [sha, full_message] = record.split(SEP.field);
|
|
26
|
-
|
|
27
|
-
// ensure sha is a hex string, otherwise we should throw an error
|
|
28
|
-
if (!RE.git_sha.test(sha)) {
|
|
29
|
-
const actions = Store.getState().actions;
|
|
30
|
-
const sep_values = JSON.stringify(Object.values(SEP));
|
|
31
|
-
const message = `unable to parse git commits, maybe commit message contained ${sep_values}`;
|
|
32
|
-
actions.error(message);
|
|
33
|
-
actions.exit(19);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const metadata = Metadata.read(full_message);
|
|
37
|
-
const branch_id = metadata.id;
|
|
38
|
-
const subject_line = metadata.subject || "";
|
|
39
|
-
const title = metadata.title;
|
|
40
|
-
const master_base = metadata.base === "master";
|
|
41
|
-
|
|
42
|
-
const commit = {
|
|
43
|
-
sha,
|
|
44
|
-
full_message,
|
|
45
|
-
subject_line,
|
|
46
|
-
branch_id,
|
|
47
|
-
title,
|
|
48
|
-
master_base,
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
commit_list.push(commit);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
commit_list.reverse();
|
|
55
|
-
|
|
56
|
-
return commit_list;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export async function get_diff(commit_list: CommitList) {
|
|
60
|
-
invariant(commit_list.length, "commit_list must exist");
|
|
61
|
-
const first_commit = commit_list[0];
|
|
62
|
-
const last_commit = commit_list[commit_list.length - 1];
|
|
63
|
-
const sha_range = `${first_commit.sha}~1..${last_commit.sha}`;
|
|
64
|
-
const diff_result = await cli(`git --no-pager diff --color=never ${sha_range}`, { quiet: true });
|
|
65
|
-
return diff_result.stdout;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Why these separators?
|
|
69
|
-
// - Rare in human written text
|
|
70
|
-
// - Supported in git %xNN to write bytes
|
|
71
|
-
// - Supported in javascript \xNN to write bytes
|
|
72
|
-
// - Used historically as separators in unicode
|
|
73
|
-
// https://en.wikipedia.org/wiki/C0_and_C1_control_codes#Field_separators
|
|
74
|
-
const SEP = {
|
|
75
|
-
record: "\x1e",
|
|
76
|
-
field: "\x1f",
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
const FORMAT = `%H${SEP.field}%B${SEP.record}`;
|
|
80
|
-
|
|
81
|
-
const RE = {
|
|
82
|
-
git_sha: /^[0-9a-fA-F]{40}$/,
|
|
83
|
-
};
|