git-stack-cli 0.8.2 → 0.8.3
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/__fixtures__/metadata.json +186 -0
- package/dist/app/App copy.js +30 -0
- package/dist/app/ArgCheck.js +21 -0
- package/dist/app/Brackets copy.js +10 -0
- package/dist/app/Counter.js +19 -0
- package/dist/app/GatherMetadata copy.js +91 -0
- package/dist/app/GatherMetadata.js +7 -0
- package/dist/app/InitMetadata.js +14 -0
- package/dist/app/Input.js +15 -0
- package/dist/app/KeepAlive.js +11 -0
- package/dist/app/LocalMergeRebase.js +5 -1
- package/dist/app/Main copy.js +200 -0
- package/dist/app/ManualRebase copy.js +127 -0
- package/dist/app/ManualRebase.js +5 -1
- package/dist/app/MultiSelect copy.js +76 -0
- package/dist/app/NPMAutoUpdate.js +34 -0
- package/dist/app/Parens copy.js +9 -0
- package/dist/app/PostRebaseStatus copy.js +23 -0
- package/dist/app/PreSelectCommitRanges copy.js +21 -0
- package/dist/app/SelectCommitRange.js +1 -0
- package/dist/app/Status copy.js +46 -0
- package/dist/app/Url copy.js +6 -0
- package/dist/app/YesNoPrompt copy.js +24 -0
- package/dist/cli.js +9 -0
- package/dist/core/Metadata copy.js +37 -0
- package/dist/core/StackTable.js +38 -0
- package/dist/core/SummaryTable.js +38 -0
- package/dist/core/ZustandStore.js +23 -0
- package/dist/core/cli copy.js +44 -0
- package/dist/core/color.js +83 -0
- package/dist/core/dependency_check.js +27 -0
- package/dist/core/exit.js +4 -0
- package/dist/core/get_commit_metadata.js +61 -0
- package/dist/core/id.js +61 -0
- package/dist/core/invariant copy.js +5 -0
- package/dist/core/isFiniteValue.js +3 -0
- package/dist/core/is_dev.js +1 -0
- package/dist/core/readJson.js +3 -0
- package/dist/core/serialize_json.js +17 -0
- package/dist/core/sleep copy.js +3 -0
- package/dist/main copy.js +266 -0
- package/dist/main.backup.js +266 -0
- package/dist/main.js +265 -0
- package/package.json +6 -4
- /package/dist/app/{Main.js → main.js} +0 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { v4 as uuid_v4 } from "uuid";
|
|
2
|
+
import { cli } from "./core/cli.js";
|
|
3
|
+
import { color } from "./core/color.js";
|
|
4
|
+
import * as github from "./core/github.js";
|
|
5
|
+
import { invariant } from "./core/invariant.js";
|
|
6
|
+
export async function main(argv) {
|
|
7
|
+
const head_sha = (await cli("git rev-parse HEAD")).stdout;
|
|
8
|
+
const merge_base = (await cli("git merge-base HEAD master")).stdout;
|
|
9
|
+
// handle when there are no detected changes
|
|
10
|
+
if (head_sha === merge_base) {
|
|
11
|
+
console.error(color.dim("No changes detected."));
|
|
12
|
+
return process.exit();
|
|
13
|
+
}
|
|
14
|
+
const branch_name = (await cli("git rev-parse --abbrev-ref HEAD")).stdout;
|
|
15
|
+
// git@github.com:magus/git-multi-diff-playground.git
|
|
16
|
+
// https://github.com/magus/git-multi-diff-playground.git
|
|
17
|
+
const origin_url = (await cli(`git config --get remote.origin.url`)).stdout;
|
|
18
|
+
const repo_path = match_group(origin_url, RE.repo_path, "repo_path");
|
|
19
|
+
const commit_metadata_list = await get_commit_metadata_list();
|
|
20
|
+
print_table(repo_path, commit_metadata_list);
|
|
21
|
+
const needs_update = commit_metadata_list.some(commit_needs_update);
|
|
22
|
+
if (argv.check) {
|
|
23
|
+
return process.exit();
|
|
24
|
+
}
|
|
25
|
+
if (!argv.force && !needs_update) {
|
|
26
|
+
console.debug();
|
|
27
|
+
console.debug("Everything up to date.");
|
|
28
|
+
console.debug("Run with `--force` to force update all pull requests.");
|
|
29
|
+
return process.exit();
|
|
30
|
+
}
|
|
31
|
+
const temp_branch_name = `${branch_name}_${uuid_v4()}`;
|
|
32
|
+
try {
|
|
33
|
+
// create temporary branch based on merge base
|
|
34
|
+
await cli(`git checkout -b ${temp_branch_name} ${merge_base}`);
|
|
35
|
+
const picked_commit_metadata_list = [];
|
|
36
|
+
// cherry-pick and amend commits one by one
|
|
37
|
+
for (let i = 0; i < commit_metadata_list.length; i++) {
|
|
38
|
+
const sha = commit_metadata_list[i].sha;
|
|
39
|
+
let base;
|
|
40
|
+
if (i === 0) {
|
|
41
|
+
base = "master";
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
base = picked_commit_metadata_list[i - 1].metadata.id;
|
|
45
|
+
invariant(base, `metadata must be set on previous commit [${i}]`);
|
|
46
|
+
}
|
|
47
|
+
await cli(`git cherry-pick ${sha}`);
|
|
48
|
+
const args = await get_commit_metadata(sha, base);
|
|
49
|
+
if (!args.metadata.id) {
|
|
50
|
+
args.metadata.id = uuid_v4();
|
|
51
|
+
await write_metadata(args);
|
|
52
|
+
}
|
|
53
|
+
picked_commit_metadata_list.push(args);
|
|
54
|
+
// always push to origin since github requires commit shas to line up perfectly
|
|
55
|
+
console.debug();
|
|
56
|
+
console.debug(`Syncing [${args.metadata.id}] ...`);
|
|
57
|
+
await cli(`git push -f origin HEAD:${args.metadata.id}`);
|
|
58
|
+
if (args.pr_exists) {
|
|
59
|
+
// ensure base matches pr in github
|
|
60
|
+
await github.pr_base(args.metadata.id, base);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
try {
|
|
64
|
+
// delete metadata id branch if leftover
|
|
65
|
+
await cli(`git branch -D ${args.metadata.id}`, {
|
|
66
|
+
ignoreExitCode: true,
|
|
67
|
+
});
|
|
68
|
+
// move to temporary branch for creating pr
|
|
69
|
+
await cli(`git checkout -b ${args.metadata.id}`);
|
|
70
|
+
// create pr in github
|
|
71
|
+
await github.pr_create(args.metadata.id, base);
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
console.error("Moving back to temp branch...");
|
|
75
|
+
console.error(err);
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
// move back to temp branch
|
|
79
|
+
await cli(`git checkout ${temp_branch_name}`);
|
|
80
|
+
// delete metadata id branch if leftover
|
|
81
|
+
await cli(`git branch -D ${args.metadata.id}`, {
|
|
82
|
+
ignoreExitCode: true,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// after all commits have been cherry-picked and amended
|
|
88
|
+
// move the branch pointer to the temporary branch (with the metadata)
|
|
89
|
+
await cli(`git branch -f ${branch_name} ${temp_branch_name}`);
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
console.error("Restoring original branch...");
|
|
93
|
+
console.error(err);
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
// always put self back in original branch
|
|
97
|
+
await cli(`git checkout ${branch_name}`);
|
|
98
|
+
// ...and cleanup temporary branch
|
|
99
|
+
await cli(`git branch -D ${temp_branch_name}`, { ignoreExitCode: true });
|
|
100
|
+
}
|
|
101
|
+
print_table(repo_path, await get_commit_metadata_list());
|
|
102
|
+
}
|
|
103
|
+
function print_table(repo_path, commit_metadata_list) {
|
|
104
|
+
console.debug();
|
|
105
|
+
for (const args of commit_metadata_list) {
|
|
106
|
+
print_table_row(repo_path, args);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function print_table_row(repo_path, args) {
|
|
110
|
+
let icon;
|
|
111
|
+
let status;
|
|
112
|
+
if (!args.pr_exists) {
|
|
113
|
+
icon = "🌱";
|
|
114
|
+
status = "NEW";
|
|
115
|
+
}
|
|
116
|
+
else if (args.pr_dirty) {
|
|
117
|
+
icon = "⚠️";
|
|
118
|
+
status = "OUTDATED";
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
icon = "✅";
|
|
122
|
+
status = "SYNCED";
|
|
123
|
+
}
|
|
124
|
+
// print clean metadata about this commit / branch
|
|
125
|
+
const parts = [
|
|
126
|
+
icon,
|
|
127
|
+
" ",
|
|
128
|
+
col(status, 10, "left"),
|
|
129
|
+
col(args.message, 80, "left"),
|
|
130
|
+
];
|
|
131
|
+
if (args.pr?.number) {
|
|
132
|
+
parts.push(` https://github.com/${repo_path}/pull/${args.pr.number}`);
|
|
133
|
+
}
|
|
134
|
+
console.debug(...parts);
|
|
135
|
+
}
|
|
136
|
+
function commit_needs_update(meta) {
|
|
137
|
+
return !meta.pr_exists || meta.pr_dirty;
|
|
138
|
+
}
|
|
139
|
+
async function get_commit_metadata(sha, base) {
|
|
140
|
+
const raw_message = await commit_message(sha);
|
|
141
|
+
const metadata = await read_metadata(raw_message);
|
|
142
|
+
const message = display_message(raw_message);
|
|
143
|
+
let pr = null;
|
|
144
|
+
let pr_exists = false;
|
|
145
|
+
let pr_dirty = false;
|
|
146
|
+
if (metadata.id) {
|
|
147
|
+
const pr_branch = get_pr_branch(metadata);
|
|
148
|
+
pr = await github.pr_status(pr_branch);
|
|
149
|
+
if (pr && pr.state === "OPEN") {
|
|
150
|
+
pr_exists = true;
|
|
151
|
+
const last_commit = pr.commits[pr.commits.length - 1];
|
|
152
|
+
pr_dirty = last_commit.oid !== sha;
|
|
153
|
+
if (pr.baseRefName !== base) {
|
|
154
|
+
// requires base update
|
|
155
|
+
pr_dirty = true;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
sha,
|
|
161
|
+
base,
|
|
162
|
+
message,
|
|
163
|
+
pr,
|
|
164
|
+
pr_exists,
|
|
165
|
+
pr_dirty,
|
|
166
|
+
metadata,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
const TEMPLATE = {
|
|
170
|
+
metadata_id(id) {
|
|
171
|
+
return `git-multi-diff-id: ${id}`;
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
const RE = {
|
|
175
|
+
flag_check: /(--check|check|-c)/i,
|
|
176
|
+
flag_force: /(--force|force|-f)/i,
|
|
177
|
+
all_double_quote: /"/g,
|
|
178
|
+
all_newline: /\n/g,
|
|
179
|
+
metadata_id: new RegExp(TEMPLATE.metadata_id("(?<id>[a-z0-9-]+)")),
|
|
180
|
+
// git@github.com:magus/git-multi-diff-playground.git
|
|
181
|
+
// https://github.com/magus/git-multi-diff-playground.git
|
|
182
|
+
repo_path: /(?<repo_path>[^:^/]+\/[^/]+)\.git/,
|
|
183
|
+
};
|
|
184
|
+
async function write_metadata(args) {
|
|
185
|
+
invariant(args.metadata.id, "metadata must have id");
|
|
186
|
+
let message = args.message;
|
|
187
|
+
message = message.replace(RE.all_double_quote, '\\"');
|
|
188
|
+
const line_list = [message, "", TEMPLATE.metadata_id(args.metadata.id)];
|
|
189
|
+
const new_message = line_list.join("\n");
|
|
190
|
+
await cli(`git commit --amend -m "${new_message}"`);
|
|
191
|
+
}
|
|
192
|
+
async function read_metadata(message) {
|
|
193
|
+
const match = message.match(RE.metadata_id);
|
|
194
|
+
const metadata = {
|
|
195
|
+
id: null,
|
|
196
|
+
};
|
|
197
|
+
if (!match?.groups) {
|
|
198
|
+
return metadata;
|
|
199
|
+
}
|
|
200
|
+
const id = match.groups["id"];
|
|
201
|
+
invariant(id, "id must exist");
|
|
202
|
+
metadata.id = id;
|
|
203
|
+
return metadata;
|
|
204
|
+
}
|
|
205
|
+
function get_pr_branch(metadata) {
|
|
206
|
+
return `${metadata.id}`;
|
|
207
|
+
}
|
|
208
|
+
async function commit_message(sha) {
|
|
209
|
+
return (await cli(`git show -s --format=%B ${sha}`)).stdout;
|
|
210
|
+
}
|
|
211
|
+
function display_message(message) {
|
|
212
|
+
// remove metadata
|
|
213
|
+
let result = message;
|
|
214
|
+
result = result.replace(RE.metadata_id, "");
|
|
215
|
+
result = result.trimEnd();
|
|
216
|
+
return result;
|
|
217
|
+
}
|
|
218
|
+
function lines(value) {
|
|
219
|
+
return value.split("\n");
|
|
220
|
+
}
|
|
221
|
+
function trunc(value, length) {
|
|
222
|
+
return value.substring(0, length);
|
|
223
|
+
}
|
|
224
|
+
function pad(value, length, align) {
|
|
225
|
+
const space_count = Math.max(0, length - value.length);
|
|
226
|
+
const padding = " ".repeat(space_count);
|
|
227
|
+
if (align === "left") {
|
|
228
|
+
return `${value}${padding}`;
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
return `${padding}${value}`;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
function col(value, length, align) {
|
|
235
|
+
let column = value;
|
|
236
|
+
column = column.replace(RE.all_newline, " ");
|
|
237
|
+
column = trunc(column, length);
|
|
238
|
+
column = pad(column, length, align);
|
|
239
|
+
return column;
|
|
240
|
+
}
|
|
241
|
+
function match_group(value, re, group) {
|
|
242
|
+
const match = value.match(re);
|
|
243
|
+
const debug = `[${value}.match(${re})]`;
|
|
244
|
+
invariant(match?.groups, `match.groups must exist ${debug}`);
|
|
245
|
+
const result = match?.groups[group];
|
|
246
|
+
invariant(result, `match.groups must contain [${group}] ${debug}`);
|
|
247
|
+
return result;
|
|
248
|
+
}
|
|
249
|
+
async function get_commit_metadata_list() {
|
|
250
|
+
const log_result = await cli(`git log master..HEAD --oneline --format=%H --color=never`);
|
|
251
|
+
const sha_list = lines(log_result.stdout).reverse();
|
|
252
|
+
const commit_metadata_list = [];
|
|
253
|
+
for (let i = 0; i < sha_list.length; i++) {
|
|
254
|
+
const sha = sha_list[i];
|
|
255
|
+
let base;
|
|
256
|
+
if (i === 0) {
|
|
257
|
+
base = "master";
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
base = commit_metadata_list[i - 1].metadata.id;
|
|
261
|
+
}
|
|
262
|
+
const commit_metadata = await get_commit_metadata(sha, base);
|
|
263
|
+
commit_metadata_list.push(commit_metadata);
|
|
264
|
+
}
|
|
265
|
+
return commit_metadata_list;
|
|
266
|
+
}
|
package/dist/main.js
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { v4 as uuid_v4 } from "uuid";
|
|
2
|
+
import { Store } from "./app/Store.js";
|
|
3
|
+
import { cli } from "./core/cli.js";
|
|
4
|
+
import { color } from "./core/color.js";
|
|
5
|
+
import * as github from "./core/github.js";
|
|
6
|
+
import { invariant } from "./core/invariant.js";
|
|
7
|
+
import { match_group } from "./core/match_group.js";
|
|
8
|
+
export async function main(argv) {
|
|
9
|
+
const head = (await cli("git rev-parse HEAD")).stdout;
|
|
10
|
+
const merge_base = (await cli("git merge-base HEAD master")).stdout;
|
|
11
|
+
// handle when there are no detected changes
|
|
12
|
+
if (head === merge_base) {
|
|
13
|
+
console.error(color.dim("No changes detected."));
|
|
14
|
+
return process.exit();
|
|
15
|
+
}
|
|
16
|
+
const branch_name = (await cli("git rev-parse --abbrev-ref HEAD")).stdout;
|
|
17
|
+
// git@github.com:magus/git-multi-diff-playground.git
|
|
18
|
+
// https://github.com/magus/git-multi-diff-playground.git
|
|
19
|
+
const origin_url = (await cli(`git config --get remote.origin.url`)).stdout;
|
|
20
|
+
const repo_path = match_group(origin_url, RE.repo_path, "repo_path");
|
|
21
|
+
Store.setState((state) => {
|
|
22
|
+
state.head = head;
|
|
23
|
+
state.merge_base = merge_base;
|
|
24
|
+
state.branch_name = branch_name;
|
|
25
|
+
});
|
|
26
|
+
const commit_metadata_list = await get_commit_metadata_list();
|
|
27
|
+
print_table(repo_path, commit_metadata_list);
|
|
28
|
+
const needs_update = commit_metadata_list.some(commit_needs_update);
|
|
29
|
+
if (argv.check) {
|
|
30
|
+
return process.exit();
|
|
31
|
+
}
|
|
32
|
+
if (!argv.force && !needs_update) {
|
|
33
|
+
console.debug();
|
|
34
|
+
console.debug("Everything up to date.");
|
|
35
|
+
console.debug("Run with `--force` to force update all pull requests.");
|
|
36
|
+
return process.exit();
|
|
37
|
+
}
|
|
38
|
+
const temp_branch_name = `${branch_name}_${uuid_v4()}`;
|
|
39
|
+
try {
|
|
40
|
+
// create temporary branch based on merge base
|
|
41
|
+
await cli(`git checkout -b ${temp_branch_name} ${merge_base}`);
|
|
42
|
+
const picked_commit_metadata_list = [];
|
|
43
|
+
// cherry-pick and amend commits one by one
|
|
44
|
+
for (let i = 0; i < commit_metadata_list.length; i++) {
|
|
45
|
+
const sha = commit_metadata_list[i].sha;
|
|
46
|
+
let base;
|
|
47
|
+
if (i === 0) {
|
|
48
|
+
base = "master";
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
base = picked_commit_metadata_list[i - 1].metadata.id;
|
|
52
|
+
invariant(base, `metadata must be set on previous commit [${i}]`);
|
|
53
|
+
}
|
|
54
|
+
await cli(`git cherry-pick ${sha}`);
|
|
55
|
+
const args = await get_commit_metadata(sha, base);
|
|
56
|
+
if (!args.metadata.id) {
|
|
57
|
+
args.metadata.id = uuid_v4();
|
|
58
|
+
await write_metadata(args);
|
|
59
|
+
}
|
|
60
|
+
picked_commit_metadata_list.push(args);
|
|
61
|
+
// always push to origin since github requires commit shas to line up perfectly
|
|
62
|
+
console.debug();
|
|
63
|
+
console.debug(`Syncing [${args.metadata.id}] ...`);
|
|
64
|
+
await cli(`git push -f origin HEAD:${args.metadata.id}`);
|
|
65
|
+
if (args.pr_exists) {
|
|
66
|
+
// ensure base matches pr in github
|
|
67
|
+
await github.pr_base(args.metadata.id, base);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
try {
|
|
71
|
+
// delete metadata id branch if leftover
|
|
72
|
+
await cli(`git branch -D ${args.metadata.id}`, {
|
|
73
|
+
ignoreExitCode: true,
|
|
74
|
+
});
|
|
75
|
+
// move to temporary branch for creating pr
|
|
76
|
+
await cli(`git checkout -b ${args.metadata.id}`);
|
|
77
|
+
// create pr in github
|
|
78
|
+
await github.pr_create(args.metadata.id, base);
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
console.error("Moving back to temp branch...");
|
|
82
|
+
console.error(err);
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
// move back to temp branch
|
|
86
|
+
await cli(`git checkout ${temp_branch_name}`);
|
|
87
|
+
// delete metadata id branch if leftover
|
|
88
|
+
await cli(`git branch -D ${args.metadata.id}`, {
|
|
89
|
+
ignoreExitCode: true,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// after all commits have been cherry-picked and amended
|
|
95
|
+
// move the branch pointer to the temporary branch (with the metadata)
|
|
96
|
+
await cli(`git branch -f ${branch_name} ${temp_branch_name}`);
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
console.error("Restoring original branch...");
|
|
100
|
+
console.error(err);
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
// always put self back in original branch
|
|
104
|
+
await cli(`git checkout ${branch_name}`);
|
|
105
|
+
// ...and cleanup temporary branch
|
|
106
|
+
await cli(`git branch -D ${temp_branch_name}`, { ignoreExitCode: true });
|
|
107
|
+
}
|
|
108
|
+
print_table(repo_path, await get_commit_metadata_list());
|
|
109
|
+
}
|
|
110
|
+
function print_table(repo_path, commit_metadata_list) {
|
|
111
|
+
console.debug();
|
|
112
|
+
for (const args of commit_metadata_list) {
|
|
113
|
+
print_table_row(repo_path, args);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function print_table_row(repo_path, args) {
|
|
117
|
+
let icon;
|
|
118
|
+
let status;
|
|
119
|
+
if (!args.pr_exists) {
|
|
120
|
+
icon = "🌱";
|
|
121
|
+
status = "NEW";
|
|
122
|
+
}
|
|
123
|
+
else if (args.pr_dirty) {
|
|
124
|
+
icon = "⚠️";
|
|
125
|
+
status = "OUTDATED";
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
icon = "✅";
|
|
129
|
+
status = "SYNCED";
|
|
130
|
+
}
|
|
131
|
+
// print clean metadata about this commit / branch
|
|
132
|
+
const parts = [
|
|
133
|
+
icon,
|
|
134
|
+
" ",
|
|
135
|
+
col(status, 10, "left"),
|
|
136
|
+
col(args.message, 80, "left"),
|
|
137
|
+
];
|
|
138
|
+
if (args.pr?.number) {
|
|
139
|
+
parts.push(` https://github.com/${repo_path}/pull/${args.pr.number}`);
|
|
140
|
+
}
|
|
141
|
+
console.debug(...parts);
|
|
142
|
+
}
|
|
143
|
+
function commit_needs_update(meta) {
|
|
144
|
+
return !meta.pr_exists || meta.pr_dirty;
|
|
145
|
+
}
|
|
146
|
+
async function get_commit_metadata(sha, base) {
|
|
147
|
+
const raw_message = await commit_message(sha);
|
|
148
|
+
const metadata = await read_metadata(raw_message);
|
|
149
|
+
const message = display_message(raw_message);
|
|
150
|
+
let pr = null;
|
|
151
|
+
let pr_exists = false;
|
|
152
|
+
let pr_dirty = false;
|
|
153
|
+
if (metadata.id) {
|
|
154
|
+
const pr_branch = get_pr_branch(metadata);
|
|
155
|
+
pr = await github.pr_status(pr_branch);
|
|
156
|
+
if (pr && pr.state === "OPEN") {
|
|
157
|
+
pr_exists = true;
|
|
158
|
+
const last_commit = pr.commits[pr.commits.length - 1];
|
|
159
|
+
pr_dirty = last_commit.oid !== sha;
|
|
160
|
+
if (pr.baseRefName !== base) {
|
|
161
|
+
// requires base update
|
|
162
|
+
pr_dirty = true;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
sha,
|
|
168
|
+
base,
|
|
169
|
+
message,
|
|
170
|
+
pr,
|
|
171
|
+
pr_exists,
|
|
172
|
+
pr_dirty,
|
|
173
|
+
metadata,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
const TEMPLATE = {
|
|
177
|
+
metadata_id(id) {
|
|
178
|
+
return `git-multi-diff-id: ${id}`;
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
const RE = {
|
|
182
|
+
flag_check: /(--check|check|-c)/i,
|
|
183
|
+
flag_force: /(--force|force|-f)/i,
|
|
184
|
+
all_double_quote: /"/g,
|
|
185
|
+
all_newline: /\n/g,
|
|
186
|
+
metadata_id: new RegExp(TEMPLATE.metadata_id("(?<id>[a-z0-9-]+)")),
|
|
187
|
+
// git@github.com:magus/git-multi-diff-playground.git
|
|
188
|
+
// https://github.com/magus/git-multi-diff-playground.git
|
|
189
|
+
repo_path: /(?<repo_path>[^:^/]+\/[^/]+)\.git/,
|
|
190
|
+
};
|
|
191
|
+
async function write_metadata(args) {
|
|
192
|
+
invariant(args.metadata.id, "metadata must have id");
|
|
193
|
+
let message = args.message;
|
|
194
|
+
message = message.replace(RE.all_double_quote, '\\"');
|
|
195
|
+
const line_list = [message, "", TEMPLATE.metadata_id(args.metadata.id)];
|
|
196
|
+
const new_message = line_list.join("\n");
|
|
197
|
+
await cli(`git commit --amend -m "${new_message}"`);
|
|
198
|
+
}
|
|
199
|
+
async function read_metadata(message) {
|
|
200
|
+
const match = message.match(RE.metadata_id);
|
|
201
|
+
const metadata = {
|
|
202
|
+
id: null,
|
|
203
|
+
};
|
|
204
|
+
if (!match?.groups) {
|
|
205
|
+
return metadata;
|
|
206
|
+
}
|
|
207
|
+
const id = match.groups["id"];
|
|
208
|
+
invariant(id, "id must exist");
|
|
209
|
+
metadata.id = id;
|
|
210
|
+
return metadata;
|
|
211
|
+
}
|
|
212
|
+
function get_pr_branch(metadata) {
|
|
213
|
+
return `${metadata.id}`;
|
|
214
|
+
}
|
|
215
|
+
async function commit_message(sha) {
|
|
216
|
+
return (await cli(`git show -s --format=%B ${sha}`)).stdout;
|
|
217
|
+
}
|
|
218
|
+
function display_message(message) {
|
|
219
|
+
// remove metadata
|
|
220
|
+
let result = message;
|
|
221
|
+
result = result.replace(RE.metadata_id, "");
|
|
222
|
+
result = result.trimEnd();
|
|
223
|
+
return result;
|
|
224
|
+
}
|
|
225
|
+
function lines(value) {
|
|
226
|
+
return value.split("\n");
|
|
227
|
+
}
|
|
228
|
+
function trunc(value, length) {
|
|
229
|
+
return value.substring(0, length);
|
|
230
|
+
}
|
|
231
|
+
function pad(value, length, align) {
|
|
232
|
+
const space_count = Math.max(0, length - value.length);
|
|
233
|
+
const padding = " ".repeat(space_count);
|
|
234
|
+
if (align === "left") {
|
|
235
|
+
return `${value}${padding}`;
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
return `${padding}${value}`;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
function col(value, length, align) {
|
|
242
|
+
let column = value;
|
|
243
|
+
column = column.replace(RE.all_newline, " ");
|
|
244
|
+
column = trunc(column, length);
|
|
245
|
+
column = pad(column, length, align);
|
|
246
|
+
return column;
|
|
247
|
+
}
|
|
248
|
+
async function get_commit_metadata_list() {
|
|
249
|
+
const log_result = await cli(`git log master..HEAD --oneline --format=%H --color=never`);
|
|
250
|
+
const sha_list = lines(log_result.stdout).reverse();
|
|
251
|
+
const commit_metadata_list = [];
|
|
252
|
+
for (let i = 0; i < sha_list.length; i++) {
|
|
253
|
+
const sha = sha_list[i];
|
|
254
|
+
let base;
|
|
255
|
+
if (i === 0) {
|
|
256
|
+
base = "master";
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
base = commit_metadata_list[i - 1].metadata.id;
|
|
260
|
+
}
|
|
261
|
+
const commit_metadata = await get_commit_metadata(sha, base);
|
|
262
|
+
commit_metadata_list.push(commit_metadata);
|
|
263
|
+
}
|
|
264
|
+
return commit_metadata_list;
|
|
265
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "git-stack-cli",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.3",
|
|
4
4
|
"description": "",
|
|
5
5
|
"author": "magus",
|
|
6
6
|
"license": "MIT",
|
|
@@ -19,9 +19,11 @@
|
|
|
19
19
|
"scripts": {
|
|
20
20
|
"build": "tsc",
|
|
21
21
|
"dev": "tsc --watch",
|
|
22
|
-
"lint": "eslint .
|
|
23
|
-
"
|
|
24
|
-
"
|
|
22
|
+
"lint:check": "eslint .",
|
|
23
|
+
"lint": "npm run lint:check -- --fix",
|
|
24
|
+
"prettier:check": "prettier ./src --check",
|
|
25
|
+
"prettier": "npm run prettier:check -- --write",
|
|
26
|
+
"test": "npm run prettier:check && npm run lint:check && npm run build",
|
|
25
27
|
"prepublishOnly": "npm test"
|
|
26
28
|
},
|
|
27
29
|
"dependencies": {
|
|
File without changes
|