git-stack-cli 1.12.0 → 1.13.1

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.
@@ -11,12 +11,13 @@ var node_events = require('node:events');
11
11
  var fs = require('node:fs');
12
12
  var require$$1 = require('module');
13
13
  var node_buffer = require('node:buffer');
14
+ var fs$1 = require('node:fs/promises');
14
15
  var path = require('node:path');
15
16
  var child = require('node:child_process');
16
17
  var https = require('node:https');
17
18
  var crypto = require('node:crypto');
18
19
  var path$1 = require('path');
19
- var fs$1 = require('fs');
20
+ var fs$2 = require('fs');
20
21
  var util = require('util');
21
22
  var url = require('url');
22
23
 
@@ -26212,6 +26213,7 @@ const BaseStore = createStore()(immer((set, get) => ({
26212
26213
  commit_map: null,
26213
26214
  pr_templates: [],
26214
26215
  pr_template_body: null,
26216
+ sync_github: null,
26215
26217
  step: "loading",
26216
26218
  output: [],
26217
26219
  pending_output: {},
@@ -26520,9 +26522,9 @@ function is_finite_value(value) {
26520
26522
  return typeof value === "number" && Number.isFinite(value);
26521
26523
  }
26522
26524
 
26523
- function read_json(path) {
26525
+ async function read_json(path) {
26524
26526
  try {
26525
- const file_buffer = fs.readFileSync(path);
26527
+ const file_buffer = await fs$1.readFile(path);
26526
26528
  const json_str = String(file_buffer);
26527
26529
  const json = JSON.parse(json_str);
26528
26530
  return json;
@@ -26607,10 +26609,11 @@ function AutoUpdate(props) {
26607
26609
  if (!latest_version) {
26608
26610
  throw new Error("Unable to retrieve latest version from npm");
26609
26611
  }
26610
- const script_dir = path.dirname(fs.realpathSync(process.argv[1]));
26612
+ const script_path = await fs$1.realpath(process.argv[1]);
26613
+ const script_dir = path.dirname(script_path);
26611
26614
  // dist/ts/index.js
26612
26615
  const package_json_path = path.join(script_dir, "..", "..", "package.json");
26613
- const package_json = read_json(package_json_path);
26616
+ const package_json = await read_json(package_json_path);
26614
26617
  if (!package_json) {
26615
26618
  // unable to find read package.json, skip auto update
26616
26619
  throw new Error(`Unable to read package.json [${package_json_path}]`);
@@ -26773,6 +26776,16 @@ function Command(props) {
26773
26776
  return (reactExports.createElement(Text, { bold: true, color: text_color }, props.children));
26774
26777
  }
26775
26778
 
26779
+ async function safe_exists(filepath) {
26780
+ try {
26781
+ await fs$1.access(filepath);
26782
+ return true;
26783
+ }
26784
+ catch {
26785
+ return false;
26786
+ }
26787
+ }
26788
+
26776
26789
  function reducer$3(state, patch) {
26777
26790
  return { ...state, ...patch };
26778
26791
  }
@@ -26803,7 +26816,8 @@ function CherryPickCheck(props) {
26803
26816
  const actions = Store.getState().actions;
26804
26817
  try {
26805
26818
  const git_dir = (await cli(`git rev-parse --absolute-git-dir`)).stdout;
26806
- const is_cherry_pick = fs.existsSync(path.join(git_dir, "CHERRY_PICK_HEAD"));
26819
+ const cherry_pick_file = path.join(git_dir, "CHERRY_PICK_HEAD");
26820
+ const is_cherry_pick = await safe_exists(cherry_pick_file);
26807
26821
  const status = is_cherry_pick ? "prompt" : "done";
26808
26822
  patch({ status });
26809
26823
  }
@@ -26855,6 +26869,16 @@ function deserialize(obj) {
26855
26869
  return obj;
26856
26870
  }
26857
26871
 
26872
+ async function safe_rm(filepath) {
26873
+ try {
26874
+ await fs$1.access(filepath);
26875
+ await fs$1.rm(filepath);
26876
+ }
26877
+ catch {
26878
+ // if access fails there is no file to remove this is safe
26879
+ }
26880
+ }
26881
+
26858
26882
  function Debug() {
26859
26883
  const actions = Store.useActions();
26860
26884
  const state = Store.useState((state) => state);
@@ -26865,17 +26889,18 @@ function Debug() {
26865
26889
  actions.output(reactExports.createElement(Text, { color: colors.yellow }, "Debug mode enabled"));
26866
26890
  }
26867
26891
  }, [argv]);
26868
- reactExports.useEffect(function syncStateJson() {
26892
+ reactExports.useEffect(function sync_state_json() {
26869
26893
  if (!argv?.["write-state-json"]) {
26870
26894
  return;
26871
26895
  }
26872
- const output_file = path.join(state.cwd, "git-stack-state.json");
26873
- if (fs.existsSync(output_file)) {
26874
- fs.rmSync(output_file);
26896
+ sync().catch(actions.error);
26897
+ async function sync() {
26898
+ const output_file = path.join(state.cwd, "git-stack-state.json");
26899
+ await safe_rm(output_file);
26900
+ const serialized = serialize(state);
26901
+ const content = JSON.stringify(serialized, null, 2);
26902
+ await fs$1.writeFile(output_file, content);
26875
26903
  }
26876
- const serialized = serialize(state);
26877
- const content = JSON.stringify(serialized, null, 2);
26878
- fs.writeFileSync(output_file, content);
26879
26904
  }, [argv, state]);
26880
26905
  return null;
26881
26906
  }
@@ -27005,11 +27030,6 @@ function CheckGithubCliAuth(props) {
27005
27030
  }
27006
27031
  function CheckGitRevise(props) {
27007
27032
  const actions = Store.useActions();
27008
- const argv = Store.useState((state) => state.argv);
27009
- // skip git revise check when `rebase` is not git-revise
27010
- if (argv?.["rebase"] !== "git-revise") {
27011
- return props.children;
27012
- }
27013
27033
  return (reactExports.createElement(Await, { fallback: reactExports.createElement(Text, { color: colors.yellow },
27014
27034
  reactExports.createElement(Text, null,
27015
27035
  "Checking ",
@@ -29776,7 +29796,8 @@ async function pr_create(args) {
29776
29796
  async function pr_edit(args) {
29777
29797
  const command_parts = [`gh pr edit ${args.branch} --base ${args.base}`];
29778
29798
  if (args.body) {
29779
- command_parts.push(`--body-file="${body_file(args.body)}"`);
29799
+ const body_file = await write_body_file(args);
29800
+ command_parts.push(`--body-file="${body_file}"`);
29780
29801
  }
29781
29802
  const command = command_parts.join(" ");
29782
29803
  const cli_result = await cli(command);
@@ -29785,6 +29806,9 @@ async function pr_edit(args) {
29785
29806
  }
29786
29807
  }
29787
29808
  async function pr_draft(args) {
29809
+ // https://cli.github.com/manual/gh_api
29810
+ // https://docs.github.com/en/graphql/reference/mutations#convertpullrequesttodraft
29811
+ // https://docs.github.com/en/graphql/reference/mutations#markpullrequestreadyforreview
29788
29812
  const mutation_name = args.draft
29789
29813
  ? "convertPullRequestToDraft"
29790
29814
  : "markPullRequestReadyForReview";
@@ -29827,7 +29851,7 @@ async function gh_json(command) {
29827
29851
  return new Error(cli_result.output);
29828
29852
  }
29829
29853
  // read from file
29830
- const json_str = fs.readFileSync(tmp_pr_json, "utf-8");
29854
+ const json_str = await fs$1.readFile(tmp_pr_json, "utf-8");
29831
29855
  const json = JSON.parse(json_str);
29832
29856
  return json;
29833
29857
  }
@@ -29840,13 +29864,12 @@ function handle_error(output) {
29840
29864
  throw new Error(output);
29841
29865
  }
29842
29866
  // convert a string to a file for use via github cli `--body-file`
29843
- function body_file(body) {
29867
+ async function write_body_file(args) {
29868
+ invariant(args.body, "args.body must exist");
29844
29869
  const temp_dir = os.tmpdir();
29845
- const temp_path = path.join(temp_dir, "git-stack-body");
29846
- if (fs.existsSync(temp_path)) {
29847
- fs.rmSync(temp_path);
29848
- }
29849
- fs.writeFileSync(temp_path, body);
29870
+ const temp_path = path.join(temp_dir, `git-stack-body-${args.base}`);
29871
+ await safe_rm(temp_path);
29872
+ await fs$1.writeFile(temp_path, args.body);
29850
29873
  return temp_path;
29851
29874
  }
29852
29875
 
@@ -30128,9 +30151,9 @@ echo "------ END ------"
30128
30151
  echo "$GIT_REVISE_TODO" > "$git_revise_todo_path"
30129
30152
  `;
30130
30153
  // write script to temporary path
30131
- fs.writeFileSync(tmp_git_sequence_editor_path, GIT_SEQUENCE_EDITOR_SCRIPT);
30154
+ await fs$1.writeFile(tmp_git_sequence_editor_path, GIT_SEQUENCE_EDITOR_SCRIPT);
30132
30155
  // ensure script is executable
30133
- fs.chmodSync(tmp_git_sequence_editor_path, "755");
30156
+ await fs$1.chmod(tmp_git_sequence_editor_path, "755");
30134
30157
  const git_revise_todo = GitReviseTodo(args);
30135
30158
  // execute cli with temporary git sequence editor script
30136
30159
  // revise from merge base to pick correct commits
@@ -30300,9 +30323,9 @@ function DirtyCheck(props) {
30300
30323
 
30301
30324
  function GatherMetadata(props) {
30302
30325
  const fallback = (reactExports.createElement(Text, { color: colors.yellow }, "Gathering local git information\u2026"));
30303
- return (reactExports.createElement(Await, { fallback: fallback, function: run$9 }, props.children));
30326
+ return (reactExports.createElement(Await, { fallback: fallback, function: run$a }, props.children));
30304
30327
  }
30305
- async function run$9() {
30328
+ async function run$a() {
30306
30329
  const actions = Store.getState().actions;
30307
30330
  const argv = Store.getState().argv;
30308
30331
  try {
@@ -30396,9 +30419,9 @@ function format_time(date) {
30396
30419
  }
30397
30420
 
30398
30421
  function GithubApiError() {
30399
- return reactExports.createElement(Await, { fallback: null, function: run$8 });
30422
+ return reactExports.createElement(Await, { fallback: null, function: run$9 });
30400
30423
  }
30401
- async function run$8() {
30424
+ async function run$9() {
30402
30425
  const actions = Store.getState().actions;
30403
30426
  const res = await cli(`gh api https://api.github.com/rate_limit`);
30404
30427
  const res_json = JSON.parse(res.stdout);
@@ -30440,7 +30463,7 @@ function LocalCommitStatus(props) {
30440
30463
  if (argv["mock-metadata"]) {
30441
30464
  return (reactExports.createElement(Await, { fallback: fallback, function: mock_metadata }, props.children));
30442
30465
  }
30443
- return (reactExports.createElement(Await, { fallback: fallback, function: run$7 }, props.children));
30466
+ return (reactExports.createElement(Await, { fallback: fallback, function: run$8 }, props.children));
30444
30467
  }
30445
30468
  async function mock_metadata() {
30446
30469
  const module = await Promise.resolve().then(function () { return metadata; });
@@ -30450,7 +30473,7 @@ async function mock_metadata() {
30450
30473
  state.step = "status";
30451
30474
  });
30452
30475
  }
30453
- async function run$7() {
30476
+ async function run$8() {
30454
30477
  const actions = Store.getState().actions;
30455
30478
  try {
30456
30479
  const commit_range = await range();
@@ -30529,10 +30552,22 @@ function encode(value) {
30529
30552
  return result.padStart(max_char_size, "=");
30530
30553
  }
30531
30554
 
30532
- function Rebase$1() {
30533
- return (reactExports.createElement(Await, { function: Rebase$1.run, fallback: reactExports.createElement(Text, { color: colors.yellow }, "Rebasing commits\u2026") }));
30555
+ function Rebase() {
30556
+ const abort_handler = reactExports.useRef(() => { });
30557
+ reactExports.useEffect(function listen_sigint() {
30558
+ process.once("SIGINT", sigint_handler);
30559
+ return function cleanup() {
30560
+ process.removeListener("SIGINT", sigint_handler);
30561
+ };
30562
+ function sigint_handler() {
30563
+ abort_handler.current();
30564
+ }
30565
+ }, []);
30566
+ return (reactExports.createElement(Await, { fallback: reactExports.createElement(Text, { color: colors.yellow }, "Rebasing commits\u2026"), function: async function () {
30567
+ await Rebase.run({ abort_handler });
30568
+ } }));
30534
30569
  }
30535
- Rebase$1.run = async function run() {
30570
+ Rebase.run = async function run(args) {
30536
30571
  const state = Store.getState();
30537
30572
  const actions = state.actions;
30538
30573
  const branch_name = state.branch_name;
@@ -30544,7 +30579,10 @@ Rebase$1.run = async function run() {
30544
30579
  invariant(commit_range, "commit_range must exist");
30545
30580
  invariant(repo_root, "repo_root must exist");
30546
30581
  // always listen for SIGINT event and restore git state
30547
- process.once("SIGINT", handle_exit);
30582
+ args.abort_handler.current = async function sigint_handler() {
30583
+ actions.output(reactExports.createElement(Text, { color: colors.red }, "\uD83D\uDEA8 Abort"));
30584
+ handle_exit(19);
30585
+ };
30548
30586
  const temp_branch_name = `${branch_name}_${short_id()}`;
30549
30587
  try {
30550
30588
  // actions.debug(`commit_range=${JSON.stringify(commit_range, null, 2)}`);
@@ -30586,9 +30624,8 @@ Rebase$1.run = async function run() {
30586
30624
  const sha_list = picked_commit_list.map((commit) => commit.sha).join(" ");
30587
30625
  await cli(`git cherry-pick --keep-redundant-commits ${sha_list}`);
30588
30626
  }
30589
- // after all commits have been cherry-picked and amended
30590
- // move the branch pointer to the newly created temporary branch
30591
- // now we are locally in sync with github and on the original branch
30627
+ // after all commits have been cherry-picked move the pointer
30628
+ // of original branch to the newly created temporary branch
30592
30629
  await cli(`git branch -f ${branch_name} ${temp_branch_name}`);
30593
30630
  restore_git();
30594
30631
  const next_commit_range = await range();
@@ -30608,7 +30645,7 @@ Rebase$1.run = async function run() {
30608
30645
  actions.error(err.message);
30609
30646
  }
30610
30647
  }
30611
- handle_exit();
30648
+ handle_exit(20);
30612
30649
  }
30613
30650
  // cleanup git operations if cancelled during manual rebase
30614
30651
  function restore_git() {
@@ -30616,8 +30653,6 @@ Rebase$1.run = async function run() {
30616
30653
  // trying to use `await cli(...)` here will silently fail since
30617
30654
  // all children processes receive the SIGINT signal
30618
30655
  const spawn_options = { ignoreExitCode: true };
30619
- // always clean up any patch files
30620
- cli.sync(`rm ${PATCH_FILE$1}`, spawn_options);
30621
30656
  // always hard reset and clean to allow subsequent checkout
30622
30657
  // if there are files checkout will fail and cascade fail subsequent commands
30623
30658
  cli.sync(`git reset --hard`, spawn_options);
@@ -30626,19 +30661,13 @@ Rebase$1.run = async function run() {
30626
30661
  cli.sync(`git checkout ${branch_name}`, spawn_options);
30627
30662
  // ...and cleanup temporary branch
30628
30663
  cli.sync(`git branch -D ${temp_branch_name}`, spawn_options);
30629
- if (commit_range) {
30630
- // ...and cleanup pr group branches
30631
- for (const group of commit_range.group_list) {
30632
- cli.sync(`git branch -D ${group.id}`, spawn_options);
30633
- }
30634
- }
30635
30664
  // restore back to original dir
30636
30665
  if (fs.existsSync(cwd)) {
30637
30666
  process.chdir(cwd);
30638
30667
  }
30639
30668
  cli.sync(`pwd`, spawn_options);
30640
30669
  }
30641
- function handle_exit() {
30670
+ function handle_exit(code) {
30642
30671
  actions.output(reactExports.createElement(Text, { color: colors.yellow },
30643
30672
  "Restoring ",
30644
30673
  reactExports.createElement(Brackets, null, branch_name),
@@ -30648,126 +30677,30 @@ Rebase$1.run = async function run() {
30648
30677
  "Restored ",
30649
30678
  reactExports.createElement(Brackets, null, branch_name),
30650
30679
  "."));
30651
- actions.exit(6);
30680
+ actions.exit(code);
30652
30681
  }
30653
30682
  };
30654
- const PATCH_FILE$1 = "git-stack-cli-patch.patch";
30655
30683
 
30656
30684
  function LocalMergeRebase() {
30657
- return reactExports.createElement(Rebase$1, null);
30685
+ return reactExports.createElement(Rebase, null);
30658
30686
  }
30659
30687
 
30660
- function write(args) {
30661
- const stack_table = table(args);
30662
- let result = args.body;
30663
- if (RE.stack_table_link.test(result)) {
30664
- // replace stack table
30665
- result = result.replace(RE.stack_table_link, stack_table);
30666
- }
30667
- else if (RE.stack_table_legacy.test(result)) {
30668
- // replace stack table
30669
- result = result.replace(RE.stack_table_legacy, stack_table);
30670
- }
30671
- else {
30672
- // append stack table
30673
- result = `${result}\n\n${stack_table}`;
30674
- }
30675
- result = result.trimEnd();
30676
- return result;
30677
- }
30678
- function table(args) {
30679
- const stack_pr_url_list = [...args.pr_url_list];
30680
- const old_stack = parse(args.body);
30681
- // remove existing stack pr urls from the old stack pr urls
30682
- for (const pr_url of stack_pr_url_list) {
30683
- old_stack.delete(pr_url);
30684
- }
30685
- // add remaining old stack pr urls to the front of stack pr url list
30686
- const old_pr_list = Array.from(old_stack.keys());
30687
- old_pr_list.reverse();
30688
- for (const pr_url of old_pr_list) {
30689
- stack_pr_url_list.unshift(pr_url);
30690
- }
30691
- const stack_list = [];
30692
- const num_digits = String(stack_pr_url_list.length).length;
30693
- for (let i = 0; i < stack_pr_url_list.length; i++) {
30694
- const pr_url = stack_pr_url_list[i];
30695
- const selected = args.selected_url === pr_url;
30696
- let icon;
30697
- if (old_stack.has(pr_url)) {
30698
- icon = "✅";
30699
- }
30700
- else if (selected) {
30701
- icon = "👉";
30702
- }
30703
- else {
30704
- icon = "⏳";
30705
- }
30706
- const num = String(i + 1).padStart(num_digits, "0");
30707
- stack_list.push(TEMPLATE.row({ icon, num, pr_url }));
30708
- }
30709
- if (!stack_list.length) {
30710
- return "";
30711
- }
30712
- return TEMPLATE.stack_table_link(["", ...stack_list, "", ""].join("\n"));
30713
- }
30714
- function parse(body) {
30715
- let stack_table_match = body.match(RE.stack_table_link);
30716
- if (!stack_table_match?.groups) {
30717
- stack_table_match = body.match(RE.stack_table_legacy);
30718
- }
30719
- if (!stack_table_match?.groups) {
30720
- return new Map();
30721
- }
30722
- const rows_string = stack_table_match.groups["rows"];
30723
- const row_list = rows_string.split("\n");
30724
- const result = new Map();
30725
- for (const row of row_list) {
30726
- const row_match = row.match(RE.row);
30727
- const parsed_row = row_match?.groups;
30728
- if (!parsed_row) {
30729
- // skip invalid row
30730
- continue;
30731
- }
30732
- if (!RE.pr_url.test(parsed_row.pr_url)) {
30733
- continue;
30734
- }
30735
- result.set(parsed_row.pr_url, parsed_row);
30736
- }
30737
- return result;
30738
- }
30739
- const TEMPLATE = {
30740
- stack_table_legacy(rows) {
30741
- return `#### git stack${rows}`;
30742
- },
30743
- stack_table_link(rows) {
30744
- return `#### [git stack](https://github.com/magus/git-stack-cli)${rows}`;
30745
- },
30746
- row(args) {
30747
- return `- ${args.icon} \`${args.num}\` ${args.pr_url}`;
30748
- },
30749
- };
30750
- const RE = {
30751
- // https://regex101.com/r/kqB9Ft/1
30752
- stack_table_legacy: new RegExp(TEMPLATE.stack_table_legacy("\\s+(?<rows>(?:- [^\r^\n]*(?:[\r\n]+)?)+)")),
30753
- stack_table_link: new RegExp(TEMPLATE.stack_table_link("ROWS")
30754
- .replace("[", "\\[")
30755
- .replace("]", "\\]")
30756
- .replace("(", "\\(")
30757
- .replace(")", "\\)")
30758
- .replace("ROWS", "\\s+(?<rows>(?:- [^\r^\n]*(?:[\r\n]+)?)+)")),
30759
- row: new RegExp(TEMPLATE.row({
30760
- icon: "(?<icon>.+)",
30761
- num: "(?<num>\\d+)",
30762
- pr_url: "(?<pr_url>.+)",
30763
- })),
30764
- pr_url: /^https:\/\/.*$/,
30765
- };
30766
-
30767
30688
  function ManualRebase() {
30768
- return (reactExports.createElement(Await, { fallback: reactExports.createElement(Text, { color: colors.yellow }, "Rebasing commits\u2026"), function: run$6 }));
30689
+ const abort_handler = reactExports.useRef(() => { });
30690
+ reactExports.useEffect(function listen_sigint() {
30691
+ process.once("SIGINT", sigint_handler);
30692
+ return function cleanup() {
30693
+ process.removeListener("SIGINT", sigint_handler);
30694
+ };
30695
+ async function sigint_handler() {
30696
+ abort_handler.current();
30697
+ }
30698
+ }, []);
30699
+ return (reactExports.createElement(Await, { fallback: reactExports.createElement(Text, { color: colors.yellow }, "Rebasing commits\u2026"), function: async function () {
30700
+ await run$7({ abort_handler });
30701
+ } }));
30769
30702
  }
30770
- async function run$6() {
30703
+ async function run$7(args) {
30771
30704
  const state = Store.getState();
30772
30705
  const actions = state.actions;
30773
30706
  const argv = state.argv;
@@ -30780,80 +30713,53 @@ async function run$6() {
30780
30713
  invariant(commit_map, "commit_map must exist");
30781
30714
  invariant(repo_root, "repo_root must exist");
30782
30715
  // always listen for SIGINT event and restore git state
30783
- process.once("SIGINT", handle_exit);
30784
- // get latest merge_base relative to local master
30785
- const merge_base = (await cli(`git merge-base HEAD ${master_branch}`)).stdout;
30786
- // immediately paint all commit to preserve selected commit ranges
30787
- let commit_range = await range(commit_map);
30788
- // reverse group list to ensure we create git revise in correct order
30789
- commit_range.group_list.reverse();
30790
- for (const commit of commit_range.commit_list) {
30791
- const group_from_map = commit_map[commit.sha];
30792
- commit.branch_id = group_from_map.id;
30793
- commit.title = group_from_map.title;
30794
- }
30795
- await GitReviseTodo.execute({
30796
- rebase_group_index: 0,
30797
- rebase_merge_base: merge_base,
30798
- commit_range,
30799
- });
30800
- let DEFAULT_PR_BODY = "";
30801
- if (state.pr_template_body) {
30802
- DEFAULT_PR_BODY = state.pr_template_body;
30803
- }
30716
+ args.abort_handler.current = function sigint_handler() {
30717
+ actions.output(reactExports.createElement(Text, { color: colors.red }, "\uD83D\uDEA8 Abort"));
30718
+ handle_exit(15);
30719
+ };
30804
30720
  const temp_branch_name = `${branch_name}_${short_id()}`;
30805
- commit_range = await range(commit_map);
30806
- // reverse commit list so that we can cherry-pick in order
30807
- commit_range.group_list.reverse();
30808
- let rebase_merge_base = merge_base;
30809
- let rebase_group_index = 0;
30810
- for (let i = 0; i < commit_range.group_list.length; i++) {
30811
- const group = commit_range.group_list[i];
30812
- if (!group.dirty) {
30813
- continue;
30814
- }
30815
- if (i > 0) {
30816
- const prev_group = commit_range.group_list[i - 1];
30817
- const prev_commit = prev_group.commits[prev_group.commits.length - 1];
30818
- rebase_merge_base = prev_commit.sha;
30819
- rebase_group_index = i;
30820
- }
30821
- break;
30822
- }
30823
- actions.debug(`rebase_merge_base = ${rebase_merge_base}`);
30824
- actions.debug(`rebase_group_index = ${rebase_group_index}`);
30825
- // actions.debug(`commit_range=${JSON.stringify(commit_range, null, 2)}`);
30826
30721
  try {
30827
- // must perform rebase from repo root for applying git patch
30828
- process.chdir(repo_root);
30829
- await cli(`pwd`);
30830
- if (argv["rebase"] === "git-revise") {
30831
- await rebase_git_revise();
30832
- }
30833
- else {
30834
- await rebase_cherry_pick();
30722
+ // get latest merge_base relative to local master
30723
+ const merge_base = (await cli(`git merge-base HEAD ${master_branch}`))
30724
+ .stdout;
30725
+ // immediately paint all commit to preserve selected commit ranges
30726
+ let commit_range = await range(commit_map);
30727
+ // reverse group list to ensure we create git revise in correct order
30728
+ commit_range.group_list.reverse();
30729
+ for (const commit of commit_range.commit_list) {
30730
+ const group_from_map = commit_map[commit.sha];
30731
+ commit.branch_id = group_from_map.id;
30732
+ commit.title = group_from_map.title;
30835
30733
  }
30836
- // after all commits have been cherry-picked and amended
30837
- // move the branch pointer to the newly created temporary branch
30838
- // now we are in locally in sync with github and on the original branch
30839
- await cli(`git branch -f ${branch_name} ${temp_branch_name}`);
30840
- restore_git();
30841
- actions.set((state) => {
30842
- state.step = "post-rebase-status";
30734
+ await GitReviseTodo.execute({
30735
+ rebase_group_index: 0,
30736
+ rebase_merge_base: merge_base,
30737
+ commit_range,
30843
30738
  });
30844
- }
30845
- catch (err) {
30846
- actions.error("Unable to rebase.");
30847
- if (err instanceof Error) {
30848
- if (actions.isDebug()) {
30849
- actions.error(err.message);
30739
+ commit_range = await range(commit_map);
30740
+ // reverse commit list so that we can cherry-pick in order
30741
+ commit_range.group_list.reverse();
30742
+ let rebase_merge_base = merge_base;
30743
+ let rebase_group_index = 0;
30744
+ for (let i = 0; i < commit_range.group_list.length; i++) {
30745
+ const group = commit_range.group_list[i];
30746
+ if (!group.dirty) {
30747
+ continue;
30748
+ }
30749
+ if (i > 0) {
30750
+ const prev_group = commit_range.group_list[i - 1];
30751
+ const prev_commit = prev_group.commits[prev_group.commits.length - 1];
30752
+ rebase_merge_base = prev_commit.sha;
30753
+ rebase_group_index = i;
30850
30754
  }
30755
+ break;
30851
30756
  }
30852
- handle_exit();
30853
- }
30854
- async function rebase_git_revise() {
30855
- actions.debug(`rebase_git_revise`);
30856
- actions.output(reactExports.createElement(Text, { color: colors.yellow, wrap: "truncate-end" }, "Rebasing\u2026"));
30757
+ actions.debug(`rebase_merge_base = ${rebase_merge_base}`);
30758
+ actions.debug(`rebase_group_index = ${rebase_group_index}`);
30759
+ // actions.debug(`commit_range=${JSON.stringify(commit_range, null, 2)}`);
30760
+ // must perform rebase from repo root for applying git patch
30761
+ process.chdir(repo_root);
30762
+ await cli(`pwd`);
30857
30763
  // create temporary branch
30858
30764
  await cli(`git checkout -b ${temp_branch_name}`);
30859
30765
  await GitReviseTodo.execute({
@@ -30861,195 +30767,31 @@ async function run$6() {
30861
30767
  rebase_merge_base,
30862
30768
  commit_range,
30863
30769
  });
30864
- // early return since we do not need to sync
30865
- if (!argv.sync) {
30866
- return;
30867
- }
30868
- // in order to sync we walk from rebase_group_index to HEAD
30869
- // checking out each group and syncing to github
30870
- // start from HEAD and work backward to rebase_group_index
30871
- const push_group_list = [];
30872
- let lookback_index = 0;
30873
- for (let i = 0; i < commit_range.group_list.length; i++) {
30874
- const index = commit_range.group_list.length - 1 - i;
30875
- // do not go past rebase_group_index
30876
- if (index < rebase_group_index) {
30877
- break;
30878
- }
30879
- const group = commit_range.group_list[index];
30880
- // console.debug({ i, index, group });
30881
- if (i > 0) {
30882
- const prev_group = commit_range.group_list[index + 1];
30883
- lookback_index += prev_group.commits.length;
30884
- }
30885
- // console.debug(`git show head~${lookback_index}`);
30886
- // push group and lookback_index onto front of push_group_list
30887
- push_group_list.unshift({ group, lookback_index });
30888
- }
30889
- const pr_url_list = commit_range.group_list.map(get_group_url);
30890
- // use push_group_list to sync each group HEAD to github
30891
- for (const push_group of push_group_list) {
30892
- const { group } = push_group;
30893
- // move to temporary branch for resetting to lookback_index to create PR
30894
- await cli(`git checkout -b ${group.id}`);
30895
- // prepare branch for sync, reset to commit at lookback index
30896
- await cli(`git reset --hard HEAD~${push_group.lookback_index}`);
30897
- await sync_group_github({ group, pr_url_list, skip_checkout: true });
30898
- // done, remove temp push branch and move back to temp branch
30899
- await cli(`git checkout ${temp_branch_name}`);
30900
- await cli(`git branch -D ${group.id}`);
30901
- }
30902
- // finally, ensure all prs have the updated stack table from updated pr_url_list
30903
- await update_pr_tables(pr_url_list);
30904
- }
30905
- async function rebase_cherry_pick() {
30906
- actions.debug("rebase_cherry_pick");
30907
- // create temporary branch based on merge base
30908
- await cli(`git checkout -b ${temp_branch_name} ${rebase_merge_base}`);
30909
- const pr_url_list = commit_range.group_list.map(get_group_url);
30910
- for (let i = rebase_group_index; i < commit_range.group_list.length; i++) {
30911
- const group = commit_range.group_list[i];
30912
- invariant(group.base, "group.base must exist");
30913
- actions.output(reactExports.createElement(FormatText, { wrapper: reactExports.createElement(Text, { color: colors.yellow, wrap: "truncate-end" }), message: "Rebasing {group}\u2026", values: {
30914
- group: (reactExports.createElement(Brackets, null, group.pr?.title || group.title || group.id)),
30915
- } }));
30916
- // cherry-pick and amend commits one by one
30917
- for (const commit of group.commits) {
30918
- // ensure clean base to avoid conflicts when applying patch
30919
- await cli(`git clean -fd`);
30920
- // create, apply and cleanup patch
30921
- await cli(`git format-patch -1 ${commit.sha} --stdout > ${PATCH_FILE}`);
30922
- await cli(`git apply ${PATCH_FILE}`);
30923
- await cli(`rm ${PATCH_FILE}`);
30924
- // add all changes to stage
30925
- await cli(`git add --all`);
30926
- const metadata = { id: group.id, title: group.title };
30927
- const new_message = write$1(commit.full_message, metadata);
30928
- const git_commit_comand = [`git commit -m "${new_message}"`];
30929
- if (argv.verify === false) {
30930
- git_commit_comand.push("--no-verify");
30931
- }
30932
- await cli(git_commit_comand);
30933
- }
30934
- await sync_group_github({ group, pr_url_list, skip_checkout: false });
30935
- }
30936
- // finally, ensure all prs have the updated stack table from updated pr_url_list
30937
- await update_pr_tables(pr_url_list);
30938
- }
30939
- async function sync_group_github(args) {
30940
- if (!argv.sync) {
30941
- return;
30942
- }
30943
- const { group, pr_url_list } = args;
30944
- invariant(group.base, "group.base must exist");
30945
- actions.output(reactExports.createElement(FormatText, { wrapper: reactExports.createElement(Text, { color: colors.yellow, wrap: "truncate-end" }), message: "Syncing {group}\u2026", values: {
30946
- group: (reactExports.createElement(Brackets, null, group.pr?.title || group.title || group.id)),
30947
- } }));
30948
- // we may temporarily mark PR as a draft before editing it
30949
- // if it is not already a draft PR, to avoid notification spam
30950
- let is_temp_draft = false;
30951
- // before pushing reset base to master temporarily
30952
- // avoid accidentally pointing to orphaned parent commit
30953
- // should hopefully fix issues where a PR includes a bunch of commits after pushing
30954
- if (group.pr) {
30955
- if (!group.pr.isDraft) {
30956
- is_temp_draft = true;
30957
- }
30958
- if (is_temp_draft) {
30959
- await pr_draft({
30960
- branch: group.id,
30961
- draft: true,
30962
- });
30963
- }
30964
- await pr_edit({
30965
- branch: group.id,
30966
- base: master_branch,
30967
- });
30968
- }
30969
- // push to origin since github requires commit shas to line up perfectly
30970
- const git_push_command = [`git push -f origin HEAD:${group.id}`];
30971
- if (argv.verify === false) {
30972
- git_push_command.push("--no-verify");
30973
- }
30974
- await cli(git_push_command);
30975
- const selected_url = get_group_url(group);
30976
- if (group.pr) {
30977
- // ensure base matches pr in github
30978
- await pr_edit({
30979
- branch: group.id,
30980
- base: group.base,
30981
- body: write({
30982
- body: group.pr.body,
30983
- pr_url_list,
30984
- selected_url,
30985
- }),
30770
+ // after all commits have been modified move the pointer
30771
+ // of original branch to the newly created temporary branch
30772
+ await cli(`git branch -f ${branch_name} ${temp_branch_name}`);
30773
+ restore_git();
30774
+ if (argv.sync) {
30775
+ actions.set((state) => {
30776
+ state.step = "sync-github";
30777
+ state.sync_github = { commit_range, rebase_group_index };
30986
30778
  });
30987
- if (is_temp_draft) {
30988
- // mark pr as ready for review again
30989
- await pr_draft({
30990
- branch: group.id,
30991
- draft: false,
30992
- });
30993
- }
30994
30779
  }
30995
30780
  else {
30996
- if (!args.skip_checkout) {
30997
- // delete local group branch if leftover
30998
- await cli(`git branch -D ${group.id}`, { ignoreExitCode: true });
30999
- // move to temporary branch for creating pr
31000
- await cli(`git checkout -b ${group.id}`);
31001
- }
31002
- // create pr in github
31003
- const pr_url = await pr_create({
31004
- branch: group.id,
31005
- base: group.base,
31006
- title: group.title,
31007
- body: DEFAULT_PR_BODY,
31008
- draft: argv.draft,
30781
+ actions.set((state) => {
30782
+ state.step = "post-rebase-status";
31009
30783
  });
31010
- if (!pr_url) {
31011
- throw new Error("unable to create pr");
31012
- }
31013
- // update pr_url_list with created pr_url
31014
- for (let i = 0; i < pr_url_list.length; i++) {
31015
- const url = pr_url_list[i];
31016
- if (url === selected_url) {
31017
- pr_url_list[i] = pr_url;
31018
- }
31019
- }
31020
- // move back to temp branch
31021
- if (!args.skip_checkout) {
31022
- await cli(`git checkout ${temp_branch_name}`);
31023
- }
31024
30784
  }
31025
30785
  }
31026
- async function update_pr_tables(pr_url_list) {
31027
- if (!argv.sync) {
31028
- return;
30786
+ catch (err) {
30787
+ if (err instanceof Error) {
30788
+ actions.error(err.message);
31029
30789
  }
31030
- for (let i = 0; i < commit_range.group_list.length; i++) {
31031
- const group = commit_range.group_list[i];
31032
- // use the updated pr_url_list to get the actual selected_url
31033
- const selected_url = pr_url_list[i];
31034
- invariant(group.base, "group.base must exist");
31035
- const body = group.pr?.body || DEFAULT_PR_BODY;
31036
- const update_body = write({
31037
- body,
31038
- pr_url_list,
31039
- selected_url,
31040
- });
31041
- if (update_body === body) {
31042
- actions.debug(`Skipping body update for ${selected_url}`);
31043
- }
31044
- else {
31045
- actions.debug(`Update body for ${selected_url}`);
31046
- await pr_edit({
31047
- branch: group.id,
31048
- base: group.base,
31049
- body: update_body,
31050
- });
31051
- }
30790
+ actions.error("Unable to rebase.");
30791
+ if (!argv.verbose) {
30792
+ actions.error("Try again with `--verbose` to see more information.");
31052
30793
  }
30794
+ handle_exit(16);
31053
30795
  }
31054
30796
  // cleanup git operations if cancelled during manual rebase
31055
30797
  function restore_git() {
@@ -31057,8 +30799,6 @@ async function run$6() {
31057
30799
  // trying to use `await cli(...)` here will silently fail since
31058
30800
  // all children processes receive the SIGINT signal
31059
30801
  const spawn_options = { ignoreExitCode: true };
31060
- // always clean up any patch files
31061
- cli.sync(`rm ${PATCH_FILE}`, spawn_options);
31062
30802
  // always hard reset and clean to allow subsequent checkout
31063
30803
  // if there are files checkout will fail and cascade fail subsequent commands
31064
30804
  cli.sync(`git reset --hard`, spawn_options);
@@ -31067,19 +30807,13 @@ async function run$6() {
31067
30807
  cli.sync(`git checkout ${branch_name}`, spawn_options);
31068
30808
  // ...and cleanup temporary branch
31069
30809
  cli.sync(`git branch -D ${temp_branch_name}`, spawn_options);
31070
- if (commit_range) {
31071
- // ...and cleanup pr group branches
31072
- for (const group of commit_range.group_list) {
31073
- cli.sync(`git branch -D ${group.id}`, spawn_options);
31074
- }
31075
- }
31076
30810
  // restore back to original dir
31077
30811
  if (fs.existsSync(cwd)) {
31078
30812
  process.chdir(cwd);
31079
30813
  }
31080
30814
  cli.sync(`pwd`, spawn_options);
31081
30815
  }
31082
- function handle_exit() {
30816
+ function handle_exit(code) {
31083
30817
  actions.output(reactExports.createElement(Text, { color: colors.yellow },
31084
30818
  "Restoring ",
31085
30819
  reactExports.createElement(Brackets, null, branch_name),
@@ -31089,11 +30823,9 @@ async function run$6() {
31089
30823
  "Restored ",
31090
30824
  reactExports.createElement(Brackets, null, branch_name),
31091
30825
  "."));
31092
- actions.exit(5);
30826
+ actions.exit(code);
31093
30827
  }
31094
30828
  }
31095
- const get_group_url = (group) => group.pr?.url || group.id;
31096
- const PATCH_FILE = "git-stack-cli-patch.patch";
31097
30829
 
31098
30830
  function Table(props) {
31099
30831
  if (!props.data.length) {
@@ -31272,9 +31004,9 @@ function get_status_bold(row) {
31272
31004
  }
31273
31005
 
31274
31006
  function PostRebaseStatus() {
31275
- return reactExports.createElement(Await, { fallback: null, function: run$5 });
31007
+ return reactExports.createElement(Await, { fallback: null, function: run$6 });
31276
31008
  }
31277
- async function run$5() {
31009
+ async function run$6() {
31278
31010
  const actions = Store.getState().actions;
31279
31011
  // reset github pr cache before refreshing via commit range below
31280
31012
  actions.reset_pr();
@@ -31304,9 +31036,9 @@ function PreLocalMergeRebase() {
31304
31036
  }
31305
31037
 
31306
31038
  function PreManualRebase() {
31307
- return reactExports.createElement(Await, { fallback: null, function: run$4 });
31039
+ return reactExports.createElement(Await, { fallback: null, function: run$5 });
31308
31040
  }
31309
- async function run$4() {
31041
+ async function run$5() {
31310
31042
  const state = Store.getState();
31311
31043
  const actions = state.actions;
31312
31044
  const repo_root = state.repo_root;
@@ -31325,8 +31057,8 @@ async function run$4() {
31325
31057
  // ./docs/pull_request_template.md
31326
31058
  for (const key of PR_TEMPLATE_KEY_LIST) {
31327
31059
  const pr_template_fn = PR_TEMPLATE[key];
31328
- if (fs.existsSync(pr_template_fn(repo_root))) {
31329
- pr_template_body = fs.readFileSync(pr_template_fn(repo_root), "utf-8");
31060
+ if (await safe_exists(pr_template_fn(repo_root))) {
31061
+ pr_template_body = await fs$1.readFile(pr_template_fn(repo_root), "utf-8");
31330
31062
  actions.output(reactExports.createElement(FormatText, { wrapper: reactExports.createElement(Text, { color: colors.yellow }), message: "Using PR template {pr_filepath}", values: {
31331
31063
  pr_filepath: reactExports.createElement(Brackets, null, pr_template_fn("")),
31332
31064
  } }));
@@ -31335,8 +31067,8 @@ async function run$4() {
31335
31067
  }
31336
31068
  // ./.github/PULL_REQUEST_TEMPLATE/*.md
31337
31069
  let pr_templates = [];
31338
- if (fs.existsSync(PR_TEMPLATE.TemplateDir(repo_root))) {
31339
- pr_templates = fs.readdirSync(PR_TEMPLATE.TemplateDir(repo_root));
31070
+ if (await safe_exists(PR_TEMPLATE.TemplateDir(repo_root))) {
31071
+ pr_templates = await fs$1.readdir(PR_TEMPLATE.TemplateDir(repo_root));
31340
31072
  }
31341
31073
  // check if repo has multiple pr templates
31342
31074
  actions.set((state) => {
@@ -31815,9 +31547,9 @@ const SYMBOL = {
31815
31547
  };
31816
31548
 
31817
31549
  function Status() {
31818
- return reactExports.createElement(Await, { fallback: null, function: run$3 });
31550
+ return reactExports.createElement(Await, { fallback: null, function: run$4 });
31819
31551
  }
31820
- async function run$3() {
31552
+ async function run$4() {
31821
31553
  const state = Store.getState();
31822
31554
  const actions = state.actions;
31823
31555
  const argv = state.argv;
@@ -31862,6 +31594,366 @@ async function run$3() {
31862
31594
  }
31863
31595
  }
31864
31596
 
31597
+ /**
31598
+ * Gets the last element of `array`.
31599
+ *
31600
+ * @static
31601
+ * @memberOf _
31602
+ * @since 0.1.0
31603
+ * @category Array
31604
+ * @param {Array} array The array to query.
31605
+ * @returns {*} Returns the last element of `array`.
31606
+ * @example
31607
+ *
31608
+ * _.last([1, 2, 3]);
31609
+ * // => 3
31610
+ */
31611
+
31612
+ function last(array) {
31613
+ var length = array == null ? 0 : array.length;
31614
+ return length ? array[length - 1] : undefined;
31615
+ }
31616
+
31617
+ var last_1 = last;
31618
+
31619
+ var last$1 = /*@__PURE__*/getDefaultExportFromCjs(last_1);
31620
+
31621
+ function write(args) {
31622
+ const stack_table = table(args);
31623
+ let result = args.body;
31624
+ if (RE.stack_table_link.test(result)) {
31625
+ // replace stack table
31626
+ result = result.replace(RE.stack_table_link, stack_table);
31627
+ }
31628
+ else if (RE.stack_table_legacy.test(result)) {
31629
+ // replace stack table
31630
+ result = result.replace(RE.stack_table_legacy, stack_table);
31631
+ }
31632
+ else {
31633
+ // append stack table
31634
+ result = `${result}\n\n${stack_table}`;
31635
+ }
31636
+ result = result.trimEnd();
31637
+ return result;
31638
+ }
31639
+ function table(args) {
31640
+ const stack_pr_url_list = [...args.pr_url_list];
31641
+ const old_stack = parse(args.body);
31642
+ // remove existing stack pr urls from the old stack pr urls
31643
+ for (const pr_url of stack_pr_url_list) {
31644
+ old_stack.delete(pr_url);
31645
+ }
31646
+ // add remaining old stack pr urls to the front of stack pr url list
31647
+ const old_pr_list = Array.from(old_stack.keys());
31648
+ old_pr_list.reverse();
31649
+ for (const pr_url of old_pr_list) {
31650
+ stack_pr_url_list.unshift(pr_url);
31651
+ }
31652
+ const stack_list = [];
31653
+ const num_digits = String(stack_pr_url_list.length).length;
31654
+ for (let i = 0; i < stack_pr_url_list.length; i++) {
31655
+ const pr_url = stack_pr_url_list[i];
31656
+ const selected = args.selected_url === pr_url;
31657
+ let icon;
31658
+ if (old_stack.has(pr_url)) {
31659
+ icon = "✅";
31660
+ }
31661
+ else if (selected) {
31662
+ icon = "👉";
31663
+ }
31664
+ else {
31665
+ icon = "⏳";
31666
+ }
31667
+ const num = String(i + 1).padStart(num_digits, "0");
31668
+ stack_list.push(TEMPLATE.row({ icon, num, pr_url }));
31669
+ }
31670
+ if (!stack_list.length) {
31671
+ return "";
31672
+ }
31673
+ return TEMPLATE.stack_table_link(["", ...stack_list, "", ""].join("\n"));
31674
+ }
31675
+ function parse(body) {
31676
+ let stack_table_match = body.match(RE.stack_table_link);
31677
+ if (!stack_table_match?.groups) {
31678
+ stack_table_match = body.match(RE.stack_table_legacy);
31679
+ }
31680
+ if (!stack_table_match?.groups) {
31681
+ return new Map();
31682
+ }
31683
+ const rows_string = stack_table_match.groups["rows"];
31684
+ const row_list = rows_string.split("\n");
31685
+ const result = new Map();
31686
+ for (const row of row_list) {
31687
+ const row_match = row.match(RE.row);
31688
+ const parsed_row = row_match?.groups;
31689
+ if (!parsed_row) {
31690
+ // skip invalid row
31691
+ continue;
31692
+ }
31693
+ if (!RE.pr_url.test(parsed_row.pr_url)) {
31694
+ continue;
31695
+ }
31696
+ result.set(parsed_row.pr_url, parsed_row);
31697
+ }
31698
+ return result;
31699
+ }
31700
+ const TEMPLATE = {
31701
+ stack_table_legacy(rows) {
31702
+ return `#### git stack${rows}`;
31703
+ },
31704
+ stack_table_link(rows) {
31705
+ return `#### [git stack](https://github.com/magus/git-stack-cli)${rows}`;
31706
+ },
31707
+ row(args) {
31708
+ return `- ${args.icon} \`${args.num}\` ${args.pr_url}`;
31709
+ },
31710
+ };
31711
+ const RE = {
31712
+ // https://regex101.com/r/kqB9Ft/1
31713
+ stack_table_legacy: new RegExp(TEMPLATE.stack_table_legacy("\\s+(?<rows>(?:- [^\r^\n]*(?:[\r\n]+)?)+)")),
31714
+ stack_table_link: new RegExp(TEMPLATE.stack_table_link("ROWS")
31715
+ .replace("[", "\\[")
31716
+ .replace("]", "\\]")
31717
+ .replace("(", "\\(")
31718
+ .replace(")", "\\)")
31719
+ .replace("ROWS", "\\s+(?<rows>(?:- [^\r^\n]*(?:[\r\n]+)?)+)")),
31720
+ row: new RegExp(TEMPLATE.row({
31721
+ icon: "(?<icon>.+)",
31722
+ num: "(?<num>\\d+)",
31723
+ pr_url: "(?<pr_url>.+)",
31724
+ })),
31725
+ pr_url: /^https:\/\/.*$/,
31726
+ };
31727
+
31728
+ function SyncGithub() {
31729
+ const abort_handler = reactExports.useRef(() => { });
31730
+ reactExports.useEffect(function listen_sigint() {
31731
+ process.once("SIGINT", sigint_handler);
31732
+ return function cleanup() {
31733
+ process.removeListener("SIGINT", sigint_handler);
31734
+ };
31735
+ function sigint_handler() {
31736
+ abort_handler.current();
31737
+ }
31738
+ }, []);
31739
+ return (reactExports.createElement(Await, { fallback: reactExports.createElement(Text, { color: colors.yellow }, "Syncing\u2026"), function: async function () {
31740
+ await run$3({ abort_handler });
31741
+ } }));
31742
+ }
31743
+ async function run$3(args) {
31744
+ const state = Store.getState();
31745
+ const actions = state.actions;
31746
+ const argv = state.argv;
31747
+ const branch_name = state.branch_name;
31748
+ const commit_map = state.commit_map;
31749
+ const master_branch = state.master_branch;
31750
+ const repo_root = state.repo_root;
31751
+ const sync_github = state.sync_github;
31752
+ invariant(branch_name, "branch_name must exist");
31753
+ invariant(commit_map, "commit_map must exist");
31754
+ invariant(repo_root, "repo_root must exist");
31755
+ invariant(sync_github, "sync_github must exist");
31756
+ const commit_range = sync_github.commit_range;
31757
+ const rebase_group_index = sync_github.rebase_group_index;
31758
+ // always listen for SIGINT event and restore pr state
31759
+ args.abort_handler.current = function sigint_handler() {
31760
+ actions.output(reactExports.createElement(Text, { color: colors.red }, "\uD83D\uDEA8 Abort"));
31761
+ handle_exit(17);
31762
+ };
31763
+ let DEFAULT_PR_BODY = "";
31764
+ if (state.pr_template_body) {
31765
+ DEFAULT_PR_BODY = state.pr_template_body;
31766
+ }
31767
+ const push_group_list = get_push_group_list();
31768
+ // for all push targets in push_group_list
31769
+ // things that can be done in parallel are grouped by numbers
31770
+ //
31771
+ // -----------------------------------
31772
+ // 1 (before_push) temp mark draft
31773
+ // --------------------------------------
31774
+ // 2 push simultaneously to github
31775
+ // --------------------------------------
31776
+ // 2 create PR / edit PR
31777
+ // 2 (after_push) undo temp mark draft
31778
+ // --------------------------------------
31779
+ try {
31780
+ const before_push_tasks = [];
31781
+ for (const group of push_group_list) {
31782
+ before_push_tasks.push(before_push({ group }));
31783
+ }
31784
+ await Promise.all(before_push_tasks);
31785
+ // git push -f origin HEAD~6:OtVX7Qvrw HEAD~3:E63ytp5dj HEAD~2:gs-NBabNSjXA HEAD~1:gs-UGVJdKNoD HEAD~0:gs-6LAx-On4
31786
+ const git_push_command = [`git push -f origin`];
31787
+ if (argv.verify === false) {
31788
+ git_push_command.push("--no-verify");
31789
+ }
31790
+ for (const group of push_group_list) {
31791
+ const last_commit = last$1(group.commits);
31792
+ invariant(last_commit, "last_commit must exist");
31793
+ const target = `${last_commit.sha}:${group.id}`;
31794
+ git_push_command.push(target);
31795
+ }
31796
+ await cli(git_push_command);
31797
+ const pr_url_list = commit_range.group_list.map(get_group_url);
31798
+ const after_push_tasks = [];
31799
+ for (const group of push_group_list) {
31800
+ after_push_tasks.push(after_push({ group, pr_url_list }));
31801
+ }
31802
+ await Promise.all(after_push_tasks);
31803
+ // finally, ensure all prs have the updated stack table from updated pr_url_list
31804
+ // this step must come after the after_push since that step may create new PRs
31805
+ // we need the urls for all prs at this step so we run it after the after_push
31806
+ const update_pr_body_tasks = [];
31807
+ for (let i = 0; i < commit_range.group_list.length; i++) {
31808
+ const group = commit_range.group_list[i];
31809
+ // use the updated pr_url_list to get the actual selected_url
31810
+ const selected_url = pr_url_list[i];
31811
+ const task = update_pr_body({ group, selected_url, pr_url_list });
31812
+ update_pr_body_tasks.push(task);
31813
+ }
31814
+ await Promise.all(update_pr_body_tasks);
31815
+ actions.set((state) => {
31816
+ state.step = "post-rebase-status";
31817
+ });
31818
+ }
31819
+ catch (err) {
31820
+ if (err instanceof Error) {
31821
+ actions.error(err.message);
31822
+ }
31823
+ actions.error("Unable to sync.");
31824
+ if (!argv.verbose) {
31825
+ actions.error("Try again with `--verbose` to see more information.");
31826
+ }
31827
+ await handle_exit(18);
31828
+ }
31829
+ function get_push_group_list() {
31830
+ // start from HEAD and work backward to rebase_group_index
31831
+ const push_group_list = [];
31832
+ for (let i = 0; i < commit_range.group_list.length; i++) {
31833
+ const index = commit_range.group_list.length - 1 - i;
31834
+ // do not go past rebase_group_index
31835
+ if (index < rebase_group_index) {
31836
+ break;
31837
+ }
31838
+ const group = commit_range.group_list[index];
31839
+ push_group_list.unshift(group);
31840
+ }
31841
+ return push_group_list;
31842
+ }
31843
+ async function before_push(args) {
31844
+ const { group } = args;
31845
+ invariant(group.base, "group.base must exist");
31846
+ // we may temporarily mark PR as a draft before editing it
31847
+ // if it is not already a draft PR, to avoid notification spam
31848
+ let is_temp_draft = !group.pr?.isDraft;
31849
+ // before pushing reset base to master temporarily
31850
+ // avoid accidentally pointing to orphaned parent commit
31851
+ // should hopefully fix issues where a PR includes a bunch of commits after pushing
31852
+ if (group.pr) {
31853
+ if (!group.pr.isDraft) {
31854
+ is_temp_draft = true;
31855
+ }
31856
+ if (is_temp_draft) {
31857
+ await pr_draft({
31858
+ branch: group.id,
31859
+ draft: true,
31860
+ });
31861
+ }
31862
+ await pr_edit({
31863
+ branch: group.id,
31864
+ base: master_branch,
31865
+ });
31866
+ }
31867
+ }
31868
+ async function after_push(args) {
31869
+ const { group, pr_url_list } = args;
31870
+ invariant(group.base, "group.base must exist");
31871
+ const selected_url = get_group_url(group);
31872
+ if (group.pr) {
31873
+ // ensure base matches pr in github
31874
+ await pr_edit({
31875
+ branch: group.id,
31876
+ base: group.base,
31877
+ body: write({
31878
+ body: group.pr.body,
31879
+ pr_url_list,
31880
+ selected_url,
31881
+ }),
31882
+ });
31883
+ // we may temporarily mark PR as a draft before editing it
31884
+ // if it is not already a draft PR, to avoid notification spam
31885
+ let is_temp_draft = !group.pr?.isDraft;
31886
+ if (is_temp_draft) {
31887
+ // mark pr as ready for review again
31888
+ await pr_draft({
31889
+ branch: group.id,
31890
+ draft: false,
31891
+ });
31892
+ }
31893
+ }
31894
+ else {
31895
+ // create pr in github
31896
+ const pr_url = await pr_create({
31897
+ branch: group.id,
31898
+ base: group.base,
31899
+ title: group.title,
31900
+ body: DEFAULT_PR_BODY,
31901
+ draft: argv.draft,
31902
+ });
31903
+ if (!pr_url) {
31904
+ throw new Error("unable to create pr");
31905
+ }
31906
+ // update pr_url_list with created pr_url
31907
+ for (let i = 0; i < pr_url_list.length; i++) {
31908
+ const url = pr_url_list[i];
31909
+ if (url === selected_url) {
31910
+ pr_url_list[i] = pr_url;
31911
+ }
31912
+ }
31913
+ }
31914
+ }
31915
+ async function update_pr_body(args) {
31916
+ const { group, selected_url, pr_url_list } = args;
31917
+ invariant(group.base, "group.base must exist");
31918
+ const body = group.pr?.body || DEFAULT_PR_BODY;
31919
+ const update_body = write({
31920
+ body,
31921
+ pr_url_list,
31922
+ selected_url,
31923
+ });
31924
+ if (update_body === body) {
31925
+ actions.debug(`Skipping body update for ${selected_url}`);
31926
+ }
31927
+ else {
31928
+ actions.debug(`Update body for ${selected_url}`);
31929
+ await pr_edit({
31930
+ branch: group.id,
31931
+ base: group.base,
31932
+ body: update_body,
31933
+ });
31934
+ }
31935
+ }
31936
+ function handle_exit(code) {
31937
+ actions.output(reactExports.createElement(Text, { color: colors.yellow }, "Restoring PR state\u2026"));
31938
+ for (const group of push_group_list) {
31939
+ // we may temporarily mark PR as a draft before editing it
31940
+ // if it is not already a draft PR, to avoid notification spam
31941
+ let is_temp_draft = !group.pr?.isDraft;
31942
+ // restore PR to non-draft state
31943
+ if (is_temp_draft) {
31944
+ pr_draft({
31945
+ branch: group.id,
31946
+ draft: false,
31947
+ })
31948
+ .catch(actions.error);
31949
+ }
31950
+ }
31951
+ actions.output(reactExports.createElement(Text, { color: colors.yellow }, "Restored PR state."));
31952
+ actions.exit(code);
31953
+ }
31954
+ }
31955
+ const get_group_url = (group) => group.pr?.url || group.id;
31956
+
31865
31957
  function Main() {
31866
31958
  const step = Store.useState((state) => state.step);
31867
31959
  switch (step) {
@@ -31883,6 +31975,8 @@ function Main() {
31883
31975
  return reactExports.createElement(PreManualRebase, null);
31884
31976
  case "manual-rebase":
31885
31977
  return reactExports.createElement(ManualRebase, null);
31978
+ case "sync-github":
31979
+ return reactExports.createElement(SyncGithub, null);
31886
31980
  case "post-rebase-status":
31887
31981
  return reactExports.createElement(PostRebaseStatus, null);
31888
31982
  default:
@@ -31943,8 +32037,8 @@ function RebaseCheck(props) {
31943
32037
  try {
31944
32038
  const git_dir = (await cli(`git rev-parse --absolute-git-dir`)).stdout;
31945
32039
  let is_rebase = false;
31946
- is_rebase ||= fs.existsSync(path.join(git_dir, "rebase-apply"));
31947
- is_rebase ||= fs.existsSync(path.join(git_dir, "rebase-merge"));
32040
+ is_rebase ||= await safe_exists(path.join(git_dir, "rebase-apply"));
32041
+ is_rebase ||= await safe_exists(path.join(git_dir, "rebase-merge"));
31948
32042
  const status = is_rebase ? "prompt" : "done";
31949
32043
  patch({ status });
31950
32044
  }
@@ -32151,7 +32245,7 @@ function MaybeMain() {
32151
32245
  else if (positional_list.has("rebase")) {
32152
32246
  return (reactExports.createElement(GatherMetadata, null,
32153
32247
  reactExports.createElement(LocalCommitStatus, null,
32154
- reactExports.createElement(Rebase$1, null))));
32248
+ reactExports.createElement(Rebase, null))));
32155
32249
  }
32156
32250
  return (reactExports.createElement(DirtyCheck, null,
32157
32251
  !argv.verbose ? null : reactExports.createElement(GithubApiError, null),
@@ -32490,14 +32584,14 @@ function ui (opts) {
32490
32584
 
32491
32585
  function escalade (start, callback) {
32492
32586
  let dir = path$1.resolve('.', start);
32493
- let tmp, stats = fs$1.statSync(dir);
32587
+ let tmp, stats = fs$2.statSync(dir);
32494
32588
 
32495
32589
  if (!stats.isDirectory()) {
32496
32590
  dir = path$1.dirname(dir);
32497
32591
  }
32498
32592
 
32499
32593
  while (true) {
32500
- tmp = callback(dir, fs$1.readdirSync(dir));
32594
+ tmp = callback(dir, fs$2.readdirSync(dir));
32501
32595
  if (tmp) return path$1.resolve(dir, tmp);
32502
32596
  dir = path$1.dirname(tmp = dir);
32503
32597
  if (tmp === dir) break;
@@ -33703,7 +33797,7 @@ const parser = new YargsParser({
33703
33797
  }
33704
33798
  else if (path.match(/\.json$/)) {
33705
33799
  // Addresses: https://github.com/yargs/yargs/issues/2040
33706
- return JSON.parse(fs$1.readFileSync(path, 'utf8'));
33800
+ return JSON.parse(fs$2.readFileSync(path, 'utf8'));
33707
33801
  }
33708
33802
  else {
33709
33803
  throw Error('only .json config files are supported in ESM');
@@ -33751,14 +33845,14 @@ class YError extends Error {
33751
33845
 
33752
33846
  var shim$3 = {
33753
33847
  fs: {
33754
- readFileSync: fs$1.readFileSync,
33755
- writeFile: fs$1.writeFile
33848
+ readFileSync: fs$2.readFileSync,
33849
+ writeFile: fs$2.writeFile
33756
33850
  },
33757
33851
  format: util.format,
33758
33852
  resolve: path$1.resolve,
33759
33853
  exists: (file) => {
33760
33854
  try {
33761
- return fs$1.statSync(file).isFile();
33855
+ return fs$2.statSync(file).isFile();
33762
33856
  }
33763
33857
  catch (err) {
33764
33858
  return false;
@@ -33989,7 +34083,7 @@ var shim$1 = {
33989
34083
  nextTick: process.nextTick,
33990
34084
  stdColumns: typeof process.stdout.columns !== 'undefined' ? process.stdout.columns : null
33991
34085
  },
33992
- readFileSync: fs$1.readFileSync,
34086
+ readFileSync: fs$2.readFileSync,
33993
34087
  require: () => {
33994
34088
  throw new YError(REQUIRE_ERROR)
33995
34089
  },
@@ -37479,15 +37573,11 @@ async function command() {
37479
37573
  .wrap(123)
37480
37574
  // disallow unknown options
37481
37575
  .strict()
37482
- .version("1.12.0" )
37576
+ .version("1.13.1" )
37483
37577
  .showHidden("show-hidden", "Show hidden options via `git stack help --show-hidden`")
37484
37578
  .help("help", "Show usage via `git stack help`")
37485
37579
  .argv;
37486
37580
  }
37487
- const Rebase = Object.freeze({
37488
- "git-revise": "git-revise",
37489
- "cherry-pick": "cherry-pick",
37490
- });
37491
37581
  const GlobalOptions = {
37492
37582
  verbose: {
37493
37583
  type: "boolean",
@@ -37520,16 +37610,6 @@ const DefaultOptions = {
37520
37610
  default: true,
37521
37611
  description: "Run git hooks such as pre-commit and pre-push, disable with --no-verify",
37522
37612
  },
37523
- "rebase": {
37524
- type: "string",
37525
- choices: [Rebase["git-revise"], Rebase["cherry-pick"]],
37526
- default: Rebase["git-revise"],
37527
- description: [
37528
- "Strategy used for syncing branches",
37529
- `${Rebase["git-revise"]}: perform faster in-memory rebase`,
37530
- `${Rebase["cherry-pick"]}: use disk and incrementally rebase each commit`,
37531
- ].join(" | "),
37532
- },
37533
37613
  "update": {
37534
37614
  type: "boolean",
37535
37615
  alias: ["u", "upgrade"],
@@ -37578,7 +37658,10 @@ const FixupOptions = {
37578
37658
 
37579
37659
  command()
37580
37660
  .then((argv) => {
37581
- const ink = render(reactExports.createElement(App, null));
37661
+ const ink = render(reactExports.createElement(App, null), {
37662
+ // If true, each update will be rendered as a separate output, without replacing the previous one.
37663
+ // debug: true,
37664
+ });
37582
37665
  Store.setState((state) => {
37583
37666
  state.ink = ink;
37584
37667
  state.process_argv = process.argv;