git-stack-cli 0.8.7 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/README.md +32 -3
  2. package/dist/cjs/index.cjs +34770 -0
  3. package/package.json +21 -7
  4. package/dist/__fixtures__/metadata.js +0 -666
  5. package/dist/__fixtures__/metadata.json +0 -186
  6. package/dist/app/App copy.js +0 -30
  7. package/dist/app/App.js +0 -42
  8. package/dist/app/ArgCheck.js +0 -21
  9. package/dist/app/AutoUpdate.js +0 -128
  10. package/dist/app/Await.js +0 -45
  11. package/dist/app/Brackets copy.js +0 -10
  12. package/dist/app/Brackets.js +0 -11
  13. package/dist/app/Command.js +0 -7
  14. package/dist/app/Counter.js +0 -19
  15. package/dist/app/Debug.js +0 -33
  16. package/dist/app/DependencyCheck.js +0 -89
  17. package/dist/app/Exit.js +0 -14
  18. package/dist/app/FormatText.js +0 -9
  19. package/dist/app/GatherMetadata copy.js +0 -91
  20. package/dist/app/GatherMetadata.js +0 -100
  21. package/dist/app/GithubApiError.js +0 -50
  22. package/dist/app/InitMetadata.js +0 -14
  23. package/dist/app/Input.js +0 -15
  24. package/dist/app/KeepAlive.js +0 -11
  25. package/dist/app/LocalCommitStatus.js +0 -43
  26. package/dist/app/LocalMergeRebase.js +0 -157
  27. package/dist/app/Main copy.js +0 -200
  28. package/dist/app/ManualRebase copy.js +0 -127
  29. package/dist/app/ManualRebase.js +0 -220
  30. package/dist/app/MultiSelect copy.js +0 -76
  31. package/dist/app/MultiSelect.js +0 -143
  32. package/dist/app/NPMAutoUpdate.js +0 -34
  33. package/dist/app/Output.js +0 -19
  34. package/dist/app/Parens copy.js +0 -9
  35. package/dist/app/Parens.js +0 -10
  36. package/dist/app/PostRebaseStatus copy.js +0 -23
  37. package/dist/app/PostRebaseStatus.js +0 -23
  38. package/dist/app/PreLocalMergeRebase.js +0 -21
  39. package/dist/app/PreSelectCommitRanges copy.js +0 -21
  40. package/dist/app/PreSelectCommitRanges.js +0 -21
  41. package/dist/app/Providers.js +0 -5
  42. package/dist/app/RebaseCheck.js +0 -56
  43. package/dist/app/SelectCommitRange.js +0 -1
  44. package/dist/app/SelectCommitRanges.js +0 -207
  45. package/dist/app/Status copy.js +0 -46
  46. package/dist/app/Status.js +0 -61
  47. package/dist/app/StatusTable.js +0 -115
  48. package/dist/app/Store.js +0 -136
  49. package/dist/app/Table.js +0 -65
  50. package/dist/app/TextInput.js +0 -51
  51. package/dist/app/Url copy.js +0 -6
  52. package/dist/app/Url.js +0 -6
  53. package/dist/app/Waterfall.js +0 -20
  54. package/dist/app/YesNoPrompt copy.js +0 -24
  55. package/dist/app/YesNoPrompt.js +0 -40
  56. package/dist/app/main.js +0 -39
  57. package/dist/cli.js +0 -9
  58. package/dist/command.js +0 -60
  59. package/dist/core/CommitMetadata.js +0 -159
  60. package/dist/core/Metadata copy.js +0 -37
  61. package/dist/core/Metadata.js +0 -36
  62. package/dist/core/Metadata.test.js +0 -34
  63. package/dist/core/StackSummaryTable.js +0 -86
  64. package/dist/core/StackSummaryTable.test.js +0 -134
  65. package/dist/core/StackTable.js +0 -38
  66. package/dist/core/SummaryTable.js +0 -38
  67. package/dist/core/ZustandStore.js +0 -23
  68. package/dist/core/assertNever.js +0 -4
  69. package/dist/core/cache.js +0 -39
  70. package/dist/core/capitalize.js +0 -5
  71. package/dist/core/chalk.js +0 -83
  72. package/dist/core/clamp.js +0 -6
  73. package/dist/core/cli copy.js +0 -44
  74. package/dist/core/cli.js +0 -86
  75. package/dist/core/color.js +0 -83
  76. package/dist/core/colors.js +0 -15
  77. package/dist/core/date.js +0 -18
  78. package/dist/core/dependency_check.js +0 -27
  79. package/dist/core/env.js +0 -4
  80. package/dist/core/exit.js +0 -4
  81. package/dist/core/fetch_json.js +0 -24
  82. package/dist/core/get_commit_metadata.js +0 -61
  83. package/dist/core/github.js +0 -118
  84. package/dist/core/id.js +0 -61
  85. package/dist/core/invariant copy.js +0 -5
  86. package/dist/core/invariant.js +0 -5
  87. package/dist/core/isFiniteValue.js +0 -3
  88. package/dist/core/is_command_available.js +0 -15
  89. package/dist/core/is_dev.js +0 -1
  90. package/dist/core/is_finite_value.js +0 -3
  91. package/dist/core/json.js +0 -35
  92. package/dist/core/match_group.js +0 -9
  93. package/dist/core/readJson.js +0 -3
  94. package/dist/core/read_json.js +0 -12
  95. package/dist/core/safe_quote.js +0 -9
  96. package/dist/core/semver_compare.js +0 -26
  97. package/dist/core/serialize_json.js +0 -17
  98. package/dist/core/short_id.js +0 -60
  99. package/dist/core/sleep copy.js +0 -3
  100. package/dist/core/sleep.js +0 -3
  101. package/dist/core/wrap_index.js +0 -10
  102. package/dist/index.js +0 -13
  103. package/dist/main copy.js +0 -266
  104. package/dist/main.backup.js +0 -266
  105. package/dist/main.js +0 -265
