git-stack-cli 2.9.9 → 2.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-stack-cli",
3
- "version": "2.9.9",
3
+ "version": "2.11.0",
4
4
  "description": "",
5
5
  "author": "magus",
6
6
  "license": "MIT",
package/src/command.ts CHANGED
@@ -180,7 +180,7 @@ const DefaultOptions = {
180
180
  const FixupOptions = {
181
181
  commit: {
182
182
  type: "number",
183
- default: 1,
183
+ default: 0,
184
184
  description: [
185
185
  "Relative number of commit to amend staged changes.",
186
186
  "Most recent is 1, next is 2, etc.",
@@ -3,11 +3,13 @@ import * as React from "react";
3
3
  import * as Ink from "ink-cjs";
4
4
 
5
5
  import { Await } from "~/app/Await";
6
+ import { Command } from "~/app/Command";
6
7
  import { FormatText } from "~/app/FormatText";
7
8
  import { Parens } from "~/app/Parens";
8
9
  import { Store } from "~/app/Store";
9
10
  import { cli } from "~/core/cli";
10
11
  import { colors } from "~/core/colors";
12
+ import { is_finite_value } from "~/core/is_finite_value";
11
13
 
12
14
  export function Fixup() {
13
15
  return (
@@ -25,27 +27,48 @@ async function run() {
25
27
 
26
28
  const relative_number = argv.commit;
27
29
 
28
- if (!relative_number) {
30
+ if (!is_finite_value(relative_number)) {
29
31
  actions.output(
30
- <Ink.Text color={colors.red}>❗️ Usage: git fixup {"<relative-commit-number>"}</Ink.Text>,
32
+ <Ink.Box flexDirection="column">
33
+ <Ink.Text color={colors.red}>❗️ Usage: git fixup {"<relative-commit-number>"}</Ink.Text>
34
+ <Ink.Text color={colors.gray}>
35
+ Automates the process of adding staged changes to a previous commit.
36
+ </Ink.Text>
37
+ <FormatText
38
+ wrapper={<Ink.Text color={colors.gray} />}
39
+ message="You can use {git_stack_log} to get the relative commit number."
40
+ values={{ git_stack_log: <Command>git stack log</Command> }}
41
+ />
42
+ <Ink.Box height={1} />
43
+ <FormatText
44
+ message=" {prompt} git stack log"
45
+ values={{ prompt: <Ink.Text color={colors.green}>❯</Ink.Text> }}
46
+ />
47
+ <FormatText
48
+ message=" 0 * {sha} 18 hours ago noah homebrew-git-stack 2.9.9"
49
+ values={{ sha: <Ink.Text color={colors.green}>e329794</Ink.Text> }}
50
+ />
51
+ <FormatText
52
+ message=" 1 * {sha} 18 hours ago noah 2.9.9"
53
+ values={{ sha: <Ink.Text color={colors.green}>c7e4065</Ink.Text> }}
54
+ />
55
+ <FormatText
56
+ message=" 2 * {sha} 18 hours ago noah command: --label + github add labels"
57
+ values={{ sha: <Ink.Text color={colors.green}>f82ac73</Ink.Text> }}
58
+ />
59
+ <Ink.Box height={1} />
60
+ <FormatText
61
+ wrapper={<Ink.Text color={colors.gray} />}
62
+ message="To target {sha} above, use {command}"
63
+ values={{
64
+ sha: <Ink.Text color={colors.green}>838e878</Ink.Text>,
65
+ command: <Command>git stack fixup 2</Command>,
66
+ }}
67
+ />
68
+ </Ink.Box>,
31
69
  );
70
+
32
71
  actions.output("");
33
- actions.output("This script automates the process of adding staged changes as a fixup commit");
34
- actions.output(
35
- "and the subsequent git rebase to flatten the commits based on relative commit number",
36
- );
37
- actions.output("You can use a `git log` like below to get the relative commit number");
38
- actions.output("");
39
- actions.output(" ❯ git stack log");
40
- actions.output(
41
- " 1\te329794d5f881cbf0fc3f26d2108cf6f3fdebabe enable drop_error_subtask test param",
42
- );
43
- actions.output(
44
- " 2\t57f43b596e5c6b97bc47e2a591f82ccc81651156 test drop_error_subtask baseline",
45
- );
46
- actions.output(" 3\t838e878d483c6a2d5393063fc59baf2407225c6d ErrorSubtask test baseline");
47
- actions.output("");
48
- actions.output("To target `838e87` above, you would call `fixup 3`");
49
72
 
50
73
  actions.exit(0);
51
74
  }
@@ -64,11 +87,8 @@ async function run() {
64
87
  // );
65
88
  }
66
89
 
67
- // Calculate commit SHA based on the relative commit number
68
- const adjusted_number = Number(relative_number) - 1;
69
-
70
90
  // get the commit SHA of the target commit
71
- const commit_sha = (await cli(`git rev-parse HEAD~${adjusted_number}`)).stdout;
91
+ const commit_sha = (await cli(`git rev-parse HEAD~${relative_number}`)).stdout;
72
92
 
73
93
  actions.output(
74
94
  <FormatText
@@ -104,7 +124,7 @@ async function run() {
104
124
 
105
125
  try {
106
126
  // rebase target needs to account for new commit created above
107
- const rebase_target = Number(relative_number) + 1;
127
+ const rebase_target = Number(relative_number) + 2;
108
128
 
109
129
  await cli(`git rebase -i --autosquash HEAD~${rebase_target}`, {
110
130
  env: {
@@ -65,7 +65,7 @@ async function run(args: Args) {
65
65
  const command = [
66
66
  `git log --pretty=format:"${format}" -n20 --graph --color ${rest_args}`,
67
67
  `cut -c 1-"${truncation_width}"`,
68
- `nl -w3 -s' '`,
68
+ `nl -v0 -w3 -s' '`,
69
69
  ].join(" | ");
70
70
 
71
71
  const result = await cli(command);
@@ -6,6 +6,7 @@ import * as Ink from "ink-cjs";
6
6
 
7
7
  import { Await } from "~/app/Await";
8
8
  import { Brackets } from "~/app/Brackets";
9
+ import { Command } from "~/app/Command";
9
10
  import { FormatText } from "~/app/FormatText";
10
11
  import { Status } from "~/app/Status";
11
12
  import { Store } from "~/app/Store";
@@ -41,8 +42,12 @@ Rebase.run = async function run(props: Props) {
41
42
  invariant(commit_range, "commit_range must exist");
42
43
  invariant(repo_root, "repo_root must exist");
43
44
 
45
+ const abort_controller = new AbortController();
46
+ const signal = abort_controller.signal;
47
+
44
48
  // immediately register abort_handler in case of ctrl+c exit
45
49
  actions.register_abort_handler(async function abort_rebase() {
50
+ abort_controller.abort();
46
51
  actions.output(<Ink.Text color={colors.red}>🚨 Abort</Ink.Text>);
47
52
  handle_exit();
48
53
  return 19;
@@ -59,7 +64,7 @@ Rebase.run = async function run(props: Props) {
59
64
  await cli(`pwd`);
60
65
 
61
66
  // fetch origin master branch for latest sha
62
- await cli(`git fetch --no-tags -v origin ${master_branch_name}`);
67
+ await cli(`git fetch --no-tags -v origin ${master_branch_name}`, { signal });
63
68
 
64
69
  if (branch_name === master_branch_name) {
65
70
  await rebase_master();
@@ -69,12 +74,41 @@ Rebase.run = async function run(props: Props) {
69
74
 
70
75
  actions.unregister_abort_handler();
71
76
  } catch (err) {
77
+ actions.unregister_abort_handler();
72
78
  actions.error("Unable to rebase.");
73
79
 
74
80
  if (err instanceof Error) {
75
81
  actions.error(err.message);
76
82
  }
77
83
 
84
+ actions.output(
85
+ <Ink.Box flexDirection="column">
86
+ <Ink.Box height={1} />
87
+ <Ink.Text>⚠️ WARNING</Ink.Text>
88
+ <FormatText
89
+ message="You are in a temporary branch {branch_name} based on {original_branch}"
90
+ values={{
91
+ branch_name: <Brackets>{temp_branch_name}</Brackets>,
92
+ original_branch: <Brackets>{branch_name}</Brackets>,
93
+ }}
94
+ />
95
+ <FormatText
96
+ message="Fix merge conflicts then run {cp_continue} to proceed"
97
+ values={{
98
+ cp_continue: <Command>git cherry-pick --continue</Command>,
99
+ }}
100
+ />
101
+ <FormatText
102
+ message="To go back to the original branch run {checkout_original}"
103
+ values={{
104
+ checkout_original: (
105
+ <Command>git cherry-pick --abort && git checkout -f {branch_name}</Command>
106
+ ),
107
+ }}
108
+ />
109
+ </Ink.Box>,
110
+ );
111
+
78
112
  actions.exit(8);
79
113
  }
80
114
 
@@ -105,7 +139,7 @@ Rebase.run = async function run(props: Props) {
105
139
  }
106
140
 
107
141
  async function rebase_master() {
108
- await cli(`git switch -C "${master_branch_name}" "${master_branch}"`);
142
+ await cli(`git switch -C "${master_branch_name}" "${master_branch}"`, { signal });
109
143
  }
110
144
 
111
145
  async function rebase_branch() {
@@ -115,7 +149,7 @@ Rebase.run = async function run(props: Props) {
115
149
  const rebase_merge_base = master_sha;
116
150
 
117
151
  // create temporary branch based on merge base
118
- await cli(`git checkout -b ${temp_branch_name} ${rebase_merge_base}`);
152
+ await cli(`git checkout -b ${temp_branch_name} ${rebase_merge_base}`, { signal });
119
153
 
120
154
  const picked_commit_list = [];
121
155
 
@@ -157,17 +191,17 @@ Rebase.run = async function run(props: Props) {
157
191
 
158
192
  if (picked_commit_list.length > 0) {
159
193
  // ensure clean base to avoid conflicts when applying patch
160
- await cli(`git clean -fd`);
194
+ await cli(`git clean -fd`, { signal });
161
195
 
162
196
  // create list of sha for cherry-pick
163
197
  const sha_list = picked_commit_list.map((commit) => commit.sha).join(" ");
164
198
 
165
- await cli(`git cherry-pick --keep-redundant-commits ${sha_list}`);
199
+ await cli(`git cherry-pick --keep-redundant-commits ${sha_list}`, { signal });
166
200
  }
167
201
 
168
202
  // after all commits have been cherry-picked move the pointer
169
203
  // of original branch to the newly created temporary branch
170
- await cli(`git branch -f ${branch_name} ${temp_branch_name}`);
204
+ await cli(`git branch -f ${branch_name} ${temp_branch_name}`, { signal });
171
205
 
172
206
  actions.debug("start restore_git()");
173
207
  restore_git();
package/src/core/cli.ts CHANGED
@@ -38,7 +38,15 @@ export async function cli(
38
38
  }
39
39
 
40
40
  return new Promise((resolve, reject) => {
41
- const childProcess = child.spawn("sh", ["-c", command], options);
41
+ let childProcess: child.ChildProcess;
42
+ try {
43
+ childProcess = child.spawn("sh", ["-c", command], options);
44
+ } catch (err) {
45
+ reject(err);
46
+ return;
47
+ }
48
+
49
+ let settled = false;
42
50
 
43
51
  let stdout = "";
44
52
  let stderr = "";
@@ -71,7 +79,11 @@ export async function cli(
71
79
  });
72
80
 
73
81
  childProcess.on("close", (unsafe_code) => {
74
- const duration = timer.duration();
82
+ if (settled) {
83
+ return;
84
+ }
85
+
86
+ settled = true;
75
87
 
76
88
  const result = {
77
89
  command,
@@ -79,11 +91,12 @@ export async function cli(
79
91
  stdout: stdout.trimEnd(),
80
92
  stderr: stderr.trimEnd(),
81
93
  output: output.trimEnd(),
82
- duration,
94
+ duration: timer.duration(),
83
95
  };
84
96
 
85
97
  state.actions.debug_pending_end(id);
86
98
  state.actions.debug(log.end(result));
99
+
87
100
  if (!options.quiet) {
88
101
  state.actions.debug(log.output(result));
89
102
  }
@@ -97,6 +110,24 @@ export async function cli(
97
110
  });
98
111
 
99
112
  childProcess.on("error", (err) => {
113
+ if (settled) {
114
+ return;
115
+ }
116
+
117
+ settled = true;
118
+
119
+ const result = {
120
+ command,
121
+ code: -1,
122
+ stdout: stdout.trimEnd(),
123
+ stderr: stderr.trimEnd(),
124
+ output: output.trimEnd(),
125
+ duration: timer.duration(),
126
+ };
127
+
128
+ state.actions.debug_pending_end(id);
129
+ state.actions.debug(log.abort({ result, err }));
130
+
100
131
  reject(err);
101
132
  });
102
133
  });
@@ -173,6 +204,12 @@ const log = {
173
204
  return `${command} (exit_code=${code} duration=${duration})`;
174
205
  },
175
206
 
207
+ abort({ result, err }: { result: Return; err?: unknown }) {
208
+ const { command, duration } = result;
209
+ const err_message = err instanceof Error ? err.message : String(err);
210
+ return `[error] ${command} err=${err_message} (duration=${duration})`;
211
+ },
212
+
176
213
  error(result: Return) {
177
214
  const lines = [result.output, this.non_zero_exit(result)];
178
215
  return lines.join("\n");