git-stack-cli 1.10.0 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -30220,9 +30220,9 @@ function DirtyCheck(props) {
30220
30220
 
30221
30221
  function GatherMetadata(props) {
30222
30222
  const fallback = (reactExports.createElement(Text, { color: colors.yellow }, "Gathering local git information\u2026"));
30223
- return (reactExports.createElement(Await, { fallback: fallback, function: run$9 }, props.children));
30223
+ return (reactExports.createElement(Await, { fallback: fallback, function: run$8 }, props.children));
30224
30224
  }
30225
- async function run$9() {
30225
+ async function run$8() {
30226
30226
  const actions = Store.getState().actions;
30227
30227
  const argv = Store.getState().argv;
30228
30228
  try {
@@ -30316,9 +30316,9 @@ function format_time(date) {
30316
30316
  }
30317
30317
 
30318
30318
  function GithubApiError() {
30319
- return reactExports.createElement(Await, { fallback: null, function: run$8 });
30319
+ return reactExports.createElement(Await, { fallback: null, function: run$7 });
30320
30320
  }
30321
- async function run$8() {
30321
+ async function run$7() {
30322
30322
  const actions = Store.getState().actions;
30323
30323
  const res = await cli(`gh api https://api.github.com/rate_limit`);
30324
30324
  const res_json = JSON.parse(res.stdout);
@@ -30360,7 +30360,7 @@ function LocalCommitStatus(props) {
30360
30360
  if (argv["mock-metadata"]) {
30361
30361
  return (reactExports.createElement(Await, { fallback: fallback, function: mock_metadata }, props.children));
30362
30362
  }
30363
- return (reactExports.createElement(Await, { fallback: fallback, function: run$7 }, props.children));
30363
+ return (reactExports.createElement(Await, { fallback: fallback, function: run$6 }, props.children));
30364
30364
  }
30365
30365
  async function mock_metadata() {
30366
30366
  const module = await Promise.resolve().then(function () { return metadata; });
@@ -30370,7 +30370,7 @@ async function mock_metadata() {
30370
30370
  state.step = "status";
30371
30371
  });
30372
30372
  }
30373
- async function run$7() {
30373
+ async function run$6() {
30374
30374
  const actions = Store.getState().actions;
30375
30375
  try {
30376
30376
  const commit_range = await range();
@@ -30449,10 +30449,10 @@ function encode(value) {
30449
30449
  return result.padStart(max_char_size, "=");
30450
30450
  }
30451
30451
 
30452
- function LocalMergeRebase() {
30453
- return (reactExports.createElement(Await, { fallback: reactExports.createElement(Text, { color: colors.yellow }, "Rebasing commits\u2026"), function: run$6 }));
30452
+ function Rebase$1() {
30453
+ return (reactExports.createElement(Await, { function: Rebase$1.run, fallback: reactExports.createElement(Text, { color: colors.yellow }, "Rebasing commits\u2026") }));
30454
30454
  }
30455
- async function run$6() {
30455
+ Rebase$1.run = async function run() {
30456
30456
  const state = Store.getState();
30457
30457
  const actions = state.actions;
30458
30458
  const branch_name = state.branch_name;
@@ -30512,6 +30512,10 @@ async function run$6() {
30512
30512
  await cli(`git branch -f ${branch_name} ${temp_branch_name}`);
30513
30513
  restore_git();
30514
30514
  const next_commit_range = await range();
30515
+ actions.output(reactExports.createElement(FormatText, { wrapper: reactExports.createElement(Text, { color: colors.green }), message: "\u2705 {branch_name} in sync with {origin_branch}", values: {
30516
+ branch_name: reactExports.createElement(Brackets, null, branch_name),
30517
+ origin_branch: reactExports.createElement(Brackets, null, `origin/${master_branch}`),
30518
+ } }));
30515
30519
  actions.set((state) => {
30516
30520
  state.commit_range = next_commit_range;
30517
30521
  state.step = "status";
@@ -30566,9 +30570,13 @@ async function run$6() {
30566
30570
  "."));
30567
30571
  actions.exit(6);
30568
30572
  }
30569
- }
30573
+ };
30570
30574
  const PATCH_FILE$1 = "git-stack-cli-patch.patch";
30571
30575
 
30576
+ function LocalMergeRebase() {
30577
+ return reactExports.createElement(Rebase$1, null);
30578
+ }
30579
+
30572
30580
  function write(args) {
30573
30581
  const stack_table = table(args);
30574
30582
  let result = args.body;
@@ -32042,6 +32050,11 @@ function MaybeMain() {
32042
32050
  else if (positional_list.has("log")) {
32043
32051
  return reactExports.createElement(Log, null);
32044
32052
  }
32053
+ else if (positional_list.has("rebase")) {
32054
+ return (reactExports.createElement(GatherMetadata, null,
32055
+ reactExports.createElement(LocalCommitStatus, null,
32056
+ reactExports.createElement(Rebase$1, null))));
32057
+ }
32045
32058
  return (reactExports.createElement(DirtyCheck, null,
32046
32059
  !argv.verbose ? null : reactExports.createElement(GithubApiError, null),
32047
32060
  reactExports.createElement(GatherMetadata, null,
@@ -37360,6 +37373,7 @@ async function command() {
37360
37373
  .command("$0", "Sync commit ranges to Github", (yargs) => yargs.options(DefaultOptions))
37361
37374
  .command("fixup [commit]", "Amend staged changes to a specific commit in history", (yargs) => yargs.positional("commit", FixupOptions.commit))
37362
37375
  .command("log [args...]", "Print an abbreviated log with numbered commits, useful for git stack fixup", (yargs) => yargs.strict(false))
37376
+ .command("rebase", "Update local branch via rebase with latest changes from origin master branch", (yargs) => yargs)
37363
37377
  .option("verbose", GlobalOptions.verbose)
37364
37378
  // yargs default wraps to 80 columns
37365
37379
  // passing null will wrap to terminal width
@@ -37367,7 +37381,7 @@ async function command() {
37367
37381
  .wrap(123)
37368
37382
  // disallow unknown options
37369
37383
  .strict()
37370
- .version("1.10.0" )
37384
+ .version("1.11.0" )
37371
37385
  .showHidden("show-hidden", "Show hidden options via `git stack help --show-hidden`")
37372
37386
  .help("help", "Show usage via `git stack help`")
37373
37387
  .argv;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-stack-cli",
3
- "version": "1.10.0",
3
+ "version": "1.11.0",
4
4
  "description": "",
5
5
  "author": "magus",
6
6
  "license": "MIT",
package/src/app/App.tsx CHANGED
@@ -16,6 +16,7 @@ import { RebaseCheck } from "~/app/RebaseCheck";
16
16
  import { Store } from "~/app/Store";
17
17
  import { Fixup } from "~/commands/Fixup";
18
18
  import { Log } from "~/commands/Log";
19
+ import { Rebase } from "~/commands/Rebase";
19
20
 
20
21
  export function App() {
21
22
  const actions = Store.useActions();
@@ -73,6 +74,14 @@ function MaybeMain() {
73
74
  return <Fixup />;
74
75
  } else if (positional_list.has("log")) {
75
76
  return <Log />;
77
+ } else if (positional_list.has("rebase")) {
78
+ return (
79
+ <GatherMetadata>
80
+ <LocalCommitStatus>
81
+ <Rebase />
82
+ </LocalCommitStatus>
83
+ </GatherMetadata>
84
+ );
76
85
  }
77
86
 
78
87
  return (
@@ -1,193 +1,7 @@
1
1
  import * as React from "react";
2
2
 
3
- import fs from "node:fs";
4
-
5
- import * as Ink from "ink-cjs";
6
-
7
- import { Await } from "~/app/Await";
8
- import { Brackets } from "~/app/Brackets";
9
- import { FormatText } from "~/app/FormatText";
10
- import { Parens } from "~/app/Parens";
11
- import { Store } from "~/app/Store";
12
- import * as CommitMetadata from "~/core/CommitMetadata";
13
- import { cli } from "~/core/cli";
14
- import { colors } from "~/core/colors";
15
- import { invariant } from "~/core/invariant";
16
- import { short_id } from "~/core/short_id";
3
+ import { Rebase } from "~/commands/Rebase";
17
4
 
18
5
  export function LocalMergeRebase() {
19
- return (
20
- <Await
21
- fallback={<Ink.Text color={colors.yellow}>Rebasing commits…</Ink.Text>}
22
- function={run}
23
- />
24
- );
25
- }
26
-
27
- async function run() {
28
- const state = Store.getState();
29
- const actions = state.actions;
30
- const branch_name = state.branch_name;
31
- const commit_range = state.commit_range;
32
- const master_branch = state.master_branch;
33
- const cwd = state.cwd;
34
- const repo_root = state.repo_root;
35
-
36
- invariant(branch_name, "branch_name must exist");
37
- invariant(commit_range, "commit_range must exist");
38
- invariant(repo_root, "repo_root must exist");
39
-
40
- // always listen for SIGINT event and restore git state
41
- process.once("SIGINT", handle_exit);
42
-
43
- const temp_branch_name = `${branch_name}_${short_id()}`;
44
-
45
- try {
46
- // actions.debug(`commit_range=${JSON.stringify(commit_range, null, 2)}`);
47
-
48
- // must perform rebase from repo root for applying git patch
49
- process.chdir(repo_root);
50
- await cli(`pwd`);
51
-
52
- // update local master to match remote
53
- await cli(
54
- `git fetch --no-tags -v origin ${master_branch}:${master_branch}`
55
- );
56
-
57
- const master_sha = (await cli(`git rev-parse ${master_branch}`)).stdout;
58
- const rebase_merge_base = master_sha;
59
-
60
- // create temporary branch based on merge base
61
- await cli(`git checkout -b ${temp_branch_name} ${rebase_merge_base}`);
62
-
63
- const picked_commit_list = [];
64
-
65
- for (let i = 0; i < commit_range.commit_list.length; i++) {
66
- const commit = commit_range.commit_list[i];
67
- const commit_pr = commit_range.pr_lookup[commit.branch_id || ""];
68
-
69
- // drop commits that are in groups of merged PRs
70
- const merged_pr = commit_pr?.state === "MERGED";
71
-
72
- if (merged_pr) {
73
- if (actions.isDebug()) {
74
- actions.output(
75
- <FormatText
76
- wrapper={<Ink.Text color={colors.yellow} wrap="truncate-end" />}
77
- message="Dropping {commit_message} {pr_status}"
78
- values={{
79
- commit_message: <Brackets>{commit.subject_line}</Brackets>,
80
- pr_status: <Parens>MERGED</Parens>,
81
- }}
82
- />
83
- );
84
- }
85
-
86
- continue;
87
- }
88
-
89
- if (actions.isDebug()) {
90
- actions.output(
91
- <FormatText
92
- wrapper={<Ink.Text color={colors.yellow} wrap="truncate-end" />}
93
- message="Picking {commit_message}"
94
- values={{
95
- commit_message: <Brackets>{commit.subject_line}</Brackets>,
96
- }}
97
- />
98
- );
99
- }
100
-
101
- picked_commit_list.push(commit);
102
- }
103
-
104
- if (picked_commit_list.length > 0) {
105
- // ensure clean base to avoid conflicts when applying patch
106
- await cli(`git clean -fd`);
107
-
108
- // create list of sha for cherry-pick
109
- const sha_list = picked_commit_list.map((commit) => commit.sha).join(" ");
110
-
111
- await cli(`git cherry-pick --keep-redundant-commits ${sha_list}`);
112
- }
113
-
114
- // after all commits have been cherry-picked and amended
115
- // move the branch pointer to the newly created temporary branch
116
- // now we are locally in sync with github and on the original branch
117
- await cli(`git branch -f ${branch_name} ${temp_branch_name}`);
118
-
119
- restore_git();
120
-
121
- const next_commit_range = await CommitMetadata.range();
122
-
123
- actions.set((state) => {
124
- state.commit_range = next_commit_range;
125
- state.step = "status";
126
- });
127
- } catch (err) {
128
- actions.error("Unable to rebase.");
129
-
130
- if (err instanceof Error) {
131
- if (actions.isDebug()) {
132
- actions.error(err.message);
133
- }
134
- }
135
-
136
- handle_exit();
137
- }
138
-
139
- // cleanup git operations if cancelled during manual rebase
140
- function restore_git() {
141
- // signint handler MUST run synchronously
142
- // trying to use `await cli(...)` here will silently fail since
143
- // all children processes receive the SIGINT signal
144
- const spawn_options = { ignoreExitCode: true };
145
-
146
- // always clean up any patch files
147
- cli.sync(`rm ${PATCH_FILE}`, spawn_options);
148
-
149
- // always hard reset and clean to allow subsequent checkout
150
- // if there are files checkout will fail and cascade fail subsequent commands
151
- cli.sync(`git reset --hard`, spawn_options);
152
- cli.sync(`git clean -df`, spawn_options);
153
-
154
- // always put self back in original branch
155
- cli.sync(`git checkout ${branch_name}`, spawn_options);
156
-
157
- // ...and cleanup temporary branch
158
- cli.sync(`git branch -D ${temp_branch_name}`, spawn_options);
159
-
160
- if (commit_range) {
161
- // ...and cleanup pr group branches
162
- for (const group of commit_range.group_list) {
163
- cli.sync(`git branch -D ${group.id}`, spawn_options);
164
- }
165
- }
166
-
167
- // restore back to original dir
168
- if (fs.existsSync(cwd)) {
169
- process.chdir(cwd);
170
- }
171
- cli.sync(`pwd`, spawn_options);
172
- }
173
-
174
- function handle_exit() {
175
- actions.output(
176
- <Ink.Text color={colors.yellow}>
177
- Restoring <Brackets>{branch_name}</Brackets>…
178
- </Ink.Text>
179
- );
180
-
181
- restore_git();
182
-
183
- actions.output(
184
- <Ink.Text color={colors.yellow}>
185
- Restored <Brackets>{branch_name}</Brackets>.
186
- </Ink.Text>
187
- );
188
-
189
- actions.exit(6);
190
- }
6
+ return <Rebase />;
191
7
  }
192
-
193
- const PATCH_FILE = "git-stack-cli-patch.patch";
package/src/command.ts CHANGED
@@ -27,6 +27,12 @@ export async function command() {
27
27
  (yargs) => yargs.strict(false)
28
28
  )
29
29
 
30
+ .command(
31
+ "rebase",
32
+ "Update local branch via rebase with latest changes from origin master branch",
33
+ (yargs) => yargs
34
+ )
35
+
30
36
  .option("verbose", GlobalOptions.verbose)
31
37
 
32
38
  // yargs default wraps to 80 columns
@@ -0,0 +1,204 @@
1
+ import * as React from "react";
2
+
3
+ import fs from "node:fs";
4
+
5
+ import * as Ink from "ink-cjs";
6
+
7
+ import { Await } from "~/app/Await";
8
+ import { Brackets } from "~/app/Brackets";
9
+ import { FormatText } from "~/app/FormatText";
10
+ import { Parens } from "~/app/Parens";
11
+ import { Store } from "~/app/Store";
12
+ import * as CommitMetadata from "~/core/CommitMetadata";
13
+ import { cli } from "~/core/cli";
14
+ import { colors } from "~/core/colors";
15
+ import { invariant } from "~/core/invariant";
16
+ import { short_id } from "~/core/short_id";
17
+
18
+ export function Rebase() {
19
+ return (
20
+ <Await
21
+ function={Rebase.run}
22
+ fallback={<Ink.Text color={colors.yellow}>Rebasing commits…</Ink.Text>}
23
+ />
24
+ );
25
+ }
26
+
27
+ Rebase.run = async function run() {
28
+ const state = Store.getState();
29
+ const actions = state.actions;
30
+ const branch_name = state.branch_name;
31
+ const commit_range = state.commit_range;
32
+ const master_branch = state.master_branch;
33
+ const cwd = state.cwd;
34
+ const repo_root = state.repo_root;
35
+
36
+ invariant(branch_name, "branch_name must exist");
37
+ invariant(commit_range, "commit_range must exist");
38
+ invariant(repo_root, "repo_root must exist");
39
+
40
+ // always listen for SIGINT event and restore git state
41
+ process.once("SIGINT", handle_exit);
42
+
43
+ const temp_branch_name = `${branch_name}_${short_id()}`;
44
+
45
+ try {
46
+ // actions.debug(`commit_range=${JSON.stringify(commit_range, null, 2)}`);
47
+
48
+ // must perform rebase from repo root for applying git patch
49
+ process.chdir(repo_root);
50
+ await cli(`pwd`);
51
+
52
+ // update local master to match remote
53
+ await cli(
54
+ `git fetch --no-tags -v origin ${master_branch}:${master_branch}`
55
+ );
56
+
57
+ const master_sha = (await cli(`git rev-parse ${master_branch}`)).stdout;
58
+ const rebase_merge_base = master_sha;
59
+
60
+ // create temporary branch based on merge base
61
+ await cli(`git checkout -b ${temp_branch_name} ${rebase_merge_base}`);
62
+
63
+ const picked_commit_list = [];
64
+
65
+ for (let i = 0; i < commit_range.commit_list.length; i++) {
66
+ const commit = commit_range.commit_list[i];
67
+ const commit_pr = commit_range.pr_lookup[commit.branch_id || ""];
68
+
69
+ // drop commits that are in groups of merged PRs
70
+ const merged_pr = commit_pr?.state === "MERGED";
71
+
72
+ if (merged_pr) {
73
+ if (actions.isDebug()) {
74
+ actions.output(
75
+ <FormatText
76
+ wrapper={<Ink.Text color={colors.yellow} wrap="truncate-end" />}
77
+ message="Dropping {commit_message} {pr_status}"
78
+ values={{
79
+ commit_message: <Brackets>{commit.subject_line}</Brackets>,
80
+ pr_status: <Parens>MERGED</Parens>,
81
+ }}
82
+ />
83
+ );
84
+ }
85
+
86
+ continue;
87
+ }
88
+
89
+ if (actions.isDebug()) {
90
+ actions.output(
91
+ <FormatText
92
+ wrapper={<Ink.Text color={colors.yellow} wrap="truncate-end" />}
93
+ message="Picking {commit_message}"
94
+ values={{
95
+ commit_message: <Brackets>{commit.subject_line}</Brackets>,
96
+ }}
97
+ />
98
+ );
99
+ }
100
+
101
+ picked_commit_list.push(commit);
102
+ }
103
+
104
+ if (picked_commit_list.length > 0) {
105
+ // ensure clean base to avoid conflicts when applying patch
106
+ await cli(`git clean -fd`);
107
+
108
+ // create list of sha for cherry-pick
109
+ const sha_list = picked_commit_list.map((commit) => commit.sha).join(" ");
110
+
111
+ await cli(`git cherry-pick --keep-redundant-commits ${sha_list}`);
112
+ }
113
+
114
+ // after all commits have been cherry-picked and amended
115
+ // move the branch pointer to the newly created temporary branch
116
+ // now we are locally in sync with github and on the original branch
117
+ await cli(`git branch -f ${branch_name} ${temp_branch_name}`);
118
+
119
+ restore_git();
120
+
121
+ const next_commit_range = await CommitMetadata.range();
122
+
123
+ actions.output(
124
+ <FormatText
125
+ wrapper={<Ink.Text color={colors.green} />}
126
+ message="✅ {branch_name} in sync with {origin_branch}"
127
+ values={{
128
+ branch_name: <Brackets>{branch_name}</Brackets>,
129
+ origin_branch: <Brackets>{`origin/${master_branch}`}</Brackets>,
130
+ }}
131
+ />
132
+ );
133
+
134
+ actions.set((state) => {
135
+ state.commit_range = next_commit_range;
136
+ state.step = "status";
137
+ });
138
+ } catch (err) {
139
+ actions.error("Unable to rebase.");
140
+
141
+ if (err instanceof Error) {
142
+ if (actions.isDebug()) {
143
+ actions.error(err.message);
144
+ }
145
+ }
146
+
147
+ handle_exit();
148
+ }
149
+
150
+ // cleanup git operations if cancelled during manual rebase
151
+ function restore_git() {
152
+ // signint handler MUST run synchronously
153
+ // trying to use `await cli(...)` here will silently fail since
154
+ // all children processes receive the SIGINT signal
155
+ const spawn_options = { ignoreExitCode: true };
156
+
157
+ // always clean up any patch files
158
+ cli.sync(`rm ${PATCH_FILE}`, spawn_options);
159
+
160
+ // always hard reset and clean to allow subsequent checkout
161
+ // if there are files checkout will fail and cascade fail subsequent commands
162
+ cli.sync(`git reset --hard`, spawn_options);
163
+ cli.sync(`git clean -df`, spawn_options);
164
+
165
+ // always put self back in original branch
166
+ cli.sync(`git checkout ${branch_name}`, spawn_options);
167
+
168
+ // ...and cleanup temporary branch
169
+ cli.sync(`git branch -D ${temp_branch_name}`, spawn_options);
170
+
171
+ if (commit_range) {
172
+ // ...and cleanup pr group branches
173
+ for (const group of commit_range.group_list) {
174
+ cli.sync(`git branch -D ${group.id}`, spawn_options);
175
+ }
176
+ }
177
+
178
+ // restore back to original dir
179
+ if (fs.existsSync(cwd)) {
180
+ process.chdir(cwd);
181
+ }
182
+ cli.sync(`pwd`, spawn_options);
183
+ }
184
+
185
+ function handle_exit() {
186
+ actions.output(
187
+ <Ink.Text color={colors.yellow}>
188
+ Restoring <Brackets>{branch_name}</Brackets>…
189
+ </Ink.Text>
190
+ );
191
+
192
+ restore_git();
193
+
194
+ actions.output(
195
+ <Ink.Text color={colors.yellow}>
196
+ Restored <Brackets>{branch_name}</Brackets>.
197
+ </Ink.Text>
198
+ );
199
+
200
+ actions.exit(6);
201
+ }
202
+ };
203
+
204
+ const PATCH_FILE = "git-stack-cli-patch.patch";