git-stack-cli 1.14.0 → 1.15.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/dist/cjs/index.cjs +173 -110
- package/package.json +1 -1
- package/src/app/App.tsx +3 -0
- package/src/app/Debug.tsx +2 -1
- package/src/app/DirtyCheck.tsx +37 -26
- package/src/app/Exit.tsx +45 -8
- package/src/app/HandleCtrlCSigint.tsx +47 -0
- package/src/app/ManualRebase.tsx +12 -30
- package/src/app/Store.tsx +24 -2
- package/src/app/SyncGithub.tsx +12 -30
- package/src/commands/Fixup.tsx +2 -2
- package/src/commands/Rebase.tsx +12 -30
- package/src/core/GitReviseTodo.ts +4 -0
- package/src/core/github.tsx +8 -1
- package/src/core/pretty_json.ts +12 -0
- package/src/index.tsx +20 -5
package/dist/cjs/index.cjs
CHANGED
|
@@ -18487,15 +18487,41 @@ const immerImpl = (initializer) => (set, get, store) => {
|
|
|
18487
18487
|
};
|
|
18488
18488
|
const immer = immerImpl;
|
|
18489
18489
|
|
|
18490
|
+
async function sleep(time) {
|
|
18491
|
+
return new Promise((resolve) => setTimeout(resolve, time));
|
|
18492
|
+
}
|
|
18493
|
+
|
|
18490
18494
|
function Exit(props) {
|
|
18491
|
-
const actions = Store.useActions();
|
|
18492
18495
|
reactExports.useEffect(() => {
|
|
18493
|
-
|
|
18494
|
-
|
|
18496
|
+
// immediately handle exit on mount
|
|
18497
|
+
handle_exit().catch((err) => {
|
|
18498
|
+
// eslint-disable-next-line no-console
|
|
18499
|
+
console.error(err);
|
|
18500
|
+
});
|
|
18501
|
+
async function handle_exit() {
|
|
18502
|
+
const state = Store.getState();
|
|
18503
|
+
const actions = state.actions;
|
|
18504
|
+
actions.debug(`[Exit] handle_exit ${JSON.stringify(props)}`);
|
|
18505
|
+
let exit_code = props.code;
|
|
18506
|
+
// run abort_handler if it exists
|
|
18507
|
+
if (state.abort_handler) {
|
|
18508
|
+
exit_code = await state.abort_handler();
|
|
18509
|
+
}
|
|
18510
|
+
// restore git stash if necessary
|
|
18511
|
+
if (state.is_dirty_check_stash) {
|
|
18512
|
+
await cli("git stash pop");
|
|
18513
|
+
actions.output(reactExports.createElement(Text, { color: colors.green }, "\u2705 Changes restored from stash"));
|
|
18514
|
+
}
|
|
18515
|
+
// ensure output has a chance to render
|
|
18516
|
+
await sleep(1);
|
|
18517
|
+
// finally handle the actual app and process exit
|
|
18518
|
+
if (props.clear) {
|
|
18519
|
+
actions.clear();
|
|
18520
|
+
}
|
|
18521
|
+
actions.unmount();
|
|
18522
|
+
process.exitCode = exit_code;
|
|
18523
|
+
process.exit();
|
|
18495
18524
|
}
|
|
18496
|
-
actions.unmount();
|
|
18497
|
-
process.exitCode = props.code;
|
|
18498
|
-
process.exit();
|
|
18499
18525
|
}, [props.clear, props.code]);
|
|
18500
18526
|
return null;
|
|
18501
18527
|
}
|
|
@@ -26197,6 +26223,10 @@ function LogTimestamp() {
|
|
|
26197
26223
|
return (reactExports.createElement(Text, { dimColor: true }, DateTime.now().toFormat("[yyyy-MM-dd HH:mm:ss.SSS] ")));
|
|
26198
26224
|
}
|
|
26199
26225
|
|
|
26226
|
+
function pretty_json(input) {
|
|
26227
|
+
return JSON.stringify(input, null, 2);
|
|
26228
|
+
}
|
|
26229
|
+
|
|
26200
26230
|
const BaseStore = createStore()(immer((set, get) => ({
|
|
26201
26231
|
// set immediately in `index.tsx` so no `null` scenario
|
|
26202
26232
|
process_argv: [],
|
|
@@ -26214,6 +26244,8 @@ const BaseStore = createStore()(immer((set, get) => ({
|
|
|
26214
26244
|
pr_templates: [],
|
|
26215
26245
|
pr_template_body: null,
|
|
26216
26246
|
sync_github: null,
|
|
26247
|
+
is_dirty_check_stash: false,
|
|
26248
|
+
abort_handler: null,
|
|
26217
26249
|
step: "loading",
|
|
26218
26250
|
output: [],
|
|
26219
26251
|
pending_output: {},
|
|
@@ -26239,7 +26271,7 @@ const BaseStore = createStore()(immer((set, get) => ({
|
|
|
26239
26271
|
},
|
|
26240
26272
|
json(value) {
|
|
26241
26273
|
set((state) => {
|
|
26242
|
-
const node =
|
|
26274
|
+
const node = pretty_json(value);
|
|
26243
26275
|
state.mutate.output(state, { node });
|
|
26244
26276
|
});
|
|
26245
26277
|
},
|
|
@@ -26276,6 +26308,16 @@ const BaseStore = createStore()(immer((set, get) => ({
|
|
|
26276
26308
|
state.pr = {};
|
|
26277
26309
|
});
|
|
26278
26310
|
},
|
|
26311
|
+
register_abort_handler(abort_handler) {
|
|
26312
|
+
set((state) => {
|
|
26313
|
+
state.abort_handler = abort_handler;
|
|
26314
|
+
});
|
|
26315
|
+
},
|
|
26316
|
+
unregister_abort_handler() {
|
|
26317
|
+
set((state) => {
|
|
26318
|
+
state.abort_handler = null;
|
|
26319
|
+
});
|
|
26320
|
+
},
|
|
26279
26321
|
set(setter) {
|
|
26280
26322
|
set((state) => {
|
|
26281
26323
|
setter(state);
|
|
@@ -26561,10 +26603,6 @@ function semver_compare(version_a, version_b) {
|
|
|
26561
26603
|
return 0;
|
|
26562
26604
|
}
|
|
26563
26605
|
|
|
26564
|
-
async function sleep(time) {
|
|
26565
|
-
return new Promise((resolve) => setTimeout(resolve, time));
|
|
26566
|
-
}
|
|
26567
|
-
|
|
26568
26606
|
function reducer$4(state, patch) {
|
|
26569
26607
|
return { ...state, ...patch };
|
|
26570
26608
|
}
|
|
@@ -26898,7 +26936,7 @@ function Debug() {
|
|
|
26898
26936
|
const output_file = path.join(state.cwd, "git-stack-state.json");
|
|
26899
26937
|
await safe_rm(output_file);
|
|
26900
26938
|
const serialized = serialize(state);
|
|
26901
|
-
const content =
|
|
26939
|
+
const content = pretty_json(serialized);
|
|
26902
26940
|
await fs$1.writeFile(output_file, content);
|
|
26903
26941
|
}
|
|
26904
26942
|
}, [argv, state]);
|
|
@@ -29807,14 +29845,19 @@ async function pr_create(args) {
|
|
|
29807
29845
|
}
|
|
29808
29846
|
async function pr_edit(args) {
|
|
29809
29847
|
const command_parts = [`gh pr edit ${args.branch} --base ${args.base}`];
|
|
29848
|
+
let body_file;
|
|
29810
29849
|
if (args.body) {
|
|
29811
|
-
|
|
29850
|
+
body_file = await write_body_file(args);
|
|
29812
29851
|
command_parts.push(`--body-file="${body_file}"`);
|
|
29813
29852
|
}
|
|
29814
29853
|
const cli_result = await cli(command_parts);
|
|
29815
29854
|
if (cli_result.code !== 0) {
|
|
29816
29855
|
handle_error(cli_result.output);
|
|
29817
29856
|
}
|
|
29857
|
+
// cleanup body_file
|
|
29858
|
+
if (body_file) {
|
|
29859
|
+
await safe_rm(body_file);
|
|
29860
|
+
}
|
|
29818
29861
|
}
|
|
29819
29862
|
async function pr_draft(args) {
|
|
29820
29863
|
// https://cli.github.com/manual/gh_api
|
|
@@ -30186,6 +30229,8 @@ echo "$GIT_REVISE_TODO" > "$git_revise_todo_path"
|
|
|
30186
30229
|
// change to pipe to see output temporarily
|
|
30187
30230
|
// https://github.com/magus/git-stack-cli/commit/f9f10e3ac3cd9a35ee75d3e0851a48391967a23f
|
|
30188
30231
|
await cli(command, { stdio: ["ignore", "ignore", "ignore"] });
|
|
30232
|
+
// cleanup tmp_git_sequence_editor_path
|
|
30233
|
+
await safe_rm(tmp_git_sequence_editor_path);
|
|
30189
30234
|
};
|
|
30190
30235
|
|
|
30191
30236
|
function reducer$2(state, patch) {
|
|
@@ -30302,19 +30347,23 @@ function DirtyCheck(props) {
|
|
|
30302
30347
|
case "done":
|
|
30303
30348
|
return props.children;
|
|
30304
30349
|
case "prompt":
|
|
30305
|
-
return (reactExports.createElement(
|
|
30306
|
-
|
|
30307
|
-
|
|
30308
|
-
|
|
30309
|
-
|
|
30310
|
-
|
|
30311
|
-
|
|
30312
|
-
|
|
30313
|
-
|
|
30314
|
-
|
|
30315
|
-
|
|
30316
|
-
|
|
30317
|
-
|
|
30350
|
+
return (reactExports.createElement(Box, { flexDirection: "column" },
|
|
30351
|
+
reactExports.createElement(FormatText, { wrapper: reactExports.createElement(Text, { color: colors.yellow }), message: "\u26A0\uFE0F Uncommitted changes detected. {git_stack} needs a clean working tree.", values: {
|
|
30352
|
+
git: reactExports.createElement(Command, null, "git"),
|
|
30353
|
+
git_stack: reactExports.createElement(Command, null, "git stack"),
|
|
30354
|
+
} }),
|
|
30355
|
+
reactExports.createElement(YesNoPrompt, { message: reactExports.createElement(FormatText, { wrapper: reactExports.createElement(Text, { color: colors.yellow }), message: "{git_stash} changes to proceed?", values: {
|
|
30356
|
+
git_stash: reactExports.createElement(Command, null, "git stash"),
|
|
30357
|
+
} }), onYes: async () => {
|
|
30358
|
+
await cli("git stash --include-untracked");
|
|
30359
|
+
actions.output(reactExports.createElement(Text, null, "\uD83D\uDCE6 Changes saved to stash"));
|
|
30360
|
+
actions.set((state) => {
|
|
30361
|
+
state.is_dirty_check_stash = true;
|
|
30362
|
+
});
|
|
30363
|
+
patch({ status: "done" });
|
|
30364
|
+
}, onNo: async () => {
|
|
30365
|
+
actions.exit(0);
|
|
30366
|
+
} })));
|
|
30318
30367
|
default:
|
|
30319
30368
|
return (reactExports.createElement(Await, { function: run, fallback: reactExports.createElement(Text, { color: colors.yellow },
|
|
30320
30369
|
"Ensuring ",
|
|
@@ -30325,8 +30374,12 @@ function DirtyCheck(props) {
|
|
|
30325
30374
|
const actions = Store.getState().actions;
|
|
30326
30375
|
try {
|
|
30327
30376
|
const git_dirty = (await cli(`git status --porcelain`)).stdout;
|
|
30328
|
-
|
|
30329
|
-
|
|
30377
|
+
if (!git_dirty) {
|
|
30378
|
+
patch({ status: "done" });
|
|
30379
|
+
}
|
|
30380
|
+
else {
|
|
30381
|
+
patch({ status: "prompt" });
|
|
30382
|
+
}
|
|
30330
30383
|
}
|
|
30331
30384
|
catch (err) {
|
|
30332
30385
|
actions.error("Must be run from within a git repository.");
|
|
@@ -30476,6 +30529,32 @@ async function run$9() {
|
|
|
30476
30529
|
reactExports.createElement(Text, { bold: true, color: colors.yellow }, time_until))));
|
|
30477
30530
|
}
|
|
30478
30531
|
|
|
30532
|
+
function HandleCtrlCSigint() {
|
|
30533
|
+
const actions = Store.useActions();
|
|
30534
|
+
const [exiting, set_exiting] = reactExports.useState(false);
|
|
30535
|
+
useInput((input, key) => {
|
|
30536
|
+
handle_input().catch((err) => {
|
|
30537
|
+
// eslint-disable-next-line no-console
|
|
30538
|
+
console.error(err);
|
|
30539
|
+
});
|
|
30540
|
+
async function handle_input() {
|
|
30541
|
+
if (input === "c" && key.ctrl) {
|
|
30542
|
+
actions.clear();
|
|
30543
|
+
actions.output(reactExports.createElement(Text, { color: colors.red },
|
|
30544
|
+
reactExports.createElement(FormatText, { message: "\uD83D\uDEA8 Ctrl+C detected" })));
|
|
30545
|
+
set_exiting(true);
|
|
30546
|
+
await sleep(1);
|
|
30547
|
+
actions.exit(235);
|
|
30548
|
+
}
|
|
30549
|
+
}
|
|
30550
|
+
});
|
|
30551
|
+
if (exiting) {
|
|
30552
|
+
return (reactExports.createElement(Text, { color: colors.red },
|
|
30553
|
+
reactExports.createElement(FormatText, { message: "\uD83D\uDEA8 Exiting\u2026" })));
|
|
30554
|
+
}
|
|
30555
|
+
return null;
|
|
30556
|
+
}
|
|
30557
|
+
|
|
30479
30558
|
function LocalCommitStatus(props) {
|
|
30480
30559
|
const argv = Store.useState((state) => state.argv);
|
|
30481
30560
|
const fallback = (reactExports.createElement(Text, { color: colors.yellow }, "Fetching PR status from Github\u2026"));
|
|
@@ -30572,21 +30651,9 @@ function encode(value) {
|
|
|
30572
30651
|
}
|
|
30573
30652
|
|
|
30574
30653
|
function Rebase() {
|
|
30575
|
-
|
|
30576
|
-
reactExports.useEffect(function listen_sigint() {
|
|
30577
|
-
process.once("SIGINT", sigint_handler);
|
|
30578
|
-
return function cleanup() {
|
|
30579
|
-
process.removeListener("SIGINT", sigint_handler);
|
|
30580
|
-
};
|
|
30581
|
-
function sigint_handler() {
|
|
30582
|
-
abort_handler.current();
|
|
30583
|
-
}
|
|
30584
|
-
}, []);
|
|
30585
|
-
return (reactExports.createElement(Await, { fallback: reactExports.createElement(Text, { color: colors.yellow }, "Rebasing commits\u2026"), function: async function () {
|
|
30586
|
-
await Rebase.run({ abort_handler });
|
|
30587
|
-
} }));
|
|
30654
|
+
return (reactExports.createElement(Await, { fallback: reactExports.createElement(Text, { color: colors.yellow }, "Rebasing commits\u2026"), function: Rebase.run }));
|
|
30588
30655
|
}
|
|
30589
|
-
Rebase.run = async function run(
|
|
30656
|
+
Rebase.run = async function run() {
|
|
30590
30657
|
const state = Store.getState();
|
|
30591
30658
|
const actions = state.actions;
|
|
30592
30659
|
const branch_name = state.branch_name;
|
|
@@ -30597,11 +30664,12 @@ Rebase.run = async function run(args) {
|
|
|
30597
30664
|
invariant(branch_name, "branch_name must exist");
|
|
30598
30665
|
invariant(commit_range, "commit_range must exist");
|
|
30599
30666
|
invariant(repo_root, "repo_root must exist");
|
|
30600
|
-
//
|
|
30601
|
-
|
|
30667
|
+
// immediately register abort_handler in case of ctrl+c exit
|
|
30668
|
+
actions.register_abort_handler(async function abort_rebase() {
|
|
30602
30669
|
actions.output(reactExports.createElement(Text, { color: colors.red }, "\uD83D\uDEA8 Abort"));
|
|
30603
|
-
handle_exit(
|
|
30604
|
-
|
|
30670
|
+
handle_exit();
|
|
30671
|
+
return 19;
|
|
30672
|
+
});
|
|
30605
30673
|
const temp_branch_name = `${branch_name}_${short_id()}`;
|
|
30606
30674
|
try {
|
|
30607
30675
|
// actions.debug(`commit_range=${JSON.stringify(commit_range, null, 2)}`);
|
|
@@ -30652,6 +30720,7 @@ Rebase.run = async function run(args) {
|
|
|
30652
30720
|
branch_name: reactExports.createElement(Brackets, null, branch_name),
|
|
30653
30721
|
origin_branch: reactExports.createElement(Brackets, null, `origin/${master_branch}`),
|
|
30654
30722
|
} }));
|
|
30723
|
+
actions.unregister_abort_handler();
|
|
30655
30724
|
actions.set((state) => {
|
|
30656
30725
|
state.commit_range = next_commit_range;
|
|
30657
30726
|
state.step = "status";
|
|
@@ -30664,7 +30733,8 @@ Rebase.run = async function run(args) {
|
|
|
30664
30733
|
actions.error(err.message);
|
|
30665
30734
|
}
|
|
30666
30735
|
}
|
|
30667
|
-
handle_exit(
|
|
30736
|
+
handle_exit();
|
|
30737
|
+
actions.exit(20);
|
|
30668
30738
|
}
|
|
30669
30739
|
// cleanup git operations if cancelled during manual rebase
|
|
30670
30740
|
function restore_git() {
|
|
@@ -30686,7 +30756,7 @@ Rebase.run = async function run(args) {
|
|
|
30686
30756
|
}
|
|
30687
30757
|
cli.sync(`pwd`, spawn_options);
|
|
30688
30758
|
}
|
|
30689
|
-
function handle_exit(
|
|
30759
|
+
function handle_exit() {
|
|
30690
30760
|
actions.output(reactExports.createElement(Text, { color: colors.yellow },
|
|
30691
30761
|
"Restoring ",
|
|
30692
30762
|
reactExports.createElement(Brackets, null, branch_name),
|
|
@@ -30696,7 +30766,6 @@ Rebase.run = async function run(args) {
|
|
|
30696
30766
|
"Restored ",
|
|
30697
30767
|
reactExports.createElement(Brackets, null, branch_name),
|
|
30698
30768
|
"."));
|
|
30699
|
-
actions.exit(code);
|
|
30700
30769
|
}
|
|
30701
30770
|
};
|
|
30702
30771
|
|
|
@@ -30705,21 +30774,9 @@ function LocalMergeRebase() {
|
|
|
30705
30774
|
}
|
|
30706
30775
|
|
|
30707
30776
|
function ManualRebase() {
|
|
30708
|
-
|
|
30709
|
-
reactExports.useEffect(function listen_sigint() {
|
|
30710
|
-
process.once("SIGINT", sigint_handler);
|
|
30711
|
-
return function cleanup() {
|
|
30712
|
-
process.removeListener("SIGINT", sigint_handler);
|
|
30713
|
-
};
|
|
30714
|
-
async function sigint_handler() {
|
|
30715
|
-
abort_handler.current();
|
|
30716
|
-
}
|
|
30717
|
-
}, []);
|
|
30718
|
-
return (reactExports.createElement(Await, { fallback: reactExports.createElement(Text, { color: colors.yellow }, "Rebasing commits\u2026"), function: async function () {
|
|
30719
|
-
await run$7({ abort_handler });
|
|
30720
|
-
} }));
|
|
30777
|
+
return (reactExports.createElement(Await, { fallback: reactExports.createElement(Text, { color: colors.yellow }, "Rebasing commits\u2026"), function: run$7 }));
|
|
30721
30778
|
}
|
|
30722
|
-
async function run$7(
|
|
30779
|
+
async function run$7() {
|
|
30723
30780
|
const state = Store.getState();
|
|
30724
30781
|
const actions = state.actions;
|
|
30725
30782
|
const argv = state.argv;
|
|
@@ -30731,11 +30788,12 @@ async function run$7(args) {
|
|
|
30731
30788
|
invariant(branch_name, "branch_name must exist");
|
|
30732
30789
|
invariant(commit_map, "commit_map must exist");
|
|
30733
30790
|
invariant(repo_root, "repo_root must exist");
|
|
30734
|
-
//
|
|
30735
|
-
|
|
30791
|
+
// immediately register abort_handler in case of ctrl+c exit
|
|
30792
|
+
actions.register_abort_handler(async function abort_manual_rebase() {
|
|
30736
30793
|
actions.output(reactExports.createElement(Text, { color: colors.red }, "\uD83D\uDEA8 Abort"));
|
|
30737
|
-
handle_exit(
|
|
30738
|
-
|
|
30794
|
+
handle_exit();
|
|
30795
|
+
return 15;
|
|
30796
|
+
});
|
|
30739
30797
|
const temp_branch_name = `${branch_name}_${short_id()}`;
|
|
30740
30798
|
try {
|
|
30741
30799
|
// get latest merge_base relative to local master
|
|
@@ -30790,6 +30848,7 @@ async function run$7(args) {
|
|
|
30790
30848
|
// of original branch to the newly created temporary branch
|
|
30791
30849
|
await cli(`git branch -f ${branch_name} ${temp_branch_name}`);
|
|
30792
30850
|
restore_git();
|
|
30851
|
+
actions.unregister_abort_handler();
|
|
30793
30852
|
if (argv.sync) {
|
|
30794
30853
|
actions.set((state) => {
|
|
30795
30854
|
state.step = "sync-github";
|
|
@@ -30810,7 +30869,8 @@ async function run$7(args) {
|
|
|
30810
30869
|
if (!argv.verbose) {
|
|
30811
30870
|
actions.error("Try again with `--verbose` to see more information.");
|
|
30812
30871
|
}
|
|
30813
|
-
handle_exit(
|
|
30872
|
+
handle_exit();
|
|
30873
|
+
actions.exit(16);
|
|
30814
30874
|
}
|
|
30815
30875
|
// cleanup git operations if cancelled during manual rebase
|
|
30816
30876
|
function restore_git() {
|
|
@@ -30832,7 +30892,7 @@ async function run$7(args) {
|
|
|
30832
30892
|
}
|
|
30833
30893
|
cli.sync(`pwd`, spawn_options);
|
|
30834
30894
|
}
|
|
30835
|
-
function handle_exit(
|
|
30895
|
+
function handle_exit() {
|
|
30836
30896
|
actions.output(reactExports.createElement(Text, { color: colors.yellow },
|
|
30837
30897
|
"Restoring ",
|
|
30838
30898
|
reactExports.createElement(Brackets, null, branch_name),
|
|
@@ -30842,7 +30902,6 @@ async function run$7(args) {
|
|
|
30842
30902
|
"Restored ",
|
|
30843
30903
|
reactExports.createElement(Brackets, null, branch_name),
|
|
30844
30904
|
"."));
|
|
30845
|
-
actions.exit(code);
|
|
30846
30905
|
}
|
|
30847
30906
|
}
|
|
30848
30907
|
|
|
@@ -31757,21 +31816,9 @@ const RE = {
|
|
|
31757
31816
|
};
|
|
31758
31817
|
|
|
31759
31818
|
function SyncGithub() {
|
|
31760
|
-
|
|
31761
|
-
reactExports.useEffect(function listen_sigint() {
|
|
31762
|
-
process.once("SIGINT", sigint_handler);
|
|
31763
|
-
return function cleanup() {
|
|
31764
|
-
process.removeListener("SIGINT", sigint_handler);
|
|
31765
|
-
};
|
|
31766
|
-
function sigint_handler() {
|
|
31767
|
-
abort_handler.current();
|
|
31768
|
-
}
|
|
31769
|
-
}, []);
|
|
31770
|
-
return (reactExports.createElement(Await, { fallback: reactExports.createElement(Text, { color: colors.yellow }, "Syncing\u2026"), function: async function () {
|
|
31771
|
-
await run$3({ abort_handler });
|
|
31772
|
-
} }));
|
|
31819
|
+
return (reactExports.createElement(Await, { fallback: reactExports.createElement(Text, { color: colors.yellow }, "Syncing\u2026"), function: run$3 }));
|
|
31773
31820
|
}
|
|
31774
|
-
async function run$3(
|
|
31821
|
+
async function run$3() {
|
|
31775
31822
|
const state = Store.getState();
|
|
31776
31823
|
const actions = state.actions;
|
|
31777
31824
|
const argv = state.argv;
|
|
@@ -31786,11 +31833,12 @@ async function run$3(args) {
|
|
|
31786
31833
|
invariant(sync_github, "sync_github must exist");
|
|
31787
31834
|
const commit_range = sync_github.commit_range;
|
|
31788
31835
|
const rebase_group_index = sync_github.rebase_group_index;
|
|
31789
|
-
//
|
|
31790
|
-
|
|
31836
|
+
// immediately register abort_handler in case of ctrl+c exit
|
|
31837
|
+
actions.register_abort_handler(async function abort_sync_github() {
|
|
31791
31838
|
actions.output(reactExports.createElement(Text, { color: colors.red }, "\uD83D\uDEA8 Abort"));
|
|
31792
|
-
handle_exit(
|
|
31793
|
-
|
|
31839
|
+
handle_exit();
|
|
31840
|
+
return 17;
|
|
31841
|
+
});
|
|
31794
31842
|
let DEFAULT_PR_BODY = "";
|
|
31795
31843
|
if (state.pr_template_body) {
|
|
31796
31844
|
DEFAULT_PR_BODY = state.pr_template_body;
|
|
@@ -31859,6 +31907,7 @@ async function run$3(args) {
|
|
|
31859
31907
|
update_pr_body_tasks.push(task);
|
|
31860
31908
|
}
|
|
31861
31909
|
await Promise.all(update_pr_body_tasks);
|
|
31910
|
+
actions.unregister_abort_handler();
|
|
31862
31911
|
actions.set((state) => {
|
|
31863
31912
|
state.step = "post-rebase-status";
|
|
31864
31913
|
});
|
|
@@ -31871,7 +31920,8 @@ async function run$3(args) {
|
|
|
31871
31920
|
if (!argv.verbose) {
|
|
31872
31921
|
actions.error("Try again with `--verbose` to see more information.");
|
|
31873
31922
|
}
|
|
31874
|
-
|
|
31923
|
+
handle_exit();
|
|
31924
|
+
actions.exit(18);
|
|
31875
31925
|
}
|
|
31876
31926
|
function get_push_group_list() {
|
|
31877
31927
|
// start from HEAD and work backward to rebase_group_index
|
|
@@ -31980,7 +32030,7 @@ async function run$3(args) {
|
|
|
31980
32030
|
});
|
|
31981
32031
|
}
|
|
31982
32032
|
}
|
|
31983
|
-
function handle_exit(
|
|
32033
|
+
function handle_exit() {
|
|
31984
32034
|
actions.output(reactExports.createElement(Text, { color: colors.yellow }, "Restoring PR state\u2026"));
|
|
31985
32035
|
for (const group of push_group_list) {
|
|
31986
32036
|
// we may temporarily mark PR as a draft before editing it
|
|
@@ -31996,7 +32046,6 @@ async function run$3(args) {
|
|
|
31996
32046
|
}
|
|
31997
32047
|
}
|
|
31998
32048
|
actions.output(reactExports.createElement(Text, { color: colors.yellow }, "Restored PR state."));
|
|
31999
|
-
actions.exit(code);
|
|
32000
32049
|
}
|
|
32001
32050
|
}
|
|
32002
32051
|
const get_group_url = (group) => group.pr?.url || group.id;
|
|
@@ -32179,7 +32228,7 @@ async function run$1() {
|
|
|
32179
32228
|
});
|
|
32180
32229
|
if (diff_cmd.code) {
|
|
32181
32230
|
save_stash = true;
|
|
32182
|
-
await cli("git stash -
|
|
32231
|
+
await cli("git stash --include-untracked");
|
|
32183
32232
|
actions.output(reactExports.createElement(Text, null, "\uD83D\uDCE6 Changes saved to stash"));
|
|
32184
32233
|
}
|
|
32185
32234
|
try {
|
|
@@ -32199,7 +32248,7 @@ async function run$1() {
|
|
|
32199
32248
|
}
|
|
32200
32249
|
finally {
|
|
32201
32250
|
if (save_stash) {
|
|
32202
|
-
await cli("git stash pop
|
|
32251
|
+
await cli("git stash pop");
|
|
32203
32252
|
actions.output(reactExports.createElement(Text, { color: colors.green }, "\u2705 Changes restored from stash"));
|
|
32204
32253
|
}
|
|
32205
32254
|
}
|
|
@@ -32278,7 +32327,8 @@ function App() {
|
|
|
32278
32327
|
reactExports.createElement(DependencyCheck, null,
|
|
32279
32328
|
reactExports.createElement(RebaseCheck, null,
|
|
32280
32329
|
reactExports.createElement(CherryPickCheck, null,
|
|
32281
|
-
reactExports.createElement(MaybeMain, null))))))
|
|
32330
|
+
reactExports.createElement(MaybeMain, null)))))),
|
|
32331
|
+
reactExports.createElement(HandleCtrlCSigint, null)));
|
|
32282
32332
|
}
|
|
32283
32333
|
function MaybeMain() {
|
|
32284
32334
|
const argv = Store.useState((state) => state.argv);
|
|
@@ -37620,7 +37670,7 @@ async function command() {
|
|
|
37620
37670
|
.wrap(123)
|
|
37621
37671
|
// disallow unknown options
|
|
37622
37672
|
.strict()
|
|
37623
|
-
.version("1.
|
|
37673
|
+
.version("1.15.0" )
|
|
37624
37674
|
.showHidden("show-hidden", "Show hidden options via `git stack help --show-hidden`")
|
|
37625
37675
|
.help("help", "Show usage via `git stack help`")
|
|
37626
37676
|
.argv;
|
|
@@ -37708,22 +37758,35 @@ const FixupOptions = {
|
|
|
37708
37758
|
},
|
|
37709
37759
|
};
|
|
37710
37760
|
|
|
37711
|
-
|
|
37712
|
-
|
|
37713
|
-
|
|
37714
|
-
|
|
37715
|
-
|
|
37716
|
-
|
|
37717
|
-
|
|
37718
|
-
|
|
37719
|
-
|
|
37720
|
-
|
|
37721
|
-
|
|
37722
|
-
|
|
37723
|
-
|
|
37724
|
-
|
|
37761
|
+
(async function main() {
|
|
37762
|
+
try {
|
|
37763
|
+
const argv = await command();
|
|
37764
|
+
const ink = render(reactExports.createElement(App, null), {
|
|
37765
|
+
// If true, each update will be rendered as a separate output, without replacing the previous one.
|
|
37766
|
+
// debug: true,
|
|
37767
|
+
//
|
|
37768
|
+
// Configure whether Ink should listen to Ctrl+C keyboard input and exit the app.
|
|
37769
|
+
// We intentionally handle this ourselves in `<Exit />`
|
|
37770
|
+
exitOnCtrlC: false,
|
|
37771
|
+
});
|
|
37772
|
+
Store.setState((state) => {
|
|
37773
|
+
state.ink = ink;
|
|
37774
|
+
state.process_argv = process.argv;
|
|
37775
|
+
state.argv = argv;
|
|
37776
|
+
state.cwd = process.cwd();
|
|
37777
|
+
});
|
|
37778
|
+
Store.getState().actions.debug(pretty_json(argv));
|
|
37779
|
+
await ink.waitUntilExit();
|
|
37780
|
+
}
|
|
37781
|
+
catch (err) {
|
|
37782
|
+
// eslint-disable-next-line no-console
|
|
37783
|
+
console.error(err);
|
|
37784
|
+
process.exit(235);
|
|
37785
|
+
}
|
|
37786
|
+
})().catch((err) => {
|
|
37725
37787
|
// eslint-disable-next-line no-console
|
|
37726
|
-
|
|
37788
|
+
console.error(err);
|
|
37789
|
+
});
|
|
37727
37790
|
|
|
37728
37791
|
// prettier-ignore
|
|
37729
37792
|
const METADATA = {
|
package/package.json
CHANGED
package/src/app/App.tsx
CHANGED
|
@@ -8,6 +8,7 @@ import { DetectInitialPR } from "~/app/DetectInitialPR";
|
|
|
8
8
|
import { DirtyCheck } from "~/app/DirtyCheck";
|
|
9
9
|
import { GatherMetadata } from "~/app/GatherMetadata";
|
|
10
10
|
import { GithubApiError } from "~/app/GithubApiError";
|
|
11
|
+
import { HandleCtrlCSigint } from "~/app/HandleCtrlCSigint";
|
|
11
12
|
import { LocalCommitStatus } from "~/app/LocalCommitStatus";
|
|
12
13
|
import { Main } from "~/app/Main";
|
|
13
14
|
import { Output } from "~/app/Output";
|
|
@@ -65,6 +66,8 @@ export function App() {
|
|
|
65
66
|
</DependencyCheck>
|
|
66
67
|
</VerboseDebugInfo>
|
|
67
68
|
</AutoUpdate>
|
|
69
|
+
|
|
70
|
+
<HandleCtrlCSigint />
|
|
68
71
|
</Providers>
|
|
69
72
|
);
|
|
70
73
|
}
|
package/src/app/Debug.tsx
CHANGED
|
@@ -8,6 +8,7 @@ import * as Ink from "ink-cjs";
|
|
|
8
8
|
import { Store } from "~/app/Store";
|
|
9
9
|
import { colors } from "~/core/colors";
|
|
10
10
|
import * as json from "~/core/json";
|
|
11
|
+
import { pretty_json } from "~/core/pretty_json";
|
|
11
12
|
import { safe_rm } from "~/core/safe_rm";
|
|
12
13
|
|
|
13
14
|
export function Debug() {
|
|
@@ -41,7 +42,7 @@ export function Debug() {
|
|
|
41
42
|
await safe_rm(output_file);
|
|
42
43
|
|
|
43
44
|
const serialized = json.serialize(state);
|
|
44
|
-
const content =
|
|
45
|
+
const content = pretty_json(serialized);
|
|
45
46
|
await fs.writeFile(output_file, content);
|
|
46
47
|
}
|
|
47
48
|
},
|
package/src/app/DirtyCheck.tsx
CHANGED
|
@@ -15,7 +15,7 @@ type Props = {
|
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
type State = {
|
|
18
|
-
status: "init" | "prompt" | "done";
|
|
18
|
+
status: "init" | "prompt" | "stash" | "done";
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
function reducer(state: State, patch: Partial<State>) {
|
|
@@ -35,34 +35,42 @@ export function DirtyCheck(props: Props) {
|
|
|
35
35
|
|
|
36
36
|
case "prompt":
|
|
37
37
|
return (
|
|
38
|
-
<
|
|
39
|
-
|
|
40
|
-
<Ink.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
38
|
+
<Ink.Box flexDirection="column">
|
|
39
|
+
<FormatText
|
|
40
|
+
wrapper={<Ink.Text color={colors.yellow} />}
|
|
41
|
+
message="⚠️ Uncommitted changes detected. {git_stack} needs a clean working tree."
|
|
42
|
+
values={{
|
|
43
|
+
git: <Command>git</Command>,
|
|
44
|
+
git_stack: <Command>git stack</Command>,
|
|
45
|
+
}}
|
|
46
|
+
/>
|
|
47
|
+
|
|
48
|
+
<YesNoPrompt
|
|
49
|
+
message={
|
|
49
50
|
<FormatText
|
|
50
51
|
wrapper={<Ink.Text color={colors.yellow} />}
|
|
51
|
-
message="
|
|
52
|
+
message="{git_stash} changes to proceed?"
|
|
52
53
|
values={{
|
|
53
|
-
|
|
54
|
-
git_stack: <Command>git stack</Command>,
|
|
54
|
+
git_stash: <Command>git stash</Command>,
|
|
55
55
|
}}
|
|
56
56
|
/>
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
57
|
+
}
|
|
58
|
+
onYes={async () => {
|
|
59
|
+
await cli("git stash --include-untracked");
|
|
60
|
+
|
|
61
|
+
actions.output(<Ink.Text>📦 Changes saved to stash</Ink.Text>);
|
|
62
|
+
|
|
63
|
+
actions.set((state) => {
|
|
64
|
+
state.is_dirty_check_stash = true;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
patch({ status: "done" });
|
|
68
|
+
}}
|
|
69
|
+
onNo={async () => {
|
|
70
|
+
actions.exit(0);
|
|
71
|
+
}}
|
|
72
|
+
/>
|
|
73
|
+
</Ink.Box>
|
|
66
74
|
);
|
|
67
75
|
|
|
68
76
|
default:
|
|
@@ -84,8 +92,11 @@ export function DirtyCheck(props: Props) {
|
|
|
84
92
|
try {
|
|
85
93
|
const git_dirty = (await cli(`git status --porcelain`)).stdout;
|
|
86
94
|
|
|
87
|
-
|
|
88
|
-
|
|
95
|
+
if (!git_dirty) {
|
|
96
|
+
patch({ status: "done" });
|
|
97
|
+
} else {
|
|
98
|
+
patch({ status: "prompt" });
|
|
99
|
+
}
|
|
89
100
|
} catch (err) {
|
|
90
101
|
actions.error("Must be run from within a git repository.");
|
|
91
102
|
|
package/src/app/Exit.tsx
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
|
|
3
|
+
import * as Ink from "ink-cjs";
|
|
4
|
+
|
|
3
5
|
import { Store } from "~/app/Store";
|
|
6
|
+
import { cli } from "~/core/cli";
|
|
7
|
+
import { colors } from "~/core/colors";
|
|
8
|
+
import { sleep } from "~/core/sleep";
|
|
4
9
|
|
|
5
10
|
type Props = {
|
|
6
11
|
clear: boolean;
|
|
@@ -8,17 +13,49 @@ type Props = {
|
|
|
8
13
|
};
|
|
9
14
|
|
|
10
15
|
export function Exit(props: Props) {
|
|
11
|
-
const actions = Store.useActions();
|
|
12
|
-
|
|
13
16
|
React.useEffect(() => {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
+
// immediately handle exit on mount
|
|
18
|
+
handle_exit().catch((err) => {
|
|
19
|
+
// eslint-disable-next-line no-console
|
|
20
|
+
console.error(err);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
async function handle_exit() {
|
|
24
|
+
const state = Store.getState();
|
|
25
|
+
const actions = state.actions;
|
|
26
|
+
|
|
27
|
+
actions.debug(`[Exit] handle_exit ${JSON.stringify(props)}`);
|
|
28
|
+
|
|
29
|
+
let exit_code = props.code;
|
|
17
30
|
|
|
18
|
-
|
|
31
|
+
// run abort_handler if it exists
|
|
32
|
+
if (state.abort_handler) {
|
|
33
|
+
exit_code = await state.abort_handler();
|
|
34
|
+
}
|
|
19
35
|
|
|
20
|
-
|
|
21
|
-
|
|
36
|
+
// restore git stash if necessary
|
|
37
|
+
if (state.is_dirty_check_stash) {
|
|
38
|
+
await cli("git stash pop");
|
|
39
|
+
actions.output(
|
|
40
|
+
<Ink.Text color={colors.green}>
|
|
41
|
+
✅ Changes restored from stash
|
|
42
|
+
</Ink.Text>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ensure output has a chance to render
|
|
47
|
+
await sleep(1);
|
|
48
|
+
|
|
49
|
+
// finally handle the actual app and process exit
|
|
50
|
+
if (props.clear) {
|
|
51
|
+
actions.clear();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
actions.unmount();
|
|
55
|
+
|
|
56
|
+
process.exitCode = exit_code;
|
|
57
|
+
process.exit();
|
|
58
|
+
}
|
|
22
59
|
}, [props.clear, props.code]);
|
|
23
60
|
|
|
24
61
|
return null;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import * as Ink from "ink-cjs";
|
|
4
|
+
|
|
5
|
+
import { FormatText } from "~/app/FormatText";
|
|
6
|
+
import { Store } from "~/app/Store";
|
|
7
|
+
import { colors } from "~/core/colors";
|
|
8
|
+
import { sleep } from "~/core/sleep";
|
|
9
|
+
|
|
10
|
+
export function HandleCtrlCSigint() {
|
|
11
|
+
const actions = Store.useActions();
|
|
12
|
+
|
|
13
|
+
const [exiting, set_exiting] = React.useState(false);
|
|
14
|
+
|
|
15
|
+
Ink.useInput((input, key) => {
|
|
16
|
+
handle_input().catch((err) => {
|
|
17
|
+
// eslint-disable-next-line no-console
|
|
18
|
+
console.error(err);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
async function handle_input() {
|
|
22
|
+
if (input === "c" && key.ctrl) {
|
|
23
|
+
actions.clear();
|
|
24
|
+
|
|
25
|
+
actions.output(
|
|
26
|
+
<Ink.Text color={colors.red}>
|
|
27
|
+
<FormatText message="🚨 Ctrl+C detected" />
|
|
28
|
+
</Ink.Text>
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
set_exiting(true);
|
|
32
|
+
await sleep(1);
|
|
33
|
+
actions.exit(235);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
if (exiting) {
|
|
39
|
+
return (
|
|
40
|
+
<Ink.Text color={colors.red}>
|
|
41
|
+
<FormatText message="🚨 Exiting…" />
|
|
42
|
+
</Ink.Text>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return null;
|
|
47
|
+
}
|
package/src/app/ManualRebase.tsx
CHANGED
|
@@ -15,35 +15,15 @@ import { invariant } from "~/core/invariant";
|
|
|
15
15
|
import { short_id } from "~/core/short_id";
|
|
16
16
|
|
|
17
17
|
export function ManualRebase() {
|
|
18
|
-
const abort_handler = React.useRef(() => {});
|
|
19
|
-
|
|
20
|
-
React.useEffect(function listen_sigint() {
|
|
21
|
-
process.once("SIGINT", sigint_handler);
|
|
22
|
-
|
|
23
|
-
return function cleanup() {
|
|
24
|
-
process.removeListener("SIGINT", sigint_handler);
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
async function sigint_handler() {
|
|
28
|
-
abort_handler.current();
|
|
29
|
-
}
|
|
30
|
-
}, []);
|
|
31
|
-
|
|
32
18
|
return (
|
|
33
19
|
<Await
|
|
34
20
|
fallback={<Ink.Text color={colors.yellow}>Rebasing commits…</Ink.Text>}
|
|
35
|
-
function={
|
|
36
|
-
await run({ abort_handler });
|
|
37
|
-
}}
|
|
21
|
+
function={run}
|
|
38
22
|
/>
|
|
39
23
|
);
|
|
40
24
|
}
|
|
41
25
|
|
|
42
|
-
|
|
43
|
-
abort_handler: React.MutableRefObject<() => void>;
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
async function run(args: Args) {
|
|
26
|
+
async function run() {
|
|
47
27
|
const state = Store.getState();
|
|
48
28
|
const actions = state.actions;
|
|
49
29
|
const argv = state.argv;
|
|
@@ -57,11 +37,12 @@ async function run(args: Args) {
|
|
|
57
37
|
invariant(commit_map, "commit_map must exist");
|
|
58
38
|
invariant(repo_root, "repo_root must exist");
|
|
59
39
|
|
|
60
|
-
//
|
|
61
|
-
|
|
40
|
+
// immediately register abort_handler in case of ctrl+c exit
|
|
41
|
+
actions.register_abort_handler(async function abort_manual_rebase() {
|
|
62
42
|
actions.output(<Ink.Text color={colors.red}>🚨 Abort</Ink.Text>);
|
|
63
|
-
handle_exit(
|
|
64
|
-
|
|
43
|
+
handle_exit();
|
|
44
|
+
return 15;
|
|
45
|
+
});
|
|
65
46
|
|
|
66
47
|
const temp_branch_name = `${branch_name}_${short_id()}`;
|
|
67
48
|
|
|
@@ -137,6 +118,8 @@ async function run(args: Args) {
|
|
|
137
118
|
|
|
138
119
|
restore_git();
|
|
139
120
|
|
|
121
|
+
actions.unregister_abort_handler();
|
|
122
|
+
|
|
140
123
|
if (argv.sync) {
|
|
141
124
|
actions.set((state) => {
|
|
142
125
|
state.step = "sync-github";
|
|
@@ -157,7 +140,8 @@ async function run(args: Args) {
|
|
|
157
140
|
actions.error("Try again with `--verbose` to see more information.");
|
|
158
141
|
}
|
|
159
142
|
|
|
160
|
-
handle_exit(
|
|
143
|
+
handle_exit();
|
|
144
|
+
actions.exit(16);
|
|
161
145
|
}
|
|
162
146
|
|
|
163
147
|
// cleanup git operations if cancelled during manual rebase
|
|
@@ -185,7 +169,7 @@ async function run(args: Args) {
|
|
|
185
169
|
cli.sync(`pwd`, spawn_options);
|
|
186
170
|
}
|
|
187
171
|
|
|
188
|
-
function handle_exit(
|
|
172
|
+
function handle_exit() {
|
|
189
173
|
actions.output(
|
|
190
174
|
<Ink.Text color={colors.yellow}>
|
|
191
175
|
Restoring <Brackets>{branch_name}</Brackets>…
|
|
@@ -199,7 +183,5 @@ async function run(args: Args) {
|
|
|
199
183
|
Restored <Brackets>{branch_name}</Brackets>.
|
|
200
184
|
</Ink.Text>
|
|
201
185
|
);
|
|
202
|
-
|
|
203
|
-
actions.exit(code);
|
|
204
186
|
}
|
|
205
187
|
}
|
package/src/app/Store.tsx
CHANGED
|
@@ -7,6 +7,7 @@ import { immer } from "zustand/middleware/immer";
|
|
|
7
7
|
import { Exit } from "~/app/Exit";
|
|
8
8
|
import { LogTimestamp } from "~/app/LogTimestamp";
|
|
9
9
|
import { colors } from "~/core/colors";
|
|
10
|
+
import { pretty_json } from "~/core/pretty_json";
|
|
10
11
|
|
|
11
12
|
import type { Instance as InkInstance } from "ink-cjs";
|
|
12
13
|
import type { Argv } from "~/command";
|
|
@@ -29,6 +30,9 @@ type SyncGithubState = {
|
|
|
29
30
|
rebase_group_index: number;
|
|
30
31
|
};
|
|
31
32
|
|
|
33
|
+
// async function that returns exit code
|
|
34
|
+
type AbortHandler = () => Promise<number>;
|
|
35
|
+
|
|
32
36
|
export type State = {
|
|
33
37
|
// set immediately in `index.tsx` so no `null` scenario
|
|
34
38
|
process_argv: Array<string>;
|
|
@@ -47,6 +51,8 @@ export type State = {
|
|
|
47
51
|
pr_templates: Array<string>;
|
|
48
52
|
pr_template_body: null | string;
|
|
49
53
|
sync_github: null | SyncGithubState;
|
|
54
|
+
is_dirty_check_stash: boolean;
|
|
55
|
+
abort_handler: null | AbortHandler;
|
|
50
56
|
|
|
51
57
|
step:
|
|
52
58
|
| "github-api-error"
|
|
@@ -71,7 +77,7 @@ export type State = {
|
|
|
71
77
|
clear(): void;
|
|
72
78
|
unmount(): void;
|
|
73
79
|
newline(): void;
|
|
74
|
-
json(value:
|
|
80
|
+
json(value: pretty_json.JSONValue): void;
|
|
75
81
|
error(message: string): void;
|
|
76
82
|
output(node: React.ReactNode): void;
|
|
77
83
|
debug(node: React.ReactNode, id?: string): void;
|
|
@@ -79,6 +85,8 @@ export type State = {
|
|
|
79
85
|
isDebug(): boolean;
|
|
80
86
|
|
|
81
87
|
reset_pr(): void;
|
|
88
|
+
register_abort_handler(abort_handler: AbortHandler): void;
|
|
89
|
+
unregister_abort_handler(): void;
|
|
82
90
|
|
|
83
91
|
set(setter: Setter): void;
|
|
84
92
|
};
|
|
@@ -113,6 +121,8 @@ const BaseStore = createStore<State>()(
|
|
|
113
121
|
pr_templates: [],
|
|
114
122
|
pr_template_body: null,
|
|
115
123
|
sync_github: null,
|
|
124
|
+
is_dirty_check_stash: false,
|
|
125
|
+
abort_handler: null,
|
|
116
126
|
|
|
117
127
|
step: "loading",
|
|
118
128
|
|
|
@@ -146,7 +156,7 @@ const BaseStore = createStore<State>()(
|
|
|
146
156
|
|
|
147
157
|
json(value) {
|
|
148
158
|
set((state) => {
|
|
149
|
-
const node =
|
|
159
|
+
const node = pretty_json(value);
|
|
150
160
|
state.mutate.output(state, { node });
|
|
151
161
|
});
|
|
152
162
|
},
|
|
@@ -189,6 +199,18 @@ const BaseStore = createStore<State>()(
|
|
|
189
199
|
});
|
|
190
200
|
},
|
|
191
201
|
|
|
202
|
+
register_abort_handler(abort_handler) {
|
|
203
|
+
set((state) => {
|
|
204
|
+
state.abort_handler = abort_handler;
|
|
205
|
+
});
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
unregister_abort_handler() {
|
|
209
|
+
set((state) => {
|
|
210
|
+
state.abort_handler = null;
|
|
211
|
+
});
|
|
212
|
+
},
|
|
213
|
+
|
|
192
214
|
set(setter) {
|
|
193
215
|
set((state) => {
|
|
194
216
|
setter(state);
|
package/src/app/SyncGithub.tsx
CHANGED
|
@@ -14,35 +14,15 @@ import { invariant } from "~/core/invariant";
|
|
|
14
14
|
import type * as CommitMetadata from "~/core/CommitMetadata";
|
|
15
15
|
|
|
16
16
|
export function SyncGithub() {
|
|
17
|
-
const abort_handler = React.useRef(() => {});
|
|
18
|
-
|
|
19
|
-
React.useEffect(function listen_sigint() {
|
|
20
|
-
process.once("SIGINT", sigint_handler);
|
|
21
|
-
|
|
22
|
-
return function cleanup() {
|
|
23
|
-
process.removeListener("SIGINT", sigint_handler);
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
function sigint_handler() {
|
|
27
|
-
abort_handler.current();
|
|
28
|
-
}
|
|
29
|
-
}, []);
|
|
30
|
-
|
|
31
17
|
return (
|
|
32
18
|
<Await
|
|
33
19
|
fallback={<Ink.Text color={colors.yellow}>Syncing…</Ink.Text>}
|
|
34
|
-
function={
|
|
35
|
-
await run({ abort_handler });
|
|
36
|
-
}}
|
|
20
|
+
function={run}
|
|
37
21
|
/>
|
|
38
22
|
);
|
|
39
23
|
}
|
|
40
24
|
|
|
41
|
-
|
|
42
|
-
abort_handler: React.MutableRefObject<() => void>;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
async function run(args: Args) {
|
|
25
|
+
async function run() {
|
|
46
26
|
const state = Store.getState();
|
|
47
27
|
const actions = state.actions;
|
|
48
28
|
const argv = state.argv;
|
|
@@ -60,11 +40,12 @@ async function run(args: Args) {
|
|
|
60
40
|
const commit_range = sync_github.commit_range;
|
|
61
41
|
const rebase_group_index = sync_github.rebase_group_index;
|
|
62
42
|
|
|
63
|
-
//
|
|
64
|
-
|
|
43
|
+
// immediately register abort_handler in case of ctrl+c exit
|
|
44
|
+
actions.register_abort_handler(async function abort_sync_github() {
|
|
65
45
|
actions.output(<Ink.Text color={colors.red}>🚨 Abort</Ink.Text>);
|
|
66
|
-
handle_exit(
|
|
67
|
-
|
|
46
|
+
handle_exit();
|
|
47
|
+
return 17;
|
|
48
|
+
});
|
|
68
49
|
|
|
69
50
|
let DEFAULT_PR_BODY = "";
|
|
70
51
|
if (state.pr_template_body) {
|
|
@@ -151,6 +132,8 @@ async function run(args: Args) {
|
|
|
151
132
|
|
|
152
133
|
await Promise.all(update_pr_body_tasks);
|
|
153
134
|
|
|
135
|
+
actions.unregister_abort_handler();
|
|
136
|
+
|
|
154
137
|
actions.set((state) => {
|
|
155
138
|
state.step = "post-rebase-status";
|
|
156
139
|
});
|
|
@@ -164,7 +147,8 @@ async function run(args: Args) {
|
|
|
164
147
|
actions.error("Try again with `--verbose` to see more information.");
|
|
165
148
|
}
|
|
166
149
|
|
|
167
|
-
|
|
150
|
+
handle_exit();
|
|
151
|
+
actions.exit(18);
|
|
168
152
|
}
|
|
169
153
|
|
|
170
154
|
function get_push_group_list() {
|
|
@@ -305,7 +289,7 @@ async function run(args: Args) {
|
|
|
305
289
|
}
|
|
306
290
|
}
|
|
307
291
|
|
|
308
|
-
function handle_exit(
|
|
292
|
+
function handle_exit() {
|
|
309
293
|
actions.output(
|
|
310
294
|
<Ink.Text color={colors.yellow}>Restoring PR state…</Ink.Text>
|
|
311
295
|
);
|
|
@@ -329,8 +313,6 @@ async function run(args: Args) {
|
|
|
329
313
|
actions.output(
|
|
330
314
|
<Ink.Text color={colors.yellow}>Restored PR state.</Ink.Text>
|
|
331
315
|
);
|
|
332
|
-
|
|
333
|
-
actions.exit(code);
|
|
334
316
|
}
|
|
335
317
|
}
|
|
336
318
|
|
package/src/commands/Fixup.tsx
CHANGED
|
@@ -97,7 +97,7 @@ async function run() {
|
|
|
97
97
|
if (diff_cmd.code) {
|
|
98
98
|
save_stash = true;
|
|
99
99
|
|
|
100
|
-
await cli("git stash -
|
|
100
|
+
await cli("git stash --include-untracked");
|
|
101
101
|
|
|
102
102
|
actions.output(<Ink.Text>📦 Changes saved to stash</Ink.Text>);
|
|
103
103
|
}
|
|
@@ -118,7 +118,7 @@ async function run() {
|
|
|
118
118
|
await cli("git reset --soft HEAD~1");
|
|
119
119
|
} finally {
|
|
120
120
|
if (save_stash) {
|
|
121
|
-
await cli("git stash pop
|
|
121
|
+
await cli("git stash pop");
|
|
122
122
|
|
|
123
123
|
actions.output(
|
|
124
124
|
<Ink.Text color={colors.green}>✅ Changes restored from stash</Ink.Text>
|
package/src/commands/Rebase.tsx
CHANGED
|
@@ -16,35 +16,15 @@ import { invariant } from "~/core/invariant";
|
|
|
16
16
|
import { short_id } from "~/core/short_id";
|
|
17
17
|
|
|
18
18
|
export function Rebase() {
|
|
19
|
-
const abort_handler = React.useRef(() => {});
|
|
20
|
-
|
|
21
|
-
React.useEffect(function listen_sigint() {
|
|
22
|
-
process.once("SIGINT", sigint_handler);
|
|
23
|
-
|
|
24
|
-
return function cleanup() {
|
|
25
|
-
process.removeListener("SIGINT", sigint_handler);
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
function sigint_handler() {
|
|
29
|
-
abort_handler.current();
|
|
30
|
-
}
|
|
31
|
-
}, []);
|
|
32
|
-
|
|
33
19
|
return (
|
|
34
20
|
<Await
|
|
35
21
|
fallback={<Ink.Text color={colors.yellow}>Rebasing commits…</Ink.Text>}
|
|
36
|
-
function={
|
|
37
|
-
await Rebase.run({ abort_handler });
|
|
38
|
-
}}
|
|
22
|
+
function={Rebase.run}
|
|
39
23
|
/>
|
|
40
24
|
);
|
|
41
25
|
}
|
|
42
26
|
|
|
43
|
-
|
|
44
|
-
abort_handler: React.MutableRefObject<() => void>;
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
Rebase.run = async function run(args: Args) {
|
|
27
|
+
Rebase.run = async function run() {
|
|
48
28
|
const state = Store.getState();
|
|
49
29
|
const actions = state.actions;
|
|
50
30
|
const branch_name = state.branch_name;
|
|
@@ -57,11 +37,12 @@ Rebase.run = async function run(args: Args) {
|
|
|
57
37
|
invariant(commit_range, "commit_range must exist");
|
|
58
38
|
invariant(repo_root, "repo_root must exist");
|
|
59
39
|
|
|
60
|
-
//
|
|
61
|
-
|
|
40
|
+
// immediately register abort_handler in case of ctrl+c exit
|
|
41
|
+
actions.register_abort_handler(async function abort_rebase() {
|
|
62
42
|
actions.output(<Ink.Text color={colors.red}>🚨 Abort</Ink.Text>);
|
|
63
|
-
handle_exit(
|
|
64
|
-
|
|
43
|
+
handle_exit();
|
|
44
|
+
return 19;
|
|
45
|
+
});
|
|
65
46
|
|
|
66
47
|
const temp_branch_name = `${branch_name}_${short_id()}`;
|
|
67
48
|
|
|
@@ -153,6 +134,8 @@ Rebase.run = async function run(args: Args) {
|
|
|
153
134
|
/>
|
|
154
135
|
);
|
|
155
136
|
|
|
137
|
+
actions.unregister_abort_handler();
|
|
138
|
+
|
|
156
139
|
actions.set((state) => {
|
|
157
140
|
state.commit_range = next_commit_range;
|
|
158
141
|
state.step = "status";
|
|
@@ -166,7 +149,8 @@ Rebase.run = async function run(args: Args) {
|
|
|
166
149
|
}
|
|
167
150
|
}
|
|
168
151
|
|
|
169
|
-
handle_exit(
|
|
152
|
+
handle_exit();
|
|
153
|
+
actions.exit(20);
|
|
170
154
|
}
|
|
171
155
|
|
|
172
156
|
// cleanup git operations if cancelled during manual rebase
|
|
@@ -194,7 +178,7 @@ Rebase.run = async function run(args: Args) {
|
|
|
194
178
|
cli.sync(`pwd`, spawn_options);
|
|
195
179
|
}
|
|
196
180
|
|
|
197
|
-
function handle_exit(
|
|
181
|
+
function handle_exit() {
|
|
198
182
|
actions.output(
|
|
199
183
|
<Ink.Text color={colors.yellow}>
|
|
200
184
|
Restoring <Brackets>{branch_name}</Brackets>…
|
|
@@ -208,7 +192,5 @@ Rebase.run = async function run(args: Args) {
|
|
|
208
192
|
Restored <Brackets>{branch_name}</Brackets>.
|
|
209
193
|
</Ink.Text>
|
|
210
194
|
);
|
|
211
|
-
|
|
212
|
-
actions.exit(code);
|
|
213
195
|
}
|
|
214
196
|
};
|
|
@@ -5,6 +5,7 @@ import path from "node:path";
|
|
|
5
5
|
import * as Metadata from "~/core/Metadata";
|
|
6
6
|
import { cli } from "~/core/cli";
|
|
7
7
|
import { invariant } from "~/core/invariant";
|
|
8
|
+
import { safe_rm } from "~/core/safe_rm";
|
|
8
9
|
|
|
9
10
|
import type * as CommitMetadata from "~/core/CommitMetadata";
|
|
10
11
|
|
|
@@ -140,6 +141,9 @@ GitReviseTodo.execute = async function grt_execute(args: ExecuteArgs) {
|
|
|
140
141
|
// change to pipe to see output temporarily
|
|
141
142
|
// https://github.com/magus/git-stack-cli/commit/f9f10e3ac3cd9a35ee75d3e0851a48391967a23f
|
|
142
143
|
await cli(command, { stdio: ["ignore", "ignore", "ignore"] });
|
|
144
|
+
|
|
145
|
+
// cleanup tmp_git_sequence_editor_path
|
|
146
|
+
await safe_rm(tmp_git_sequence_editor_path);
|
|
143
147
|
};
|
|
144
148
|
|
|
145
149
|
type ExecuteArgs = {
|
package/src/core/github.tsx
CHANGED
|
@@ -161,8 +161,10 @@ type EditPullRequestArgs = {
|
|
|
161
161
|
export async function pr_edit(args: EditPullRequestArgs) {
|
|
162
162
|
const command_parts = [`gh pr edit ${args.branch} --base ${args.base}`];
|
|
163
163
|
|
|
164
|
+
let body_file: string | undefined;
|
|
165
|
+
|
|
164
166
|
if (args.body) {
|
|
165
|
-
|
|
167
|
+
body_file = await write_body_file(args);
|
|
166
168
|
command_parts.push(`--body-file="${body_file}"`);
|
|
167
169
|
}
|
|
168
170
|
|
|
@@ -171,6 +173,11 @@ export async function pr_edit(args: EditPullRequestArgs) {
|
|
|
171
173
|
if (cli_result.code !== 0) {
|
|
172
174
|
handle_error(cli_result.output);
|
|
173
175
|
}
|
|
176
|
+
|
|
177
|
+
// cleanup body_file
|
|
178
|
+
if (body_file) {
|
|
179
|
+
await safe_rm(body_file);
|
|
180
|
+
}
|
|
174
181
|
}
|
|
175
182
|
|
|
176
183
|
type DraftPullRequestArgs = {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export namespace pretty_json {
|
|
2
|
+
export type JSONValue =
|
|
3
|
+
| null
|
|
4
|
+
| number
|
|
5
|
+
| string
|
|
6
|
+
| boolean
|
|
7
|
+
| { [key: string]: JSONValue };
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function pretty_json<T extends pretty_json.JSONValue>(input: T): string {
|
|
11
|
+
return JSON.stringify(input, null, 2);
|
|
12
|
+
}
|
package/src/index.tsx
CHANGED
|
@@ -7,12 +7,19 @@ import * as Ink from "ink-cjs";
|
|
|
7
7
|
import { App } from "~/app/App";
|
|
8
8
|
import { Store } from "~/app/Store";
|
|
9
9
|
import { command } from "~/command";
|
|
10
|
+
import { pretty_json } from "~/core/pretty_json";
|
|
11
|
+
|
|
12
|
+
(async function main() {
|
|
13
|
+
try {
|
|
14
|
+
const argv = await command();
|
|
10
15
|
|
|
11
|
-
command()
|
|
12
|
-
.then((argv) => {
|
|
13
16
|
const ink = Ink.render(<App />, {
|
|
14
17
|
// If true, each update will be rendered as a separate output, without replacing the previous one.
|
|
15
18
|
// debug: true,
|
|
19
|
+
//
|
|
20
|
+
// Configure whether Ink should listen to Ctrl+C keyboard input and exit the app.
|
|
21
|
+
// We intentionally handle this ourselves in `<Exit />`
|
|
22
|
+
exitOnCtrlC: false,
|
|
16
23
|
});
|
|
17
24
|
|
|
18
25
|
Store.setState((state) => {
|
|
@@ -22,7 +29,15 @@ command()
|
|
|
22
29
|
state.cwd = process.cwd();
|
|
23
30
|
});
|
|
24
31
|
|
|
25
|
-
Store.getState().actions.debug(
|
|
26
|
-
|
|
32
|
+
Store.getState().actions.debug(pretty_json(argv as any));
|
|
33
|
+
|
|
34
|
+
await ink.waitUntilExit();
|
|
35
|
+
} catch (err) {
|
|
36
|
+
// eslint-disable-next-line no-console
|
|
37
|
+
console.error(err);
|
|
38
|
+
process.exit(235);
|
|
39
|
+
}
|
|
40
|
+
})().catch((err) => {
|
|
27
41
|
// eslint-disable-next-line no-console
|
|
28
|
-
|
|
42
|
+
console.error(err);
|
|
43
|
+
});
|