git-stack-cli 1.9.0 → 1.11.0
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/README.md +17 -17
- package/dist/cjs/index.cjs +3235 -487
- package/package.json +3 -1
- package/src/app/App.tsx +13 -1
- package/src/app/CherryPickCheck.tsx +3 -3
- package/src/app/DetectInitialPR.tsx +189 -0
- package/src/app/DirtyCheck.tsx +3 -3
- package/src/app/FormatText.tsx +1 -1
- package/src/app/GatherMetadata.tsx +2 -2
- package/src/app/LocalCommitStatus.tsx +2 -3
- package/src/app/LocalMergeRebase.tsx +2 -188
- package/src/app/ManualRebase.tsx +25 -16
- package/src/app/RebaseCheck.tsx +3 -3
- package/src/app/SelectCommitRanges.tsx +2 -2
- package/src/command.ts +6 -0
- package/src/commands/Rebase.tsx +204 -0
- package/src/core/CommitMetadata.ts +1 -24
- package/src/core/GitReviseTodo.test.ts +29 -29
- package/src/core/GitReviseTodo.ts +75 -17
- package/src/core/gs_short_id.ts +5 -0
package/src/command.ts
CHANGED
|
@@ -27,6 +27,12 @@ export async function command() {
|
|
|
27
27
|
(yargs) => yargs.strict(false)
|
|
28
28
|
)
|
|
29
29
|
|
|
30
|
+
.command(
|
|
31
|
+
"rebase",
|
|
32
|
+
"Update local branch via rebase with latest changes from origin master branch",
|
|
33
|
+
(yargs) => yargs
|
|
34
|
+
)
|
|
35
|
+
|
|
30
36
|
.option("verbose", GlobalOptions.verbose)
|
|
31
37
|
|
|
32
38
|
// yargs default wraps to 80 columns
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
|
|
5
|
+
import * as Ink from "ink-cjs";
|
|
6
|
+
|
|
7
|
+
import { Await } from "~/app/Await";
|
|
8
|
+
import { Brackets } from "~/app/Brackets";
|
|
9
|
+
import { FormatText } from "~/app/FormatText";
|
|
10
|
+
import { Parens } from "~/app/Parens";
|
|
11
|
+
import { Store } from "~/app/Store";
|
|
12
|
+
import * as CommitMetadata from "~/core/CommitMetadata";
|
|
13
|
+
import { cli } from "~/core/cli";
|
|
14
|
+
import { colors } from "~/core/colors";
|
|
15
|
+
import { invariant } from "~/core/invariant";
|
|
16
|
+
import { short_id } from "~/core/short_id";
|
|
17
|
+
|
|
18
|
+
export function Rebase() {
|
|
19
|
+
return (
|
|
20
|
+
<Await
|
|
21
|
+
function={Rebase.run}
|
|
22
|
+
fallback={<Ink.Text color={colors.yellow}>Rebasing commits…</Ink.Text>}
|
|
23
|
+
/>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
Rebase.run = async function run() {
|
|
28
|
+
const state = Store.getState();
|
|
29
|
+
const actions = state.actions;
|
|
30
|
+
const branch_name = state.branch_name;
|
|
31
|
+
const commit_range = state.commit_range;
|
|
32
|
+
const master_branch = state.master_branch;
|
|
33
|
+
const cwd = state.cwd;
|
|
34
|
+
const repo_root = state.repo_root;
|
|
35
|
+
|
|
36
|
+
invariant(branch_name, "branch_name must exist");
|
|
37
|
+
invariant(commit_range, "commit_range must exist");
|
|
38
|
+
invariant(repo_root, "repo_root must exist");
|
|
39
|
+
|
|
40
|
+
// always listen for SIGINT event and restore git state
|
|
41
|
+
process.once("SIGINT", handle_exit);
|
|
42
|
+
|
|
43
|
+
const temp_branch_name = `${branch_name}_${short_id()}`;
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
// actions.debug(`commit_range=${JSON.stringify(commit_range, null, 2)}`);
|
|
47
|
+
|
|
48
|
+
// must perform rebase from repo root for applying git patch
|
|
49
|
+
process.chdir(repo_root);
|
|
50
|
+
await cli(`pwd`);
|
|
51
|
+
|
|
52
|
+
// update local master to match remote
|
|
53
|
+
await cli(
|
|
54
|
+
`git fetch --no-tags -v origin ${master_branch}:${master_branch}`
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const master_sha = (await cli(`git rev-parse ${master_branch}`)).stdout;
|
|
58
|
+
const rebase_merge_base = master_sha;
|
|
59
|
+
|
|
60
|
+
// create temporary branch based on merge base
|
|
61
|
+
await cli(`git checkout -b ${temp_branch_name} ${rebase_merge_base}`);
|
|
62
|
+
|
|
63
|
+
const picked_commit_list = [];
|
|
64
|
+
|
|
65
|
+
for (let i = 0; i < commit_range.commit_list.length; i++) {
|
|
66
|
+
const commit = commit_range.commit_list[i];
|
|
67
|
+
const commit_pr = commit_range.pr_lookup[commit.branch_id || ""];
|
|
68
|
+
|
|
69
|
+
// drop commits that are in groups of merged PRs
|
|
70
|
+
const merged_pr = commit_pr?.state === "MERGED";
|
|
71
|
+
|
|
72
|
+
if (merged_pr) {
|
|
73
|
+
if (actions.isDebug()) {
|
|
74
|
+
actions.output(
|
|
75
|
+
<FormatText
|
|
76
|
+
wrapper={<Ink.Text color={colors.yellow} wrap="truncate-end" />}
|
|
77
|
+
message="Dropping {commit_message} {pr_status}"
|
|
78
|
+
values={{
|
|
79
|
+
commit_message: <Brackets>{commit.subject_line}</Brackets>,
|
|
80
|
+
pr_status: <Parens>MERGED</Parens>,
|
|
81
|
+
}}
|
|
82
|
+
/>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (actions.isDebug()) {
|
|
90
|
+
actions.output(
|
|
91
|
+
<FormatText
|
|
92
|
+
wrapper={<Ink.Text color={colors.yellow} wrap="truncate-end" />}
|
|
93
|
+
message="Picking {commit_message}"
|
|
94
|
+
values={{
|
|
95
|
+
commit_message: <Brackets>{commit.subject_line}</Brackets>,
|
|
96
|
+
}}
|
|
97
|
+
/>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
picked_commit_list.push(commit);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (picked_commit_list.length > 0) {
|
|
105
|
+
// ensure clean base to avoid conflicts when applying patch
|
|
106
|
+
await cli(`git clean -fd`);
|
|
107
|
+
|
|
108
|
+
// create list of sha for cherry-pick
|
|
109
|
+
const sha_list = picked_commit_list.map((commit) => commit.sha).join(" ");
|
|
110
|
+
|
|
111
|
+
await cli(`git cherry-pick --keep-redundant-commits ${sha_list}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// after all commits have been cherry-picked and amended
|
|
115
|
+
// move the branch pointer to the newly created temporary branch
|
|
116
|
+
// now we are locally in sync with github and on the original branch
|
|
117
|
+
await cli(`git branch -f ${branch_name} ${temp_branch_name}`);
|
|
118
|
+
|
|
119
|
+
restore_git();
|
|
120
|
+
|
|
121
|
+
const next_commit_range = await CommitMetadata.range();
|
|
122
|
+
|
|
123
|
+
actions.output(
|
|
124
|
+
<FormatText
|
|
125
|
+
wrapper={<Ink.Text color={colors.green} />}
|
|
126
|
+
message="✅ {branch_name} in sync with {origin_branch}"
|
|
127
|
+
values={{
|
|
128
|
+
branch_name: <Brackets>{branch_name}</Brackets>,
|
|
129
|
+
origin_branch: <Brackets>{`origin/${master_branch}`}</Brackets>,
|
|
130
|
+
}}
|
|
131
|
+
/>
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
actions.set((state) => {
|
|
135
|
+
state.commit_range = next_commit_range;
|
|
136
|
+
state.step = "status";
|
|
137
|
+
});
|
|
138
|
+
} catch (err) {
|
|
139
|
+
actions.error("Unable to rebase.");
|
|
140
|
+
|
|
141
|
+
if (err instanceof Error) {
|
|
142
|
+
if (actions.isDebug()) {
|
|
143
|
+
actions.error(err.message);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
handle_exit();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// cleanup git operations if cancelled during manual rebase
|
|
151
|
+
function restore_git() {
|
|
152
|
+
// signint handler MUST run synchronously
|
|
153
|
+
// trying to use `await cli(...)` here will silently fail since
|
|
154
|
+
// all children processes receive the SIGINT signal
|
|
155
|
+
const spawn_options = { ignoreExitCode: true };
|
|
156
|
+
|
|
157
|
+
// always clean up any patch files
|
|
158
|
+
cli.sync(`rm ${PATCH_FILE}`, spawn_options);
|
|
159
|
+
|
|
160
|
+
// always hard reset and clean to allow subsequent checkout
|
|
161
|
+
// if there are files checkout will fail and cascade fail subsequent commands
|
|
162
|
+
cli.sync(`git reset --hard`, spawn_options);
|
|
163
|
+
cli.sync(`git clean -df`, spawn_options);
|
|
164
|
+
|
|
165
|
+
// always put self back in original branch
|
|
166
|
+
cli.sync(`git checkout ${branch_name}`, spawn_options);
|
|
167
|
+
|
|
168
|
+
// ...and cleanup temporary branch
|
|
169
|
+
cli.sync(`git branch -D ${temp_branch_name}`, spawn_options);
|
|
170
|
+
|
|
171
|
+
if (commit_range) {
|
|
172
|
+
// ...and cleanup pr group branches
|
|
173
|
+
for (const group of commit_range.group_list) {
|
|
174
|
+
cli.sync(`git branch -D ${group.id}`, spawn_options);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// restore back to original dir
|
|
179
|
+
if (fs.existsSync(cwd)) {
|
|
180
|
+
process.chdir(cwd);
|
|
181
|
+
}
|
|
182
|
+
cli.sync(`pwd`, spawn_options);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function handle_exit() {
|
|
186
|
+
actions.output(
|
|
187
|
+
<Ink.Text color={colors.yellow}>
|
|
188
|
+
Restoring <Brackets>{branch_name}</Brackets>…
|
|
189
|
+
</Ink.Text>
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
restore_git();
|
|
193
|
+
|
|
194
|
+
actions.output(
|
|
195
|
+
<Ink.Text color={colors.yellow}>
|
|
196
|
+
Restored <Brackets>{branch_name}</Brackets>.
|
|
197
|
+
</Ink.Text>
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
actions.exit(6);
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const PATCH_FILE = "git-stack-cli-patch.patch";
|
|
@@ -2,7 +2,6 @@ import { Store } from "~/app/Store";
|
|
|
2
2
|
import * as Metadata from "~/core/Metadata";
|
|
3
3
|
import { cli } from "~/core/cli";
|
|
4
4
|
import * as github from "~/core/github";
|
|
5
|
-
import { invariant } from "~/core/invariant";
|
|
6
5
|
|
|
7
6
|
export type CommitMetadata = Awaited<ReturnType<typeof commit>>;
|
|
8
7
|
export type CommitRange = Awaited<ReturnType<typeof range>>;
|
|
@@ -18,7 +17,7 @@ type CommitGroup = {
|
|
|
18
17
|
commits: Array<CommitMetadata>;
|
|
19
18
|
};
|
|
20
19
|
|
|
21
|
-
type SimpleGroup = { id: string; title: string };
|
|
20
|
+
export type SimpleGroup = { id: string; title: string };
|
|
22
21
|
type CommitGroupMap = { [sha: string]: SimpleGroup };
|
|
23
22
|
|
|
24
23
|
export async function range(commit_group_map?: CommitGroupMap) {
|
|
@@ -167,10 +166,6 @@ export async function range(commit_group_map?: CommitGroupMap) {
|
|
|
167
166
|
|
|
168
167
|
async function get_commit_list() {
|
|
169
168
|
const master_branch = Store.getState().master_branch;
|
|
170
|
-
const branch_name = Store.getState().branch_name;
|
|
171
|
-
|
|
172
|
-
invariant(branch_name, "branch_name must exist");
|
|
173
|
-
|
|
174
169
|
const log_result = await cli(
|
|
175
170
|
`git log ${master_branch}..HEAD --oneline --format=%H --color=never`
|
|
176
171
|
);
|
|
@@ -183,30 +178,12 @@ async function get_commit_list() {
|
|
|
183
178
|
|
|
184
179
|
const commit_metadata_list = [];
|
|
185
180
|
|
|
186
|
-
let has_metadata = false;
|
|
187
|
-
|
|
188
181
|
for (let i = 0; i < sha_list.length; i++) {
|
|
189
182
|
const sha = sha_list[i];
|
|
190
183
|
const commit_metadata = await commit(sha);
|
|
191
|
-
|
|
192
|
-
if (commit_metadata.branch_id) {
|
|
193
|
-
has_metadata = true;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
184
|
commit_metadata_list.push(commit_metadata);
|
|
197
185
|
}
|
|
198
186
|
|
|
199
|
-
if (!has_metadata) {
|
|
200
|
-
// check for pr with matching branch name to initialize group
|
|
201
|
-
const pr_result = await github.pr_status(branch_name);
|
|
202
|
-
if (pr_result) {
|
|
203
|
-
for (const commit_metadata of commit_metadata_list) {
|
|
204
|
-
commit_metadata.branch_id = branch_name;
|
|
205
|
-
commit_metadata.title = pr_result.title;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
187
|
return commit_metadata_list;
|
|
211
188
|
}
|
|
212
189
|
|
|
@@ -67,7 +67,7 @@ test("git-revise-todo handles double quotes in commit message", () => {
|
|
|
67
67
|
[
|
|
68
68
|
//force line break
|
|
69
69
|
"++ pick f143d03c723c",
|
|
70
|
-
'[new] invalid
|
|
70
|
+
'[new] invalid \\"by me\\" quotes',
|
|
71
71
|
"",
|
|
72
72
|
"git-stack-id: 6Ak-qn+5Z",
|
|
73
73
|
'git-stack-title: [new] invalid \\"by me\\" quotes',
|
|
@@ -115,7 +115,7 @@ const SINGLE_COMMIT_EXISTING_GROUP: CommitMetadata.CommitRange = {
|
|
|
115
115
|
full_message: "banana color\n\ngit-stack-id: AAWsYx1UU",
|
|
116
116
|
subject_line: "banana color",
|
|
117
117
|
branch_id: "AAWsYx1UU",
|
|
118
|
-
title:
|
|
118
|
+
title: "banana",
|
|
119
119
|
},
|
|
120
120
|
],
|
|
121
121
|
},
|
|
@@ -186,28 +186,28 @@ const SINGLE_COMMIT_EXISTING_GROUP: CommitMetadata.CommitRange = {
|
|
|
186
186
|
full_message: "lemon color\n\ngit-stack-id: E63ytp5dj",
|
|
187
187
|
subject_line: "lemon color",
|
|
188
188
|
branch_id: "E63ytp5dj",
|
|
189
|
-
title:
|
|
189
|
+
title: "lemon color",
|
|
190
190
|
},
|
|
191
191
|
{
|
|
192
192
|
sha: "d36d63499425bb46a1e62c2c9df1a4332b13004f",
|
|
193
193
|
full_message: "cantaloupe color\n\ngit-stack-id: E63ytp5dj",
|
|
194
194
|
subject_line: "cantaloupe color",
|
|
195
195
|
branch_id: "E63ytp5dj",
|
|
196
|
-
title:
|
|
196
|
+
title: "lemon color",
|
|
197
197
|
},
|
|
198
198
|
{
|
|
199
199
|
sha: "4f98dd3e67d03b79d7a12480c7d1c2fcbd186ac5",
|
|
200
200
|
full_message: "banana sweet\n\ngit-stack-id: E63ytp5dj",
|
|
201
201
|
subject_line: "banana sweet",
|
|
202
202
|
branch_id: "E63ytp5dj",
|
|
203
|
-
title:
|
|
203
|
+
title: "lemon color",
|
|
204
204
|
},
|
|
205
205
|
{
|
|
206
206
|
sha: "f143d03c723c9f5231a81c1e12098511611898cb",
|
|
207
207
|
full_message: "apple sweet",
|
|
208
208
|
subject_line: "apple sweet",
|
|
209
|
-
branch_id:
|
|
210
|
-
title:
|
|
209
|
+
branch_id: "E63ytp5dj",
|
|
210
|
+
title: "lemon color",
|
|
211
211
|
},
|
|
212
212
|
],
|
|
213
213
|
},
|
|
@@ -218,35 +218,35 @@ const SINGLE_COMMIT_EXISTING_GROUP: CommitMetadata.CommitRange = {
|
|
|
218
218
|
full_message: "banana color\n\ngit-stack-id: AAWsYx1UU",
|
|
219
219
|
subject_line: "banana color",
|
|
220
220
|
branch_id: "AAWsYx1UU",
|
|
221
|
-
title:
|
|
221
|
+
title: "banana",
|
|
222
222
|
},
|
|
223
223
|
{
|
|
224
224
|
sha: "3cb22661ecff6c872e96ce9c40b31c824938cab7",
|
|
225
225
|
full_message: "lemon color\n\ngit-stack-id: E63ytp5dj",
|
|
226
226
|
subject_line: "lemon color",
|
|
227
227
|
branch_id: "E63ytp5dj",
|
|
228
|
-
title:
|
|
228
|
+
title: "lemon color",
|
|
229
229
|
},
|
|
230
230
|
{
|
|
231
231
|
sha: "d36d63499425bb46a1e62c2c9df1a4332b13004f",
|
|
232
232
|
full_message: "cantaloupe color\n\ngit-stack-id: E63ytp5dj",
|
|
233
233
|
subject_line: "cantaloupe color",
|
|
234
234
|
branch_id: "E63ytp5dj",
|
|
235
|
-
title:
|
|
235
|
+
title: "lemon color",
|
|
236
236
|
},
|
|
237
237
|
{
|
|
238
238
|
sha: "4f98dd3e67d03b79d7a12480c7d1c2fcbd186ac5",
|
|
239
239
|
full_message: "banana sweet\n\ngit-stack-id: E63ytp5dj",
|
|
240
240
|
subject_line: "banana sweet",
|
|
241
241
|
branch_id: "E63ytp5dj",
|
|
242
|
-
title:
|
|
242
|
+
title: "lemon color",
|
|
243
243
|
},
|
|
244
244
|
{
|
|
245
245
|
sha: "f143d03c723c9f5231a81c1e12098511611898cb",
|
|
246
246
|
full_message: "apple sweet",
|
|
247
247
|
subject_line: "apple sweet",
|
|
248
|
-
branch_id:
|
|
249
|
-
title:
|
|
248
|
+
branch_id: "6Ak-qn+5Z",
|
|
249
|
+
title: "new group",
|
|
250
250
|
},
|
|
251
251
|
],
|
|
252
252
|
pr_lookup: {
|
|
@@ -376,7 +376,7 @@ const SINGLE_COMMIT_NEW_GROUP: CommitMetadata.CommitRange = {
|
|
|
376
376
|
full_message: "banana color\n\ngit-stack-id: AAWsYx1UU",
|
|
377
377
|
subject_line: "banana color",
|
|
378
378
|
branch_id: "AAWsYx1UU",
|
|
379
|
-
title:
|
|
379
|
+
title: "banana",
|
|
380
380
|
},
|
|
381
381
|
],
|
|
382
382
|
},
|
|
@@ -447,21 +447,21 @@ const SINGLE_COMMIT_NEW_GROUP: CommitMetadata.CommitRange = {
|
|
|
447
447
|
full_message: "lemon color\n\ngit-stack-id: E63ytp5dj",
|
|
448
448
|
subject_line: "lemon color",
|
|
449
449
|
branch_id: "E63ytp5dj",
|
|
450
|
-
title:
|
|
450
|
+
title: "lemon color",
|
|
451
451
|
},
|
|
452
452
|
{
|
|
453
453
|
sha: "d36d63499425bb46a1e62c2c9df1a4332b13004f",
|
|
454
454
|
full_message: "cantaloupe color\n\ngit-stack-id: E63ytp5dj",
|
|
455
455
|
subject_line: "cantaloupe color",
|
|
456
456
|
branch_id: "E63ytp5dj",
|
|
457
|
-
title:
|
|
457
|
+
title: "lemon color",
|
|
458
458
|
},
|
|
459
459
|
{
|
|
460
460
|
sha: "4f98dd3e67d03b79d7a12480c7d1c2fcbd186ac5",
|
|
461
461
|
full_message: "banana sweet\n\ngit-stack-id: E63ytp5dj",
|
|
462
462
|
subject_line: "banana sweet",
|
|
463
463
|
branch_id: "E63ytp5dj",
|
|
464
|
-
title:
|
|
464
|
+
title: "lemon color",
|
|
465
465
|
},
|
|
466
466
|
],
|
|
467
467
|
},
|
|
@@ -476,8 +476,8 @@ const SINGLE_COMMIT_NEW_GROUP: CommitMetadata.CommitRange = {
|
|
|
476
476
|
sha: "f143d03c723c9f5231a81c1e12098511611898cb",
|
|
477
477
|
full_message: "apple sweet",
|
|
478
478
|
subject_line: "apple sweet",
|
|
479
|
-
branch_id:
|
|
480
|
-
title:
|
|
479
|
+
branch_id: "6Ak-qn+5Z",
|
|
480
|
+
title: "new group",
|
|
481
481
|
},
|
|
482
482
|
],
|
|
483
483
|
},
|
|
@@ -488,35 +488,35 @@ const SINGLE_COMMIT_NEW_GROUP: CommitMetadata.CommitRange = {
|
|
|
488
488
|
full_message: "banana color\n\ngit-stack-id: AAWsYx1UU",
|
|
489
489
|
subject_line: "banana color",
|
|
490
490
|
branch_id: "AAWsYx1UU",
|
|
491
|
-
title:
|
|
491
|
+
title: "banana",
|
|
492
492
|
},
|
|
493
493
|
{
|
|
494
494
|
sha: "3cb22661ecff6c872e96ce9c40b31c824938cab7",
|
|
495
495
|
full_message: "lemon color\n\ngit-stack-id: E63ytp5dj",
|
|
496
496
|
subject_line: "lemon color",
|
|
497
497
|
branch_id: "E63ytp5dj",
|
|
498
|
-
title:
|
|
498
|
+
title: "lemon color",
|
|
499
499
|
},
|
|
500
500
|
{
|
|
501
501
|
sha: "d36d63499425bb46a1e62c2c9df1a4332b13004f",
|
|
502
502
|
full_message: "cantaloupe color\n\ngit-stack-id: E63ytp5dj",
|
|
503
503
|
subject_line: "cantaloupe color",
|
|
504
504
|
branch_id: "E63ytp5dj",
|
|
505
|
-
title:
|
|
505
|
+
title: "lemon color",
|
|
506
506
|
},
|
|
507
507
|
{
|
|
508
508
|
sha: "4f98dd3e67d03b79d7a12480c7d1c2fcbd186ac5",
|
|
509
509
|
full_message: "banana sweet\n\ngit-stack-id: E63ytp5dj",
|
|
510
510
|
subject_line: "banana sweet",
|
|
511
511
|
branch_id: "E63ytp5dj",
|
|
512
|
-
title:
|
|
512
|
+
title: "lemon color",
|
|
513
513
|
},
|
|
514
514
|
{
|
|
515
515
|
sha: "f143d03c723c9f5231a81c1e12098511611898cb",
|
|
516
516
|
full_message: "apple sweet",
|
|
517
517
|
subject_line: "apple sweet",
|
|
518
|
-
branch_id:
|
|
519
|
-
title:
|
|
518
|
+
branch_id: "6Ak-qn+5Z",
|
|
519
|
+
title: "new group",
|
|
520
520
|
},
|
|
521
521
|
],
|
|
522
522
|
pr_lookup: {
|
|
@@ -620,8 +620,8 @@ const COMMIT_MESSAGE_WITH_QUOTES: CommitMetadata.CommitRange = {
|
|
|
620
620
|
sha: "f143d03c723c9f5231a81c1e12098511611898cb",
|
|
621
621
|
full_message: '[new] invalid "by me" quotes',
|
|
622
622
|
subject_line: '[new] invalid "by me" quotes',
|
|
623
|
-
branch_id:
|
|
624
|
-
title:
|
|
623
|
+
branch_id: "6Ak-qn+5Z",
|
|
624
|
+
title: '[new] invalid "by me" quotes',
|
|
625
625
|
},
|
|
626
626
|
],
|
|
627
627
|
},
|
|
@@ -631,8 +631,8 @@ const COMMIT_MESSAGE_WITH_QUOTES: CommitMetadata.CommitRange = {
|
|
|
631
631
|
sha: "f143d03c723c9f5231a81c1e12098511611898cb",
|
|
632
632
|
full_message: '[new] invalid "by me" quotes',
|
|
633
633
|
subject_line: '[new] invalid "by me" quotes',
|
|
634
|
-
branch_id:
|
|
635
|
-
title:
|
|
634
|
+
branch_id: "6Ak-qn+5Z",
|
|
635
|
+
title: '[new] invalid "by me" quotes',
|
|
636
636
|
},
|
|
637
637
|
],
|
|
638
638
|
pr_lookup: {},
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
1
5
|
import * as Metadata from "~/core/Metadata";
|
|
6
|
+
import { cli } from "~/core/cli";
|
|
7
|
+
import { invariant } from "~/core/invariant";
|
|
2
8
|
|
|
3
9
|
import type * as CommitMetadata from "~/core/CommitMetadata";
|
|
4
10
|
|
|
@@ -47,7 +53,7 @@ import type * as CommitMetadata from "~/core/CommitMetadata";
|
|
|
47
53
|
// apple sweet
|
|
48
54
|
//
|
|
49
55
|
export function GitReviseTodo(args: Args): string {
|
|
50
|
-
const
|
|
56
|
+
const commit_list = [];
|
|
51
57
|
|
|
52
58
|
const group_list = args.commit_range.group_list;
|
|
53
59
|
|
|
@@ -55,31 +61,83 @@ export function GitReviseTodo(args: Args): string {
|
|
|
55
61
|
const group = group_list[i];
|
|
56
62
|
|
|
57
63
|
for (const commit of group.commits) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
commit_list.push(commit);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const todo = GitReviseTodo.todo({ commit_list });
|
|
69
|
+
return todo;
|
|
70
|
+
}
|
|
64
71
|
|
|
65
|
-
|
|
72
|
+
type CommitListArgs = {
|
|
73
|
+
commit_list: CommitMetadata.CommitRange["commit_list"];
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
GitReviseTodo.todo = function todo(args: CommitListArgs) {
|
|
77
|
+
const entry_list = [];
|
|
66
78
|
|
|
67
|
-
|
|
79
|
+
for (const commit of args.commit_list) {
|
|
80
|
+
// update git commit message with stack id
|
|
81
|
+
const id = commit.branch_id;
|
|
82
|
+
const title = commit.title;
|
|
68
83
|
|
|
69
|
-
|
|
70
|
-
|
|
84
|
+
invariant(id, "commit.branch_id must exist");
|
|
85
|
+
invariant(title, "commit.title must exist");
|
|
71
86
|
|
|
72
|
-
|
|
73
|
-
const entry_lines = [`++ pick ${sha}`, message_with_id];
|
|
74
|
-
const entry = entry_lines.join("\n");
|
|
87
|
+
const metadata = { id, title };
|
|
75
88
|
|
|
76
|
-
|
|
77
|
-
|
|
89
|
+
const unsafe_message_with_id = Metadata.write(
|
|
90
|
+
commit.full_message,
|
|
91
|
+
metadata
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
let message_with_id = unsafe_message_with_id;
|
|
95
|
+
|
|
96
|
+
message_with_id = message_with_id.replace(/[^\\]"/g, '\\"');
|
|
97
|
+
|
|
98
|
+
// get first 12 characters of commit sha
|
|
99
|
+
const sha = commit.sha.slice(0, 12);
|
|
100
|
+
|
|
101
|
+
// generate git revise entry
|
|
102
|
+
const entry_lines = [`++ pick ${sha}`, message_with_id];
|
|
103
|
+
const entry = entry_lines.join("\n");
|
|
104
|
+
|
|
105
|
+
entry_list.push(entry);
|
|
78
106
|
}
|
|
79
107
|
|
|
80
108
|
const todo = entry_list.join("\n\n");
|
|
81
109
|
return todo;
|
|
82
|
-
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
GitReviseTodo.execute = async function grt_execute(args: ExecuteArgs) {
|
|
113
|
+
// generate temporary directory and drop sequence editor script
|
|
114
|
+
const tmp_git_sequence_editor_path = path.join(
|
|
115
|
+
os.tmpdir(),
|
|
116
|
+
"git-sequence-editor.sh"
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
// ensure script is executable
|
|
120
|
+
fs.chmodSync(tmp_git_sequence_editor_path, "755");
|
|
121
|
+
|
|
122
|
+
const git_revise_todo = GitReviseTodo(args);
|
|
123
|
+
|
|
124
|
+
// execute cli with temporary git sequence editor script
|
|
125
|
+
// revise from merge base to pick correct commits
|
|
126
|
+
const command = [
|
|
127
|
+
`GIT_EDITOR="${tmp_git_sequence_editor_path}"`,
|
|
128
|
+
`GIT_REVISE_TODO="${git_revise_todo}"`,
|
|
129
|
+
`git`,
|
|
130
|
+
`revise --edit -i ${args.rebase_merge_base}`,
|
|
131
|
+
];
|
|
132
|
+
|
|
133
|
+
await cli(command, { stdio: ["ignore", "ignore", "ignore"] });
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
type ExecuteArgs = {
|
|
137
|
+
rebase_group_index: number;
|
|
138
|
+
rebase_merge_base: string;
|
|
139
|
+
commit_range: CommitMetadata.CommitRange;
|
|
140
|
+
};
|
|
83
141
|
|
|
84
142
|
type Args = {
|
|
85
143
|
rebase_group_index: number;
|