git-stack-cli 1.13.1 → 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -83,6 +83,19 @@ To update your local branch with the latest changes in the remote branch (e.g. `
83
83
  git stack rebase
84
84
  ```
85
85
 
86
+ ### Customizing branch name
87
+
88
+ By default `git stack` generates a unique branch name such as `gs-3cmrMBSUj`.
89
+
90
+ You can specify a prefix for the generated branch name by using either the environment variable or the command line option.
91
+ In the example below branches would be generated such as `dev/magus/gs-3cmrMBSUj`.
92
+
93
+ ```bash
94
+ GIT_STACK_BRANCH_PREFIX="dev/magus/" git stack
95
+
96
+ git stack --branch-prefix="dev/magus/"
97
+ ```
98
+
86
99
  ## Why?
87
100
 
88
101
  The goal of `git stack` is to combine the **simplicity of developing in a single branch** in order to **preserve your commit history** while also **grouping commits into pull requests for code review**.
@@ -14912,7 +14912,7 @@ var isSafeInteger = hasNativeIsSafeInteger
14912
14912
  // IE11 does not support y and u.
14913
14913
  var REGEX_SUPPORTS_U_AND_Y = true;
14914
14914
  try {
14915
- var re = RE$5('([^\\p{White_Space}\\p{Pattern_Syntax}]*)', 'yu');
14915
+ var re = RE$6('([^\\p{White_Space}\\p{Pattern_Syntax}]*)', 'yu');
14916
14916
  /**
14917
14917
  * legacy Edge or Xbox One browser
14918
14918
  * Unicode flag support: supported
@@ -15009,14 +15009,14 @@ var trimEnd = hasTrimEnd
15009
15009
  return s.replace(SPACE_SEPARATOR_END_REGEX, '');
15010
15010
  };
15011
15011
  // Prevent minifier to translate new RegExp to literal form that might cause syntax error on IE11.
15012
- function RE$5(s, flag) {
15012
+ function RE$6(s, flag) {
15013
15013
  return new RegExp(s, flag);
15014
15014
  }
15015
15015
  // #endregion
15016
15016
  var matchIdentifierAtIndex;
15017
15017
  if (REGEX_SUPPORTS_U_AND_Y) {
15018
15018
  // Native
15019
- var IDENTIFIER_PREFIX_RE_1 = RE$5('([^\\p{White_Space}\\p{Pattern_Syntax}]*)', 'yu');
15019
+ var IDENTIFIER_PREFIX_RE_1 = RE$6('([^\\p{White_Space}\\p{Pattern_Syntax}]*)', 'yu');
15020
15020
  matchIdentifierAtIndex = function matchIdentifierAtIndex(s, index) {
15021
15021
  var _a;
15022
15022
  IDENTIFIER_PREFIX_RE_1.lastIndex = index;
@@ -26941,15 +26941,15 @@ match_group.safe = (value, re, group) => {
26941
26941
 
26942
26942
  function auth_status(output) {
26943
26943
  let username;
26944
- username = match_group.safe(output, RE$4.logged_in_as, "username");
26944
+ username = match_group.safe(output, RE$5.logged_in_as, "username");
26945
26945
  if (username)
26946
26946
  return username;
26947
- username = match_group.safe(output, RE$4.logged_in_account, "username");
26947
+ username = match_group.safe(output, RE$5.logged_in_account, "username");
26948
26948
  if (username)
26949
26949
  return username;
26950
26950
  return null;
26951
26951
  }
26952
- const RE$4 = {
26952
+ const RE$5 = {
26953
26953
  // Logged in to github.com as magus
26954
26954
  logged_in_as: /Logged in to github.com as (?<username>[^\s]+)/,
26955
26955
  logged_in_account: /Logged in to github.com account (?<username>[^\s]+)/,
@@ -29663,10 +29663,10 @@ var cloneDeep$1 = /*@__PURE__*/getDefaultExportFromCjs(cloneDeep_1);
29663
29663
  // escape double-quote for cli
29664
29664
  function safe_quote(value) {
29665
29665
  let result = value;
29666
- result = result.replace(RE$3.all_double_quote, '\\"');
29666
+ result = result.replace(RE$4.all_double_quote, '\\"');
29667
29667
  return result;
29668
29668
  }
29669
- const RE$3 = {
29669
+ const RE$4 = {
29670
29670
  all_double_quote: /"/g,
29671
29671
  };
29672
29672
 
@@ -29684,12 +29684,12 @@ function write$1(message, values) {
29684
29684
  }
29685
29685
  function read(message) {
29686
29686
  const values = { id: null, title: null };
29687
- const match_id = message.match(RE$2.stack_id);
29687
+ const match_id = message.match(RE$3.stack_id);
29688
29688
  if (match_id?.groups) {
29689
29689
  values.id = match_id.groups["id"];
29690
29690
  invariant(values.id, "id must exist");
29691
29691
  }
29692
- const match_title = message.match(RE$2.group_title);
29692
+ const match_title = message.match(RE$3.group_title);
29693
29693
  if (match_title?.groups) {
29694
29694
  values.title = match_title.groups["title"];
29695
29695
  }
@@ -29698,8 +29698,8 @@ function read(message) {
29698
29698
  function remove(message) {
29699
29699
  let result = message;
29700
29700
  // remove metadata
29701
- result = result.replace(new RegExp(RE$2.stack_id, "gmi"), "");
29702
- result = result.replace(new RegExp(RE$2.group_title, "gmi"), "");
29701
+ result = result.replace(new RegExp(RE$3.stack_id, "gmi"), "");
29702
+ result = result.replace(new RegExp(RE$3.group_title, "gmi"), "");
29703
29703
  result = result.trimEnd();
29704
29704
  return result;
29705
29705
  }
@@ -29711,7 +29711,7 @@ const TEMPLATE$1 = {
29711
29711
  return `git-stack-title: ${title}`;
29712
29712
  },
29713
29713
  };
29714
- const RE$2 = {
29714
+ const RE$3 = {
29715
29715
  // https://regex101.com/r/wLmGVq/1
29716
29716
  stack_id: new RegExp(`${TEMPLATE$1.stack_id("(?<id>[^\\s]+)")}`, "i"),
29717
29717
  group_title: new RegExp(TEMPLATE$1.group_title("(?<title>[^\\n^\\r]+)"), "i"),
@@ -29782,11 +29782,23 @@ async function pr_status(branch) {
29782
29782
  }
29783
29783
  async function pr_create(args) {
29784
29784
  const title = safe_quote(args.title);
29785
- let command = `gh pr create --fill --head ${args.branch} --base ${args.base} --title="${title}" --body="${args.body}"`;
29785
+ // explicit refs/heads head branch to avoid creation failing
29786
+ //
29787
+ // ❯ gh pr create --head origin/gs-ED2etrzv2 --base gs-6LAx-On45 --title="2024-01-05 test" --body=""
29788
+ // pull request create failed: GraphQL: Head sha can't be blank, Base sha can't be blank, No commits between gs-6LAx-On45 and origin/gs-ED2etrzv2, Head ref must be a branch (createPullRequest)
29789
+ //
29790
+ // https://github.com/cli/cli/issues/5465
29791
+ let command_parts = [
29792
+ "gh pr create",
29793
+ `--head refs/heads/${args.branch}`,
29794
+ `--base ${args.base}`,
29795
+ `--title="${title}"`,
29796
+ `--body="${args.body}"`,
29797
+ ];
29786
29798
  if (args.draft) {
29787
- command += " --draft";
29799
+ command_parts.push("--draft");
29788
29800
  }
29789
- const cli_result = await cli(command);
29801
+ const cli_result = await cli(command_parts);
29790
29802
  if (cli_result.code !== 0) {
29791
29803
  handle_error(cli_result.output);
29792
29804
  return null;
@@ -29799,8 +29811,7 @@ async function pr_edit(args) {
29799
29811
  const body_file = await write_body_file(args);
29800
29812
  command_parts.push(`--body-file="${body_file}"`);
29801
29813
  }
29802
- const command = command_parts.join(" ");
29803
- const cli_result = await cli(command);
29814
+ const cli_result = await cli(command_parts);
29804
29815
  if (cli_result.code !== 0) {
29805
29816
  handle_error(cli_result.output);
29806
29817
  }
@@ -29867,11 +29878,19 @@ function handle_error(output) {
29867
29878
  async function write_body_file(args) {
29868
29879
  invariant(args.body, "args.body must exist");
29869
29880
  const temp_dir = os.tmpdir();
29870
- const temp_path = path.join(temp_dir, `git-stack-body-${args.base}`);
29881
+ // ensure unique filename is safe for filesystem
29882
+ // base (group id) might contain slashes, e.g. dev/magus/gs-3cmrMBSUj
29883
+ // the flashes would mess up the filesystem path to this file
29884
+ let temp_filename = `git-stack-body-${args.base}`;
29885
+ temp_filename = temp_filename.replace(RE$2.non_alphanumeric_dash, "-");
29886
+ const temp_path = path.join(temp_dir, temp_filename);
29871
29887
  await safe_rm(temp_path);
29872
29888
  await fs$1.writeFile(temp_path, args.body);
29873
29889
  return temp_path;
29874
29890
  }
29891
+ const RE$2 = {
29892
+ non_alphanumeric_dash: /[^a-zA-Z0-9_-]+/g,
29893
+ };
29875
29894
 
29876
29895
  async function range(commit_group_map) {
29877
29896
  const master_branch = Store.getState().master_branch;
@@ -31528,8 +31547,20 @@ function SelectCommitRangesInternal(props) {
31528
31547
  reactExports.createElement(FormatText, { wrapper: reactExports.createElement(Text, { color: colors.gray }), message: "Press {enter} to toggle commit selection", values: {
31529
31548
  enter: (reactExports.createElement(Text, { bold: true, color: colors.green }, SYMBOL.enter)),
31530
31549
  } }))));
31550
+ function get_group_id() {
31551
+ let branch_prefix = "";
31552
+ // branch prefix via cli flag or env var
31553
+ // cli flag takes precedence since it is more explicit
31554
+ if (argv["branch-prefix"]) {
31555
+ branch_prefix = argv["branch-prefix"];
31556
+ }
31557
+ else if (process.env.GIT_STACK_BRANCH_PREFIX) {
31558
+ branch_prefix = process.env.GIT_STACK_BRANCH_PREFIX;
31559
+ }
31560
+ return `${branch_prefix}${gs_short_id()}`;
31561
+ }
31531
31562
  function submit_group_input(title) {
31532
- const id = gs_short_id();
31563
+ const id = get_group_id();
31533
31564
  actions.output(reactExports.createElement(FormatText, { wrapper: reactExports.createElement(Text, { dimColor: true }), message: "Created new group {group} {note}", values: {
31534
31565
  group: reactExports.createElement(Brackets, null, title),
31535
31566
  note: reactExports.createElement(Parens, null, id),
@@ -31782,7 +31813,6 @@ async function run$3(args) {
31782
31813
  before_push_tasks.push(before_push({ group }));
31783
31814
  }
31784
31815
  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
31816
  const git_push_command = [`git push -f origin`];
31787
31817
  if (argv.verify === false) {
31788
31818
  git_push_command.push("--no-verify");
@@ -31790,7 +31820,24 @@ async function run$3(args) {
31790
31820
  for (const group of push_group_list) {
31791
31821
  const last_commit = last$1(group.commits);
31792
31822
  invariant(last_commit, "last_commit must exist");
31793
- const target = `${last_commit.sha}:${group.id}`;
31823
+ // explicit refs/heads head branch to avoid push failing
31824
+ //
31825
+ // ❯ git push -f origin --no-verify f6e249051b4820a03deb957ddebc19acfd7dfd7c:gs-ED2etrzv2
31826
+ // error: The destination you provided is not a full refname (i.e.,
31827
+ // starting with "refs/"). We tried to guess what you meant by:
31828
+ //
31829
+ // - Looking for a ref that matches 'gs-ED2etrzv2' on the remote side.
31830
+ // - Checking if the <src> being pushed ('f6e249051b4820a03deb957ddebc19acfd7dfd7c')
31831
+ // is a ref in "refs/{heads,tags}/". If so we add a corresponding
31832
+ // refs/{heads,tags}/ prefix on the remote side.
31833
+ //
31834
+ // Neither worked, so we gave up. You must fully qualify the ref.
31835
+ // hint: The <src> part of the refspec is a commit object.
31836
+ // hint: Did you mean to create a new branch by pushing to
31837
+ // hint: 'f6e249051b4820a03deb957ddebc19acfd7dfd7c:refs/heads/gs-ED2etrzv2'?
31838
+ // error: failed to push some refs to 'github.com:magus/git-multi-diff-playground.git'
31839
+ //
31840
+ const target = `${last_commit.sha}:refs/heads/${group.id}`;
31794
31841
  git_push_command.push(target);
31795
31842
  }
31796
31843
  await cli(git_push_command);
@@ -37573,7 +37620,7 @@ async function command() {
37573
37620
  .wrap(123)
37574
37621
  // disallow unknown options
37575
37622
  .strict()
37576
- .version("1.13.1" )
37623
+ .version("1.14.0" )
37577
37624
  .showHidden("show-hidden", "Show hidden options via `git stack help --show-hidden`")
37578
37625
  .help("help", "Show usage via `git stack help`")
37579
37626
  .argv;
@@ -37627,17 +37674,22 @@ const DefaultOptions = {
37627
37674
  default: false,
37628
37675
  description: "Open all PRs as drafts",
37629
37676
  },
37630
- "write-state-json": {
37631
- hidden: true,
37632
- type: "boolean",
37633
- default: false,
37634
- description: "Write state to local json file for debugging",
37677
+ "branch-prefix": {
37678
+ type: "string",
37679
+ default: "",
37680
+ description: "Prefix for generated branch names, e.g. dev/magus/",
37635
37681
  },
37636
37682
  "template": {
37637
37683
  type: "boolean",
37638
37684
  default: true,
37639
37685
  description: "Use automatic Github PR template, e.g. .github/pull_request_template.md, disable with --no-template",
37640
37686
  },
37687
+ "write-state-json": {
37688
+ hidden: true,
37689
+ type: "boolean",
37690
+ default: false,
37691
+ description: "Write state to local json file for debugging",
37692
+ },
37641
37693
  "mock-metadata": {
37642
37694
  hidden: true,
37643
37695
  type: "boolean",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-stack-cli",
3
- "version": "1.13.1",
3
+ "version": "1.14.0",
4
4
  "description": "",
5
5
  "author": "magus",
6
6
  "license": "MIT",
@@ -387,8 +387,21 @@ function SelectCommitRangesInternal(props: Props) {
387
387
  </Ink.Box>
388
388
  );
389
389
 
390
+ function get_group_id() {
391
+ let branch_prefix = "";
392
+
393
+ // branch prefix via cli flag or env var
394
+ // cli flag takes precedence since it is more explicit
395
+ if (argv["branch-prefix"]) {
396
+ branch_prefix = argv["branch-prefix"];
397
+ } else if (process.env.GIT_STACK_BRANCH_PREFIX) {
398
+ branch_prefix = process.env.GIT_STACK_BRANCH_PREFIX;
399
+ }
400
+
401
+ return `${branch_prefix}${gs_short_id()}`;
402
+ }
390
403
  function submit_group_input(title: string) {
391
- const id = gs_short_id();
404
+ const id = get_group_id();
392
405
 
393
406
  actions.output(
394
407
  <FormatText
@@ -93,8 +93,6 @@ async function run(args: Args) {
93
93
 
94
94
  await Promise.all(before_push_tasks);
95
95
 
96
- // git push -f origin HEAD~6:OtVX7Qvrw HEAD~3:E63ytp5dj HEAD~2:gs-NBabNSjXA HEAD~1:gs-UGVJdKNoD HEAD~0:gs-6LAx-On4
97
-
98
96
  const git_push_command = [`git push -f origin`];
99
97
 
100
98
  if (argv.verify === false) {
@@ -104,7 +102,25 @@ async function run(args: Args) {
104
102
  for (const group of push_group_list) {
105
103
  const last_commit = last(group.commits);
106
104
  invariant(last_commit, "last_commit must exist");
107
- const target = `${last_commit.sha}:${group.id}`;
105
+
106
+ // explicit refs/heads head branch to avoid push failing
107
+ //
108
+ // ❯ git push -f origin --no-verify f6e249051b4820a03deb957ddebc19acfd7dfd7c:gs-ED2etrzv2
109
+ // error: The destination you provided is not a full refname (i.e.,
110
+ // starting with "refs/"). We tried to guess what you meant by:
111
+ //
112
+ // - Looking for a ref that matches 'gs-ED2etrzv2' on the remote side.
113
+ // - Checking if the <src> being pushed ('f6e249051b4820a03deb957ddebc19acfd7dfd7c')
114
+ // is a ref in "refs/{heads,tags}/". If so we add a corresponding
115
+ // refs/{heads,tags}/ prefix on the remote side.
116
+ //
117
+ // Neither worked, so we gave up. You must fully qualify the ref.
118
+ // hint: The <src> part of the refspec is a commit object.
119
+ // hint: Did you mean to create a new branch by pushing to
120
+ // hint: 'f6e249051b4820a03deb957ddebc19acfd7dfd7c:refs/heads/gs-ED2etrzv2'?
121
+ // error: failed to push some refs to 'github.com:magus/git-multi-diff-playground.git'
122
+ //
123
+ const target = `${last_commit.sha}:refs/heads/${group.id}`;
108
124
  git_push_command.push(target);
109
125
  }
110
126
 
package/src/command.ts CHANGED
@@ -111,11 +111,10 @@ const DefaultOptions = {
111
111
  description: "Open all PRs as drafts",
112
112
  },
113
113
 
114
- "write-state-json": {
115
- hidden: true,
116
- type: "boolean",
117
- default: false,
118
- description: "Write state to local json file for debugging",
114
+ "branch-prefix": {
115
+ type: "string",
116
+ default: "",
117
+ description: "Prefix for generated branch names, e.g. dev/magus/",
119
118
  },
120
119
 
121
120
  "template": {
@@ -125,6 +124,13 @@ const DefaultOptions = {
125
124
  "Use automatic Github PR template, e.g. .github/pull_request_template.md, disable with --no-template",
126
125
  },
127
126
 
127
+ "write-state-json": {
128
+ hidden: true,
129
+ type: "boolean",
130
+ default: false,
131
+ description: "Write state to local json file for debugging",
132
+ },
133
+
128
134
  "mock-metadata": {
129
135
  hidden: true,
130
136
  type: "boolean",
@@ -123,13 +123,26 @@ type CreatePullRequestArgs = {
123
123
 
124
124
  export async function pr_create(args: CreatePullRequestArgs) {
125
125
  const title = safe_quote(args.title);
126
- let command = `gh pr create --fill --head ${args.branch} --base ${args.base} --title="${title}" --body="${args.body}"`;
126
+
127
+ // explicit refs/heads head branch to avoid creation failing
128
+ //
129
+ // ❯ gh pr create --head origin/gs-ED2etrzv2 --base gs-6LAx-On45 --title="2024-01-05 test" --body=""
130
+ // pull request create failed: GraphQL: Head sha can't be blank, Base sha can't be blank, No commits between gs-6LAx-On45 and origin/gs-ED2etrzv2, Head ref must be a branch (createPullRequest)
131
+ //
132
+ // https://github.com/cli/cli/issues/5465
133
+ let command_parts = [
134
+ "gh pr create",
135
+ `--head refs/heads/${args.branch}`,
136
+ `--base ${args.base}`,
137
+ `--title="${title}"`,
138
+ `--body="${args.body}"`,
139
+ ];
127
140
 
128
141
  if (args.draft) {
129
- command += " --draft";
142
+ command_parts.push("--draft");
130
143
  }
131
144
 
132
- const cli_result = await cli(command);
145
+ const cli_result = await cli(command_parts);
133
146
 
134
147
  if (cli_result.code !== 0) {
135
148
  handle_error(cli_result.output);
@@ -153,9 +166,7 @@ export async function pr_edit(args: EditPullRequestArgs) {
153
166
  command_parts.push(`--body-file="${body_file}"`);
154
167
  }
155
168
 
156
- const command = command_parts.join(" ");
157
-
158
- const cli_result = await cli(command);
169
+ const cli_result = await cli(command_parts);
159
170
 
160
171
  if (cli_result.code !== 0) {
161
172
  handle_error(cli_result.output);
@@ -247,7 +258,14 @@ async function write_body_file(args: EditPullRequestArgs) {
247
258
  invariant(args.body, "args.body must exist");
248
259
 
249
260
  const temp_dir = os.tmpdir();
250
- const temp_path = path.join(temp_dir, `git-stack-body-${args.base}`);
261
+
262
+ // ensure unique filename is safe for filesystem
263
+ // base (group id) might contain slashes, e.g. dev/magus/gs-3cmrMBSUj
264
+ // the flashes would mess up the filesystem path to this file
265
+ let temp_filename = `git-stack-body-${args.base}`;
266
+ temp_filename = temp_filename.replace(RE.non_alphanumeric_dash, "-");
267
+
268
+ const temp_path = path.join(temp_dir, temp_filename);
251
269
 
252
270
  await safe_rm(temp_path);
253
271
 
@@ -284,3 +302,7 @@ export type PullRequest = {
284
302
  url: string;
285
303
  isDraft: boolean;
286
304
  };
305
+
306
+ const RE = {
307
+ non_alphanumeric_dash: /[^a-zA-Z0-9_-]+/g,
308
+ };
@@ -5,5 +5,6 @@ declare namespace NodeJS {
5
5
  CLI_VERSION?: string;
6
6
  GIT_STACK_STANDALONE?: "true" | "false";
7
7
  GIT_SEQUENCE_EDITOR_SCRIPT?: string;
8
+ GIT_STACK_BRANCH_PREFIX?: string;
8
9
  }
9
10
  }