@@ -1,200 +0,0 @@
1
- import * as React from "react";
2
- import * as Ink from "ink";
3
- import { v4 as uuid_v4 } from "uuid";
4
- import * as CommitMetadata from "../core/CommitMetadata.js";
5
- import * as Metadata from "../core/Metadata.js";
6
- import { cli } from "../core/cli.js";
7
- import * as github from "../core/github.js";
8
- import { invariant } from "../core/invariant.js";
9
- import { match_group } from "../core/match_group.js";
10
- import { Await } from "./Await.js";
11
- import { Exit } from "./Exit.js";
12
- import { Store } from "./Store.js";
13
- export function GatherMetadata(props) {
14
- const argv = Store.useState((state) => state.argv);
15
- invariant(argv, "argv must exist");
16
- return (React.createElement(Await, { fallback: null, function: () => gather_metadata({ argv }) }, props.children));
17
- }
18
- async function gather_metadata(args) {
19
- const actions = Store.getState().actions;
20
- const head = (await cli("git rev-parse HEAD")).stdout;
21
- const merge_base = (await cli("git merge-base HEAD master")).stdout;
22
- // handle when there are no detected changes
23
- if (head === merge_base) {
24
- actions.newline();
25
- actions.output(React.createElement(Ink.Text, { color: "gray" }, "No changes detected."));
26
- actions.output(React.createElement(Exit, { clear: true, code: 0 }));
27
- return;
28
- }
29
- const branch_name = (await cli("git rev-parse --abbrev-ref HEAD")).stdout;
30
- // git@github.com:magus/git-multi-diff-playground.git
31
- // https://github.com/magus/git-multi-diff-playground.git
32
- const origin_url = (await cli(`git config --get remote.origin.url`)).stdout;
33
- const repo_path = match_group(origin_url, RE.repo_path, "repo_path");
34
- const commit_metadata_list = await CommitMetadata.all();
35
- Store.setState((state) => {
36
- state.head = head;
37
- state.merge_base = merge_base;
38
- state.branch_name = branch_name;
39
- state.repo_path = repo_path;
40
- state.commit_metadata_list = commit_metadata_list;
41
- });
42
- // print_table(repo_path, commit_metadata_list);
43
- const needs_update = commit_metadata_list.some((meta) => !meta.pr_exists || meta.pr_dirty);
44
- if (args.argv.check) {
45
- actions.output(React.createElement(Exit, { clear: true, code: 0 }));
46
- return;
47
- }
48
- if (!args.argv.force && !needs_update) {
49
- actions.newline();
50
- actions.output(React.createElement(Ink.Text, null, "\u2705 Everything up to date."));
51
- actions.output(React.createElement(Ink.Text, { color: "gray" },
52
- React.createElement(Ink.Text, null, "Run with"),
53
- React.createElement(Ink.Text, { bold: true, color: "yellow" }, ` --force `),
54
- React.createElement(Ink.Text, null, "to force update all pull requests.")));
55
- actions.output(React.createElement(Exit, { clear: true, code: 0 }));
56
- }
57
- Store.setState((state) => {
58
- state.step = "select-commit-ranges";
59
- });
60
- }
61
- async function manual_rebase(args) {
62
- const temp_branch_name = `${args.branch_name}_${uuid_v4()}`;
63
- try {
64
- // create temporary branch based on merge base
65
- await cli(`git checkout -b ${temp_branch_name} ${args.merge_base}`);
66
- const picked_commit_metadata_list = [];
67
- // cherry-pick and amend commits one by one
68
- for (let i = 0; i < args.commit_metadata_list.length; i++) {
69
- const sha = args.commit_metadata_list[i].sha;
70
- let base;
71
- if (i === 0) {
72
- base = "master";
73
- }
74
- else {
75
- base = picked_commit_metadata_list[i - 1].metadata.id;
76
- invariant(base, `metadata must be set on previous commit [${i}]`);
77
- }
78
- await cli(`git cherry-pick ${sha}`);
79
- const commit = await CommitMetadata.commit(sha, base);
80
- if (!commit.metadata.id) {
81
- commit.metadata.id = uuid_v4();
82
- await Metadata.write(commit);
83
- }
84
- picked_commit_metadata_list.push(commit);
85
- // always push to origin since github requires commit shas to line up perfectly
86
- console.debug();
87
- console.debug(`Syncing [${commit.metadata.id}] ...`);
88
- await cli(`git push -f origin HEAD:${commit.metadata.id}`);
89
- if (commit.pr_exists) {
90
- // ensure base matches pr in github
91
- await github.pr_base(commit.metadata.id, base);
92
- }
93
- else {
94
- try {
95
- // delete metadata id branch if leftover
96
- await cli(`git branch -D ${commit.metadata.id}`, {
97
- ignoreExitCode: true,
98
- });
99
- // move to temporary branch for creating pr
100
- await cli(`git checkout -b ${commit.metadata.id}`);
101
- // create pr in github
102
- await github.pr_create(commit.metadata.id, base);
103
- }
104
- catch (err) {
105
- console.error("Moving back to temp branch...");
106
- console.error(err);
107
- }
108
- finally {
109
- // move back to temp branch
110
- await cli(`git checkout ${temp_branch_name}`);
111
- // delete metadata id branch if leftover
112
- await cli(`git branch -D ${commit.metadata.id}`, {
113
- ignoreExitCode: true,
114
- });
115
- }
116
- }
117
- }
118
- // after all commits have been cherry-picked and amended
119
- // move the branch pointer to the temporary branch (with the metadata)
120
- await cli(`git branch -f ${args.branch_name} ${temp_branch_name}`);
121
- }
122
- catch (err) {
123
- console.error("Restoring original branch...");
124
- console.error(err);
125
- }
126
- finally {
127
- // always put self back in original branch
128
- await cli(`git checkout ${args.branch_name}`);
129
- // ...and cleanup temporary branch
130
- await cli(`git branch -D ${temp_branch_name}`, { ignoreExitCode: true });
131
- }
132
- // print_table(repo_path, await CommitMetadata.all());
133
- }
134
- const RE = {
135
- // git@github.com:magus/git-multi-diff-playground.git
136
- // https://github.com/magus/git-multi-diff-playground.git
137
- repo_path: /(?<repo_path>[^:^/]+\/[^/]+)\.git/,
138
- };
139
- // function print_table(
140
- // repo_path: string,
141
- // commit_metadata_list: Array<Awaited<ReturnType<typeof CommitMetadata.commit>>>
142
- // ) {
143
- // console.debug();
144
- // for (const args of commit_metadata_list) {
145
- // print_table_row(repo_path, args);
146
- // }
147
- // }
148
- // function print_table_row(
149
- // repo_path: string,
150
- // args: Awaited<ReturnType<typeof CommitMetadata.commit>>
151
- // ) {
152
- // let icon;
153
- // let status;
154
- // if (!args.pr_exists) {
155
- // icon = "🌱";
156
- // status = "NEW";
157
- // } else if (args.pr_dirty) {
158
- // icon = "⚠️";
159
- // status = "OUTDATED";
160
- // } else {
161
- // icon = "✅";
162
- // status = "SYNCED";
163
- // }
164
- // // print clean metadata about this commit / branch
165
- // const parts = [
166
- // icon,
167
- // " ",
168
- // col(status, 10, "left"),
169
- // col(args.message, 80, "left"),
170
- // ];
171
- // if (args.pr?.number) {
172
- // parts.push(` https://github.com/${repo_path}/pull/${args.pr.number}`);
173
- // }
174
- // console.debug(...parts);
175
- // }
176
- // const RE = {
177
- // all_newline: /\n/g,
178
- // // git@github.com:magus/git-multi-diff-playground.git
179
- // // https://github.com/magus/git-multi-diff-playground.git
180
- // repo_path: /(?<repo_path>[^:^/]+\/[^/]+)\.git/,
181
- // };
182
- // function trunc(value: string, length: number) {
183
- // return value.substring(0, length);
184
- // }
185
- // function pad(value: string, length: number, align: "left" | "right") {
186
- // const space_count = Math.max(0, length - value.length);
187
- // const padding = " ".repeat(space_count);
188
- // if (align === "left") {
189
- // return `${value}${padding}`;
190
- // } else {
191
- // return `${padding}${value}`;
192
- // }
193
- // }
194
- // function col(value: string, length: number, align: "left" | "right") {
195
- // let column = value;
196
- // column = column.replace(RE.all_newline, " ");
197
- // column = trunc(column, length);
198
- // column = pad(column, length, align);
199
- // return column;
200
- // }
@@ -1,127 +0,0 @@
1
- import * as React from "react";
2
- import * as Ink from "ink";
3
- import * as CommitMetadata from "../core/CommitMetadata.js";
4
- import * as Metadata from "../core/Metadata.js";
5
- import { cli } from "../core/cli.js";
6
- import * as github from "../core/github.js";
7
- import { invariant } from "../core/invariant.js";
8
- import { short_id } from "../core/short_id.js";
9
- import { Await } from "./Await.js";
10
- import { Brackets } from "./Brackets.js";
11
- import { Store } from "./Store.js";
12
- export function ManualRebase(props) {
13
- return (React.createElement(Await, { fallback: React.createElement(Ink.Text, { color: "yellow" }, "Rebasing commits..."), function: () => run(props) }));
14
- }
15
- async function run(props) {
16
- const state = Store.getState();
17
- const actions = state.actions;
18
- const branch_name = state.branch_name;
19
- const merge_base = state.merge_base;
20
- const commit_map = state.commit_map;
21
- invariant(branch_name, "branch_name must exist");
22
- invariant(merge_base, "merge_base must exist");
23
- invariant(commit_map, "commit_map must exist");
24
- // always listen for SIGINT event and restore git state
25
- process.once("SIGINT", handle_exit);
26
- const temp_branch_name = `${branch_name}_${short_id()}`;
27
- const commit_range = await CommitMetadata.range(commit_map);
28
- // reverse commit list so that we can cherry-pick in order
29
- commit_range.group_list.reverse();
30
- let rebase_merge_base = merge_base;
31
- let rebase_group_index = 0;
32
- for (let i = 0; i < commit_range.group_list.length; i++) {
33
- const group = commit_range.group_list[i];
34
- if (!group.dirty) {
35
- continue;
36
- }
37
- if (i > 0) {
38
- const last_group = commit_range.group_list[i - 1];
39
- const last_commit = last_group.commits[last_group.commits.length - 1];
40
- rebase_merge_base = last_commit.sha;
41
- rebase_group_index = i;
42
- }
43
- break;
44
- }
45
- try {
46
- // create temporary branch based on merge base
47
- await cli(`git checkout -b ${temp_branch_name} ${rebase_merge_base}`);
48
- for (let i = rebase_group_index; i < commit_range.group_list.length; i++) {
49
- const group = commit_range.group_list[i];
50
- invariant(group.base, "group.base must exist");
51
- // cherry-pick and amend commits one by one
52
- for (const commit of group.commits) {
53
- await cli(`git cherry-pick ${commit.sha}`);
54
- if (commit.branch_id !== group.id) {
55
- const new_message = await Metadata.write(commit.message, group.id);
56
- await cli(`git commit --amend -m "${new_message}"`);
57
- }
58
- }
59
- actions.output(React.createElement(Ink.Text, { color: "yellow", wrap: "truncate-end" },
60
- "Syncing ",
61
- React.createElement(Brackets, null, group.pr?.title || group.id),
62
- "..."));
63
- if (!props.skipSync) {
64
- // push to origin since github requires commit shas to line up perfectly
65
- await cli(`git push -f origin HEAD:${group.id}`);
66
- if (group.pr) {
67
- // ensure base matches pr in github
68
- await github.pr_base(group.id, group.base);
69
- }
70
- else {
71
- // delete local group branch if leftover
72
- await cli(`git branch -D ${group.id}`, { ignoreExitCode: true });
73
- // move to temporary branch for creating pr
74
- await cli(`git checkout -b ${group.id}`);
75
- // create pr in github
76
- await github.pr_create(group.id, group.base);
77
- // move back to temp branch
78
- await cli(`git checkout ${temp_branch_name}`);
79
- }
80
- }
81
- }
82
- // after all commits have been cherry-picked and amended
83
- // move the branch pointer to the newly created temporary branch
84
- // now we are in locally in sync with github and on the original branch
85
- await cli(`git branch -f ${branch_name} ${temp_branch_name}`);
86
- restore_git();
87
- actions.set((state) => {
88
- state.step = "post-rebase-status";
89
- });
90
- }
91
- catch (err) {
92
- actions.output(React.createElement(Ink.Text, { color: "#ef4444" }, "Error during rebase."));
93
- if (err instanceof Error) {
94
- actions.debug(React.createElement(Ink.Text, { color: "#ef4444" }, err.message));
95
- }
96
- handle_exit();
97
- }
98
- // cleanup git operations if cancelled during manual rebase
99
- function restore_git() {
100
- // signint handler MUST run synchronously
101
- // trying to use `await cli(...)` here will silently fail since
102
- // all children processes receive the SIGINT signal
103
- const spawn_options = { ignoreExitCode: true };
104
- // always put self back in original branch
105
- cli.sync(`git checkout ${branch_name}`, spawn_options);
106
- // ...and cleanup temporary branch
107
- cli.sync(`git branch -D ${temp_branch_name}`, spawn_options);
108
- if (commit_range) {
109
- // ...and cleanup pr group branches
110
- for (const group of commit_range.group_list) {
111
- cli.sync(`git branch -D ${group.id}`, spawn_options);
112
- }
113
- }
114
- }
115
- function handle_exit() {
116
- actions.output(React.createElement(Ink.Text, { color: "yellow" },
117
- "Restoring ",
118
- React.createElement(Brackets, null, branch_name),
119
- "..."));
120
- restore_git();
121
- actions.output(React.createElement(Ink.Text, { color: "yellow" },
122
- "Restored ",
123
- React.createElement(Brackets, null, branch_name),
124
- "."));
125
- actions.exit(5);
126
- }
127
- }
@@ -1,220 +0,0 @@
1
- import * as React from "react";
2
- import fs from "node:fs";
3
- import * as Ink from "ink";
4
- import * as CommitMetadata from "../core/CommitMetadata.js";
5
- import * as Metadata from "../core/Metadata.js";
6
- import * as StackSummaryTable from "../core/StackSummaryTable.js";
7
- import { cli } from "../core/cli.js";
8
- import { colors } from "../core/colors.js";
9
- import * as github from "../core/github.js";
10
- import { invariant } from "../core/invariant.js";
11
- import { short_id } from "../core/short_id.js";
12
- import { Await } from "./Await.js";
13
- import { Brackets } from "./Brackets.js";
14
- import { FormatText } from "./FormatText.js";
15
- import { Store } from "./Store.js";
16
- export function ManualRebase(props) {
17
- return (React.createElement(Await, { fallback: React.createElement(Ink.Text, { color: colors.yellow }, "Rebasing commits..."), function: () => run(props) }));
18
- }
19
- async function run(props) {
20
- const state = Store.getState();
21
- const actions = state.actions;
22
- const argv = state.argv;
23
- const branch_name = state.branch_name;
24
- const merge_base = state.merge_base;
25
- const commit_map = state.commit_map;
26
- const cwd = state.cwd;
27
- const repo_root = state.repo_root;
28
- invariant(argv, "argv must exist");
29
- invariant(branch_name, "branch_name must exist");
30
- invariant(merge_base, "merge_base must exist");
31
- invariant(commit_map, "commit_map must exist");
32
- invariant(cwd, "cwd must exist");
33
- invariant(repo_root, "repo_root must exist");
34
- // always listen for SIGINT event and restore git state
35
- process.once("SIGINT", handle_exit);
36
- const temp_branch_name = `${branch_name}_${short_id()}`;
37
- const commit_range = await CommitMetadata.range(commit_map);
38
- // reverse commit list so that we can cherry-pick in order
39
- commit_range.group_list.reverse();
40
- let rebase_merge_base = merge_base;
41
- let rebase_group_index = 0;
42
- for (let i = 0; i < commit_range.group_list.length; i++) {
43
- const group = commit_range.group_list[i];
44
- if (!group.dirty) {
45
- continue;
46
- }
47
- if (i > 0) {
48
- const last_group = commit_range.group_list[i - 1];
49
- const last_commit = last_group.commits[last_group.commits.length - 1];
50
- rebase_merge_base = last_commit.sha;
51
- rebase_group_index = i;
52
- }
53
- break;
54
- }
55
- try {
56
- // must perform rebase from repo root for applying git patch
57
- process.chdir(repo_root);
58
- await cli(`pwd`);
59
- // create temporary branch based on merge base
60
- await cli(`git checkout -b ${temp_branch_name} ${rebase_merge_base}`);
61
- const pr_url_list = commit_range.group_list.map(get_group_url);
62
- for (let i = rebase_group_index; i < commit_range.group_list.length; i++) {
63
- const group = commit_range.group_list[i];
64
- invariant(group.base, "group.base must exist");
65
- actions.output(React.createElement(FormatText, { wrapper: React.createElement(Ink.Text, { color: colors.yellow, wrap: "truncate-end" }), message: "Rebasing {group}\u2026", values: {
66
- group: (React.createElement(Brackets, null, group.pr?.title || group.title || group.id)),
67
- } }));
68
- const selected_url = get_group_url(group);
69
- // cherry-pick and amend commits one by one
70
- for (const commit of group.commits) {
71
- // ensure clean base to avoid conflicts when applying patch
72
- await cli(`git clean -fd`);
73
- // create, apply and cleanup patch
74
- await cli(`git format-patch -1 ${commit.sha} --stdout > ${PATCH_FILE}`);
75
- await cli(`git apply ${PATCH_FILE}`);
76
- await cli(`rm ${PATCH_FILE}`);
77
- // add all changes to stage
78
- await cli(`git add --all`);
79
- const new_message = await Metadata.write(commit.full_message, group.id);
80
- const git_commit_comand = [`git commit -m "${new_message}"`];
81
- if (argv.verify === false) {
82
- git_commit_comand.push("--no-verify");
83
- }
84
- await cli(git_commit_comand);
85
- }
86
- actions.output(React.createElement(FormatText, { wrapper: React.createElement(Ink.Text, { color: colors.yellow, wrap: "truncate-end" }), message: "Syncing {group}\u2026", values: {
87
- group: (React.createElement(Brackets, null, group.pr?.title || group.title || group.id)),
88
- } }));
89
- if (!props.skipSync) {
90
- // push to origin since github requires commit shas to line up perfectly
91
- const git_push_command = [`git push -f origin HEAD:${group.id}`];
92
- if (argv.verify === false) {
93
- git_push_command.push("--no-verify");
94
- }
95
- await cli(git_push_command);
96
- if (group.pr) {
97
- // ensure base matches pr in github
98
- await github.pr_edit({
99
- branch: group.id,
100
- base: group.base,
101
- body: StackSummaryTable.write({
102
- body: group.pr.body,
103
- pr_url_list,
104
- selected_url,
105
- }),
106
- });
107
- }
108
- else {
109
- // delete local group branch if leftover
110
- await cli(`git branch -D ${group.id}`, { ignoreExitCode: true });
111
- // move to temporary branch for creating pr
112
- await cli(`git checkout -b ${group.id}`);
113
- // create pr in github
114
- const pr_url = await github.pr_create({
115
- branch: group.id,
116
- base: group.base,
117
- title: group.title,
118
- body: "",
119
- });
120
- if (!pr_url) {
121
- throw new Error("unable to create pr");
122
- }
123
- // update pr_url_list with created pr_url
124
- for (let i = 0; i < pr_url_list.length; i++) {
125
- const url = pr_url_list[i];
126
- if (url === selected_url) {
127
- pr_url_list[i] = pr_url;
128
- }
129
- }
130
- // move back to temp branch
131
- await cli(`git checkout ${temp_branch_name}`);
132
- }
133
- }
134
- }
135
- // finally, ensure all prs have the updated stack table from updated pr_url_list
136
- for (let i = 0; i < commit_range.group_list.length; i++) {
137
- const group = commit_range.group_list[i];
138
- // use the updated pr_url_list to get the actual selected_url
139
- const selected_url = pr_url_list[i];
140
- invariant(group.base, "group.base must exist");
141
- const body = group.pr?.body || "";
142
- const update_body = StackSummaryTable.write({
143
- body,
144
- pr_url_list,
145
- selected_url,
146
- });
147
- if (update_body === body) {
148
- actions.debug(`Skipping body update for ${selected_url}`);
149
- }
150
- else {
151
- actions.debug(`Update body for ${selected_url}`);
152
- await github.pr_edit({
153
- branch: group.id,
154
- base: group.base,
155
- body: update_body,
156
- });
157
- }
158
- }
159
- // after all commits have been cherry-picked and amended
160
- // move the branch pointer to the newly created temporary branch
161
- // now we are in locally in sync with github and on the original branch
162
- await cli(`git branch -f ${branch_name} ${temp_branch_name}`);
163
- restore_git();
164
- actions.set((state) => {
165
- state.step = "post-rebase-status";
166
- });
167
- }
168
- catch (err) {
169
- actions.error("Unable to rebase.");
170
- if (err instanceof Error) {
171
- if (actions.isDebug()) {
172
- actions.error(err.message);
173
- }
174
- }
175
- handle_exit();
176
- }
177
- // cleanup git operations if cancelled during manual rebase
178
- function restore_git() {
179
- // signint handler MUST run synchronously
180
- // trying to use `await cli(...)` here will silently fail since
181
- // all children processes receive the SIGINT signal
182
- const spawn_options = { ignoreExitCode: true };
183
- // always clean up any patch files
184
- cli.sync(`rm ${PATCH_FILE}`, spawn_options);
185
- // always hard reset and clean to allow subsequent checkout
186
- // if there are files checkout will fail and cascade fail subsequent commands
187
- cli.sync(`git reset --hard`, spawn_options);
188
- cli.sync(`git clean -df`, spawn_options);
189
- // always put self back in original branch
190
- cli.sync(`git checkout ${branch_name}`, spawn_options);
191
- // ...and cleanup temporary branch
192
- cli.sync(`git branch -D ${temp_branch_name}`, spawn_options);
193
- if (commit_range) {
194
- // ...and cleanup pr group branches
195
- for (const group of commit_range.group_list) {
196
- cli.sync(`git branch -D ${group.id}`, spawn_options);
197
- }
198
- }
199
- // restore back to original dir
200
- invariant(cwd, "cwd must exist");
201
- if (fs.existsSync(cwd)) {
202
- process.chdir(cwd);
203
- }
204
- cli.sync(`pwd`, spawn_options);
205
- }
206
- function handle_exit() {
207
- actions.output(React.createElement(Ink.Text, { color: colors.yellow },
208
- "Restoring ",
209
- React.createElement(Brackets, null, branch_name),
210
- "..."));
211
- restore_git();
212
- actions.output(React.createElement(Ink.Text, { color: colors.yellow },
213
- "Restored ",
214
- React.createElement(Brackets, null, branch_name),
215
- "."));
216
- actions.exit(5);
217
- }
218
- }
219
- const get_group_url = (group) => group.pr?.url || group.id;
220
- const PATCH_FILE = "git-stack-cli-patch.patch";
@@ -1,76 +0,0 @@
1
- import * as React from "react";
2
- import * as Ink from "ink";
3
- import { clamp } from "../core/clamp.js";
4
- export function MultiSelect(props) {
5
- const [selected, select] = React.useReducer((state, value) => {
6
- const next = new Set(state);
7
- if (next.has(value)) {
8
- next.delete(value);
9
- }
10
- else {
11
- next.add(value);
12
- }
13
- return next;
14
- }, new Set());
15
- // clamp index to keep in item range
16
- const [index, set_index] = React.useReducer((_, value) => {
17
- return clamp(value, 0, props.items.length - 1);
18
- }, 0);
19
- React.useEffect(() => {
20
- const item = props.items[index];
21
- const selected_list = Array.from(selected);
22
- const list = selected_list.map((index) => props.items[index]);
23
- props.onSelect(item, list);
24
- }, [selected]);
25
- Ink.useInput((_input, key) => {
26
- if (key.return) {
27
- return select(index);
28
- }
29
- if (key.upArrow) {
30
- return set_index(index - 1);
31
- }
32
- if (key.downArrow) {
33
- return set_index(index + 1);
34
- }
35
- });
36
- return (React.createElement(Ink.Box, { flexDirection: "column" }, props.items.map((item, i) => {
37
- const active = i === index;
38
- return (React.createElement(ItemRow, { key: item.label, label: item.label, active: active, selected: selected.has(i) }));
39
- })));
40
- }
41
- function Radio(props) {
42
- let display;
43
- let color;
44
- if (props.selected) {
45
- // display = "✓";
46
- display = "◉";
47
- color = "green";
48
- }
49
- else {
50
- // display = " ";
51
- display = "◯";
52
- color = "";
53
- }
54
- return (React.createElement(Ink.Text, { bold: props.selected, color: color }, display));
55
- }
56
- function ItemRow(props) {
57
- let color;
58
- let underline;
59
- let dimColor;
60
- if (props.active) {
61
- color = "#38bdf8";
62
- underline = true;
63
- }
64
- else if (props.selected) {
65
- // color = "";
66
- dimColor = false;
67
- }
68
- else {
69
- // color = "gray";
70
- dimColor = true;
71
- }
72
- return (React.createElement(Ink.Box, { flexDirection: "row", gap: 1 },
73
- React.createElement(Radio, { selected: props.selected }),
74
- React.createElement(Ink.Box, null,
75
- React.createElement(Ink.Text, { bold: props.selected, underline: underline, color: color, dimColor: dimColor, wrap: "truncate-end" }, props.label))));
76
- }