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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "git-stack-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"author": "magus",
|
|
6
6
|
"license": "MIT",
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
"chalk": "^5.3.0",
|
|
43
43
|
"immer": "^10.0.3",
|
|
44
44
|
"ink-cjs": "4.4.1",
|
|
45
|
+
"lodash": "^4.17.21",
|
|
45
46
|
"luxon": "^3.4.4",
|
|
46
47
|
"react": "^18.2.0",
|
|
47
48
|
"react-devtools-core": "^4.19.1",
|
|
@@ -57,6 +58,7 @@
|
|
|
57
58
|
"@rollup/plugin-replace": "^5.0.5",
|
|
58
59
|
"@rollup/plugin-typescript": "^11.1.6",
|
|
59
60
|
"@types/chalk": "^2.2.0",
|
|
61
|
+
"@types/lodash": "^4.17.7",
|
|
60
62
|
"@types/luxon": "^3.4.2",
|
|
61
63
|
"@types/node": "^20.8.7",
|
|
62
64
|
"@types/react": "^18.2.33",
|
package/src/app/App.tsx
CHANGED
|
@@ -4,6 +4,7 @@ import { AutoUpdate } from "~/app/AutoUpdate";
|
|
|
4
4
|
import { CherryPickCheck } from "~/app/CherryPickCheck";
|
|
5
5
|
import { Debug } from "~/app/Debug";
|
|
6
6
|
import { DependencyCheck } from "~/app/DependencyCheck";
|
|
7
|
+
import { DetectInitialPR } from "~/app/DetectInitialPR";
|
|
7
8
|
import { DirtyCheck } from "~/app/DirtyCheck";
|
|
8
9
|
import { GatherMetadata } from "~/app/GatherMetadata";
|
|
9
10
|
import { GithubApiError } from "~/app/GithubApiError";
|
|
@@ -15,6 +16,7 @@ import { RebaseCheck } from "~/app/RebaseCheck";
|
|
|
15
16
|
import { Store } from "~/app/Store";
|
|
16
17
|
import { Fixup } from "~/commands/Fixup";
|
|
17
18
|
import { Log } from "~/commands/Log";
|
|
19
|
+
import { Rebase } from "~/commands/Rebase";
|
|
18
20
|
|
|
19
21
|
export function App() {
|
|
20
22
|
const actions = Store.useActions();
|
|
@@ -72,6 +74,14 @@ function MaybeMain() {
|
|
|
72
74
|
return <Fixup />;
|
|
73
75
|
} else if (positional_list.has("log")) {
|
|
74
76
|
return <Log />;
|
|
77
|
+
} else if (positional_list.has("rebase")) {
|
|
78
|
+
return (
|
|
79
|
+
<GatherMetadata>
|
|
80
|
+
<LocalCommitStatus>
|
|
81
|
+
<Rebase />
|
|
82
|
+
</LocalCommitStatus>
|
|
83
|
+
</GatherMetadata>
|
|
84
|
+
);
|
|
75
85
|
}
|
|
76
86
|
|
|
77
87
|
return (
|
|
@@ -80,7 +90,9 @@ function MaybeMain() {
|
|
|
80
90
|
|
|
81
91
|
<GatherMetadata>
|
|
82
92
|
<LocalCommitStatus>
|
|
83
|
-
<
|
|
93
|
+
<DetectInitialPR>
|
|
94
|
+
<Main />
|
|
95
|
+
</DetectInitialPR>
|
|
84
96
|
</LocalCommitStatus>
|
|
85
97
|
</GatherMetadata>
|
|
86
98
|
</DirtyCheck>
|
|
@@ -57,17 +57,17 @@ export function CherryPickCheck(props: Props) {
|
|
|
57
57
|
default:
|
|
58
58
|
return (
|
|
59
59
|
<Await
|
|
60
|
+
function={run}
|
|
60
61
|
fallback={
|
|
61
62
|
<Ink.Text color={colors.yellow}>
|
|
62
63
|
Checking for <Command>git cherry-pick</Command>…
|
|
63
64
|
</Ink.Text>
|
|
64
65
|
}
|
|
65
|
-
function={cherry_pick_check}
|
|
66
66
|
/>
|
|
67
67
|
);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
async function
|
|
70
|
+
async function run() {
|
|
71
71
|
const actions = Store.getState().actions;
|
|
72
72
|
|
|
73
73
|
try {
|
|
@@ -88,7 +88,7 @@ export function CherryPickCheck(props: Props) {
|
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
actions.exit(
|
|
91
|
+
actions.exit(11);
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import * as Ink from "ink-cjs";
|
|
4
|
+
import cloneDeep from "lodash/cloneDeep";
|
|
5
|
+
|
|
6
|
+
import { Await } from "~/app/Await";
|
|
7
|
+
import { Brackets } from "~/app/Brackets";
|
|
8
|
+
import { Command } from "~/app/Command";
|
|
9
|
+
import { FormatText } from "~/app/FormatText";
|
|
10
|
+
import { Store } from "~/app/Store";
|
|
11
|
+
import { Url } from "~/app/Url";
|
|
12
|
+
import { YesNoPrompt } from "~/app/YesNoPrompt";
|
|
13
|
+
import * as CommitMetadata from "~/core/CommitMetadata";
|
|
14
|
+
import { GitReviseTodo } from "~/core/GitReviseTodo";
|
|
15
|
+
import { cli } from "~/core/cli";
|
|
16
|
+
import { colors } from "~/core/colors";
|
|
17
|
+
import * as github from "~/core/github";
|
|
18
|
+
import { invariant } from "~/core/invariant";
|
|
19
|
+
|
|
20
|
+
type Props = {
|
|
21
|
+
children: React.ReactNode;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type PullRequest = NonNullable<Awaited<ReturnType<typeof github.pr_status>>>;
|
|
25
|
+
|
|
26
|
+
type State = {
|
|
27
|
+
status: "init" | "prompt" | "revise" | "done";
|
|
28
|
+
pr: null | PullRequest;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function reducer(state: State, patch: Partial<State>) {
|
|
32
|
+
return { ...state, ...patch };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function DetectInitialPR(props: Props) {
|
|
36
|
+
const actions = Store.useActions();
|
|
37
|
+
const branch_name = Store.useState((state) => state.branch_name);
|
|
38
|
+
|
|
39
|
+
const [state, patch] = React.useReducer(reducer, {
|
|
40
|
+
status: "init",
|
|
41
|
+
pr: null,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
switch (state.status) {
|
|
45
|
+
case "done":
|
|
46
|
+
return props.children;
|
|
47
|
+
|
|
48
|
+
case "revise":
|
|
49
|
+
return (
|
|
50
|
+
<Await
|
|
51
|
+
function={run_revise}
|
|
52
|
+
fallback={
|
|
53
|
+
<Ink.Text color={colors.yellow}>
|
|
54
|
+
Synchronizing local commit metadata with remote branch on Github…
|
|
55
|
+
</Ink.Text>
|
|
56
|
+
}
|
|
57
|
+
/>
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
case "prompt":
|
|
61
|
+
return (
|
|
62
|
+
<YesNoPrompt
|
|
63
|
+
message={
|
|
64
|
+
<Ink.Box flexDirection="column">
|
|
65
|
+
<FormatText
|
|
66
|
+
wrapper={<Ink.Text color={colors.yellow} />}
|
|
67
|
+
message="{branch_name} exists on Github and was not generated with {git_stack}."
|
|
68
|
+
values={{
|
|
69
|
+
branch_name: <Brackets>{branch_name}</Brackets>,
|
|
70
|
+
git_stack: <Command>git stack</Command>,
|
|
71
|
+
}}
|
|
72
|
+
/>
|
|
73
|
+
<Ink.Text> </Ink.Text>
|
|
74
|
+
<FormatText
|
|
75
|
+
message=" {url}"
|
|
76
|
+
values={{
|
|
77
|
+
url: <Url>{state.pr?.url}</Url>,
|
|
78
|
+
}}
|
|
79
|
+
/>
|
|
80
|
+
<Ink.Text> </Ink.Text>
|
|
81
|
+
<FormatText
|
|
82
|
+
wrapper={<Ink.Text color={colors.yellow} />}
|
|
83
|
+
message="In order to synchronize we need to rename your local branch, would you like to proceed?"
|
|
84
|
+
/>
|
|
85
|
+
</Ink.Box>
|
|
86
|
+
}
|
|
87
|
+
onYes={async () => {
|
|
88
|
+
patch({ status: "revise" });
|
|
89
|
+
}}
|
|
90
|
+
onNo={async () => {
|
|
91
|
+
actions.exit(0);
|
|
92
|
+
}}
|
|
93
|
+
/>
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
default:
|
|
97
|
+
return (
|
|
98
|
+
<Await
|
|
99
|
+
function={run}
|
|
100
|
+
fallback={
|
|
101
|
+
<Ink.Text color={colors.yellow}>
|
|
102
|
+
Checking for existing PR on Github…
|
|
103
|
+
</Ink.Text>
|
|
104
|
+
}
|
|
105
|
+
/>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function run() {
|
|
110
|
+
const actions = Store.getState().actions;
|
|
111
|
+
const branch_name = Store.getState().branch_name;
|
|
112
|
+
const commit_range = Store.getState().commit_range;
|
|
113
|
+
|
|
114
|
+
invariant(branch_name, "branch_name must exist");
|
|
115
|
+
invariant(commit_range, "branch_name must exist");
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
let has_existing_metadata = false;
|
|
119
|
+
for (const commit of commit_range.commit_list) {
|
|
120
|
+
if (commit.branch_id) {
|
|
121
|
+
has_existing_metadata = true;
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!has_existing_metadata) {
|
|
127
|
+
// check for pr with matching branch name to initialize group
|
|
128
|
+
const pr = await github.pr_status(branch_name);
|
|
129
|
+
if (pr) {
|
|
130
|
+
return patch({ status: "prompt", pr });
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
patch({ status: "done" });
|
|
135
|
+
} catch (err) {
|
|
136
|
+
actions.error("Must be run from within a git repository.");
|
|
137
|
+
|
|
138
|
+
if (err instanceof Error) {
|
|
139
|
+
if (actions.isDebug()) {
|
|
140
|
+
actions.error(err.message);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
actions.exit(9);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function run_revise() {
|
|
149
|
+
const actions = Store.getState().actions;
|
|
150
|
+
const master_branch = Store.getState().master_branch;
|
|
151
|
+
const branch_name = Store.getState().branch_name;
|
|
152
|
+
const commit_range = cloneDeep(Store.getState().commit_range);
|
|
153
|
+
|
|
154
|
+
invariant(branch_name, "branch_name must exist");
|
|
155
|
+
invariant(commit_range, "branch_name must exist");
|
|
156
|
+
|
|
157
|
+
for (const group of commit_range.group_list) {
|
|
158
|
+
group.id = branch_name;
|
|
159
|
+
group.title = state.pr?.title || "-";
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// get latest merge_base relative to local master
|
|
163
|
+
const rebase_group_index = 0;
|
|
164
|
+
|
|
165
|
+
const rebase_merge_base = (
|
|
166
|
+
await cli(`git merge-base HEAD ${master_branch}`)
|
|
167
|
+
).stdout;
|
|
168
|
+
|
|
169
|
+
await GitReviseTodo.execute({
|
|
170
|
+
rebase_group_index,
|
|
171
|
+
rebase_merge_base,
|
|
172
|
+
commit_range,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const new_branch_name = `${branch_name}-sync`;
|
|
176
|
+
await cli(`git checkout -b ${new_branch_name}`);
|
|
177
|
+
|
|
178
|
+
await cli(`git branch -D ${branch_name}`);
|
|
179
|
+
|
|
180
|
+
const commit_range_new = await CommitMetadata.range();
|
|
181
|
+
|
|
182
|
+
actions.set((state) => {
|
|
183
|
+
state.branch_name = new_branch_name;
|
|
184
|
+
state.commit_range = commit_range_new;
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
patch({ status: "done" });
|
|
188
|
+
}
|
|
189
|
+
}
|
package/src/app/DirtyCheck.tsx
CHANGED
|
@@ -68,17 +68,17 @@ export function DirtyCheck(props: Props) {
|
|
|
68
68
|
default:
|
|
69
69
|
return (
|
|
70
70
|
<Await
|
|
71
|
+
function={run}
|
|
71
72
|
fallback={
|
|
72
73
|
<Ink.Text color={colors.yellow}>
|
|
73
74
|
Ensuring <Command>git status --porcelain</Command>…
|
|
74
75
|
</Ink.Text>
|
|
75
76
|
}
|
|
76
|
-
function={rebase_check}
|
|
77
77
|
/>
|
|
78
78
|
);
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
async function
|
|
81
|
+
async function run() {
|
|
82
82
|
const actions = Store.getState().actions;
|
|
83
83
|
|
|
84
84
|
try {
|
|
@@ -95,7 +95,7 @@ export function DirtyCheck(props: Props) {
|
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
actions.exit(
|
|
98
|
+
actions.exit(12);
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
}
|
package/src/app/FormatText.tsx
CHANGED
|
@@ -20,13 +20,13 @@ export function GatherMetadata(props: Props) {
|
|
|
20
20
|
);
|
|
21
21
|
|
|
22
22
|
return (
|
|
23
|
-
<Await fallback={fallback} function={
|
|
23
|
+
<Await fallback={fallback} function={run}>
|
|
24
24
|
{props.children}
|
|
25
25
|
</Await>
|
|
26
26
|
);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
async function
|
|
29
|
+
async function run() {
|
|
30
30
|
const actions = Store.getState().actions;
|
|
31
31
|
const argv = Store.getState().argv;
|
|
32
32
|
|
|
@@ -28,7 +28,7 @@ export function LocalCommitStatus(props: Props) {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
return (
|
|
31
|
-
<Await fallback={fallback} function={
|
|
31
|
+
<Await fallback={fallback} function={run}>
|
|
32
32
|
{props.children}
|
|
33
33
|
</Await>
|
|
34
34
|
);
|
|
@@ -41,12 +41,11 @@ async function mock_metadata() {
|
|
|
41
41
|
|
|
42
42
|
Store.setState((state) => {
|
|
43
43
|
Object.assign(state, deserialized);
|
|
44
|
-
|
|
45
44
|
state.step = "status";
|
|
46
45
|
});
|
|
47
46
|
}
|
|
48
47
|
|
|
49
|
-
async function
|
|
48
|
+
async function run() {
|
|
50
49
|
const actions = Store.getState().actions;
|
|
51
50
|
|
|
52
51
|
try {
|
|
@@ -1,193 +1,7 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
|
|
3
|
-
import
|
|
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";
|
|
3
|
+
import { Rebase } from "~/commands/Rebase";
|
|
17
4
|
|
|
18
5
|
export function LocalMergeRebase() {
|
|
19
|
-
return
|
|
20
|
-
<Await
|
|
21
|
-
fallback={<Ink.Text color={colors.yellow}>Rebasing commits…</Ink.Text>}
|
|
22
|
-
function={run}
|
|
23
|
-
/>
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
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.set((state) => {
|
|
124
|
-
state.commit_range = next_commit_range;
|
|
125
|
-
state.step = "status";
|
|
126
|
-
});
|
|
127
|
-
} catch (err) {
|
|
128
|
-
actions.error("Unable to rebase.");
|
|
129
|
-
|
|
130
|
-
if (err instanceof Error) {
|
|
131
|
-
if (actions.isDebug()) {
|
|
132
|
-
actions.error(err.message);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
handle_exit();
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// cleanup git operations if cancelled during manual rebase
|
|
140
|
-
function restore_git() {
|
|
141
|
-
// signint handler MUST run synchronously
|
|
142
|
-
// trying to use `await cli(...)` here will silently fail since
|
|
143
|
-
// all children processes receive the SIGINT signal
|
|
144
|
-
const spawn_options = { ignoreExitCode: true };
|
|
145
|
-
|
|
146
|
-
// always clean up any patch files
|
|
147
|
-
cli.sync(`rm ${PATCH_FILE}`, spawn_options);
|
|
148
|
-
|
|
149
|
-
// always hard reset and clean to allow subsequent checkout
|
|
150
|
-
// if there are files checkout will fail and cascade fail subsequent commands
|
|
151
|
-
cli.sync(`git reset --hard`, spawn_options);
|
|
152
|
-
cli.sync(`git clean -df`, spawn_options);
|
|
153
|
-
|
|
154
|
-
// always put self back in original branch
|
|
155
|
-
cli.sync(`git checkout ${branch_name}`, spawn_options);
|
|
156
|
-
|
|
157
|
-
// ...and cleanup temporary branch
|
|
158
|
-
cli.sync(`git branch -D ${temp_branch_name}`, spawn_options);
|
|
159
|
-
|
|
160
|
-
if (commit_range) {
|
|
161
|
-
// ...and cleanup pr group branches
|
|
162
|
-
for (const group of commit_range.group_list) {
|
|
163
|
-
cli.sync(`git branch -D ${group.id}`, spawn_options);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// restore back to original dir
|
|
168
|
-
if (fs.existsSync(cwd)) {
|
|
169
|
-
process.chdir(cwd);
|
|
170
|
-
}
|
|
171
|
-
cli.sync(`pwd`, spawn_options);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function handle_exit() {
|
|
175
|
-
actions.output(
|
|
176
|
-
<Ink.Text color={colors.yellow}>
|
|
177
|
-
Restoring <Brackets>{branch_name}</Brackets>…
|
|
178
|
-
</Ink.Text>
|
|
179
|
-
);
|
|
180
|
-
|
|
181
|
-
restore_git();
|
|
182
|
-
|
|
183
|
-
actions.output(
|
|
184
|
-
<Ink.Text color={colors.yellow}>
|
|
185
|
-
Restored <Brackets>{branch_name}</Brackets>.
|
|
186
|
-
</Ink.Text>
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
actions.exit(6);
|
|
190
|
-
}
|
|
6
|
+
return <Rebase />;
|
|
191
7
|
}
|
|
192
|
-
|
|
193
|
-
const PATCH_FILE = "git-stack-cli-patch.patch";
|
package/src/app/ManualRebase.tsx
CHANGED
|
@@ -5,6 +5,7 @@ import os from "node:os";
|
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
|
|
7
7
|
import * as Ink from "ink-cjs";
|
|
8
|
+
import cloneDeep from "lodash/cloneDeep";
|
|
8
9
|
|
|
9
10
|
import { Await } from "~/app/Await";
|
|
10
11
|
import { Brackets } from "~/app/Brackets";
|
|
@@ -34,18 +35,37 @@ async function run() {
|
|
|
34
35
|
const actions = state.actions;
|
|
35
36
|
const argv = state.argv;
|
|
36
37
|
const branch_name = state.branch_name;
|
|
38
|
+
const original_commit_range = cloneDeep(state.commit_range);
|
|
37
39
|
const commit_map = state.commit_map;
|
|
38
40
|
const master_branch = state.master_branch;
|
|
39
41
|
const cwd = state.cwd;
|
|
40
42
|
const repo_root = state.repo_root;
|
|
41
43
|
|
|
42
44
|
invariant(branch_name, "branch_name must exist");
|
|
45
|
+
invariant(original_commit_range, "original_commit_range must exist");
|
|
43
46
|
invariant(commit_map, "commit_map must exist");
|
|
44
47
|
invariant(repo_root, "repo_root must exist");
|
|
45
48
|
|
|
46
49
|
// always listen for SIGINT event and restore git state
|
|
47
50
|
process.once("SIGINT", handle_exit);
|
|
48
51
|
|
|
52
|
+
// get latest merge_base relative to local master
|
|
53
|
+
const merge_base = (await cli(`git merge-base HEAD ${master_branch}`)).stdout;
|
|
54
|
+
|
|
55
|
+
// immediately paint all commit to preserve selected commit ranges
|
|
56
|
+
original_commit_range.group_list.reverse();
|
|
57
|
+
for (const commit of original_commit_range.commit_list) {
|
|
58
|
+
const group_from_map = commit_map[commit.sha];
|
|
59
|
+
commit.branch_id = group_from_map.id;
|
|
60
|
+
commit.title = group_from_map.title;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
await GitReviseTodo.execute({
|
|
64
|
+
rebase_group_index: 0,
|
|
65
|
+
rebase_merge_base: merge_base,
|
|
66
|
+
commit_range: original_commit_range,
|
|
67
|
+
});
|
|
68
|
+
|
|
49
69
|
let DEFAULT_PR_BODY = "";
|
|
50
70
|
if (state.pr_template_body) {
|
|
51
71
|
DEFAULT_PR_BODY = state.pr_template_body;
|
|
@@ -58,9 +78,6 @@ async function run() {
|
|
|
58
78
|
// reverse commit list so that we can cherry-pick in order
|
|
59
79
|
commit_range.group_list.reverse();
|
|
60
80
|
|
|
61
|
-
// get latest merge_base relative to local master
|
|
62
|
-
const merge_base = (await cli(`git merge-base HEAD ${master_branch}`)).stdout;
|
|
63
|
-
|
|
64
81
|
let rebase_merge_base = merge_base;
|
|
65
82
|
let rebase_group_index = 0;
|
|
66
83
|
|
|
@@ -150,19 +167,11 @@ async function run() {
|
|
|
150
167
|
// create temporary branch
|
|
151
168
|
await cli(`git checkout -b ${temp_branch_name}`);
|
|
152
169
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
[
|
|
159
|
-
`GIT_EDITOR="${tmp_git_sequence_editor_path}"`,
|
|
160
|
-
`GIT_REVISE_TODO="${git_revise_todo}"`,
|
|
161
|
-
`git`,
|
|
162
|
-
`revise --edit -i ${rebase_merge_base}`,
|
|
163
|
-
],
|
|
164
|
-
{ stdio: ["ignore", "ignore", "ignore"] }
|
|
165
|
-
);
|
|
170
|
+
await GitReviseTodo.execute({
|
|
171
|
+
rebase_group_index,
|
|
172
|
+
rebase_merge_base,
|
|
173
|
+
commit_range,
|
|
174
|
+
});
|
|
166
175
|
|
|
167
176
|
// early return since we do not need to sync
|
|
168
177
|
if (!argv.sync) {
|
package/src/app/RebaseCheck.tsx
CHANGED
|
@@ -57,17 +57,17 @@ export function RebaseCheck(props: Props) {
|
|
|
57
57
|
default:
|
|
58
58
|
return (
|
|
59
59
|
<Await
|
|
60
|
+
function={run}
|
|
60
61
|
fallback={
|
|
61
62
|
<Ink.Text color={colors.yellow}>
|
|
62
63
|
Checking for <Command>git rebase</Command>…
|
|
63
64
|
</Ink.Text>
|
|
64
65
|
}
|
|
65
|
-
function={rebase_check}
|
|
66
66
|
/>
|
|
67
67
|
);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
async function
|
|
70
|
+
async function run() {
|
|
71
71
|
const actions = Store.getState().actions;
|
|
72
72
|
|
|
73
73
|
try {
|
|
@@ -88,7 +88,7 @@ export function RebaseCheck(props: Props) {
|
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
actions.exit(
|
|
91
|
+
actions.exit(13);
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
}
|
|
@@ -9,8 +9,8 @@ import { Parens } from "~/app/Parens";
|
|
|
9
9
|
import { Store } from "~/app/Store";
|
|
10
10
|
import { TextInput } from "~/app/TextInput";
|
|
11
11
|
import { colors } from "~/core/colors";
|
|
12
|
+
import { gs_short_id } from "~/core/gs_short_id";
|
|
12
13
|
import { invariant } from "~/core/invariant";
|
|
13
|
-
import { short_id } from "~/core/short_id";
|
|
14
14
|
import { wrap_index } from "~/core/wrap_index";
|
|
15
15
|
|
|
16
16
|
import type { State } from "~/app/Store";
|
|
@@ -388,7 +388,7 @@ function SelectCommitRangesInternal(props: Props) {
|
|
|
388
388
|
);
|
|
389
389
|
|
|
390
390
|
function submit_group_input(title: string) {
|
|
391
|
-
const id =
|
|
391
|
+
const id = gs_short_id();
|
|
392
392
|
|
|
393
393
|
actions.output(
|
|
394
394
|
<FormatText
|