git-stack-cli 0.7.4 → 0.7.6
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 +16 -5
- package/dist/app/DependencyCheck.js +4 -0
- package/dist/app/LocalMergeRebase.js +21 -1
- package/dist/app/ManualRebase.js +13 -5
- package/dist/app/Output.js +13 -3
- package/dist/app/Store.js +43 -11
- package/dist/command.js +2 -2
- package/dist/core/cli.js +32 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
# git-stack-cli
|
|
2
2
|
|
|
3
|
+
- ✨ **[Stacked diffs](https://graphite.dev/guides/stacked-diffs) for `git`**
|
|
3
4
|
- 🚀 **Simple one-branch workflow**
|
|
4
5
|
- 🎯 **Interactively select commits for each pull request**
|
|
5
|
-
- 📚 **Preserve your detailed commit history**
|
|
6
|
-
- ♻️ **Automatically synchronize each pull request in the stack**
|
|
7
6
|
- 💬 **Group commits for focused code review**
|
|
8
|
-
-
|
|
7
|
+
- 🌐 **Use the [official GitHub CLI](https://cli.github.com/)**
|
|
8
|
+
- ♻️ **Automatically synchronize each pull request in the stack**
|
|
9
9
|
- 💪 **Work seamlessly with GitHub's interface**
|
|
10
|
-
-
|
|
10
|
+
- 🚫 **Avoid juggling mutiple branches and complex rebasing**
|
|
11
|
+
- 📚 **Preserve your detailed commit history**
|
|
12
|
+
|
|
11
13
|
|
|
12
14
|
## Demo
|
|
13
15
|
|
|
@@ -17,10 +19,19 @@
|
|
|
17
19
|
|
|
18
20
|
```bash
|
|
19
21
|
npm i -g git-stack-cli
|
|
22
|
+
```
|
|
20
23
|
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
```bash
|
|
21
28
|
git stack
|
|
22
|
-
```
|
|
23
29
|
|
|
30
|
+
git stack --verbose # print more detailed logs for debugging internals
|
|
31
|
+
git stack --no-verify # skip git hooks such as pre-commit and pre-push
|
|
32
|
+
|
|
33
|
+
git-stack --help # print a table of all command-line arguments
|
|
34
|
+
```
|
|
24
35
|
|
|
25
36
|
## Why?
|
|
26
37
|
|
|
@@ -16,6 +16,10 @@ export function DependencyCheck(props) {
|
|
|
16
16
|
"Checking ",
|
|
17
17
|
React.createElement(Command, null, "git"),
|
|
18
18
|
" install..."), function: async () => {
|
|
19
|
+
// await Promise.all([
|
|
20
|
+
// cli(`for i in $(seq 1 5); do echo $i; sleep 1; done`),
|
|
21
|
+
// cli(`for i in $(seq 5 1); do printf "$i "; sleep 1; done; echo`),
|
|
22
|
+
// ]);
|
|
19
23
|
if (is_command_available("git")) {
|
|
20
24
|
return;
|
|
21
25
|
}
|
|
@@ -17,9 +17,11 @@ export function LocalMergeRebase() {
|
|
|
17
17
|
async function run() {
|
|
18
18
|
const state = Store.getState();
|
|
19
19
|
const actions = state.actions;
|
|
20
|
+
const argv = state.argv;
|
|
20
21
|
const branch_name = state.branch_name;
|
|
21
22
|
const commit_range = state.commit_range;
|
|
22
23
|
const master_branch = state.master_branch;
|
|
24
|
+
invariant(argv, "argv must exist");
|
|
23
25
|
invariant(branch_name, "branch_name must exist");
|
|
24
26
|
invariant(commit_range, "commit_range must exist");
|
|
25
27
|
// always listen for SIGINT event and restore git state
|
|
@@ -51,7 +53,22 @@ async function run() {
|
|
|
51
53
|
commit_message: React.createElement(Brackets, null, commit.message),
|
|
52
54
|
} }));
|
|
53
55
|
}
|
|
54
|
-
await cli(`git
|
|
56
|
+
await cli(`git format-patch -1 ${commit.sha} --stdout > ${PATCH_FILE}`);
|
|
57
|
+
await cli(`git apply ${PATCH_FILE}`);
|
|
58
|
+
await cli(`rm ${PATCH_FILE}`);
|
|
59
|
+
await cli(`git add --all`);
|
|
60
|
+
let new_message;
|
|
61
|
+
if (commit.branch_id) {
|
|
62
|
+
new_message = await Metadata.write(commit.message, commit.branch_id);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
new_message = commit.message;
|
|
66
|
+
}
|
|
67
|
+
const git_commit_comand = [`git commit -m "${new_message}"`];
|
|
68
|
+
if (argv.verify === false) {
|
|
69
|
+
git_commit_comand.push("--no-verify");
|
|
70
|
+
}
|
|
71
|
+
await cli(git_commit_comand);
|
|
55
72
|
if (commit.branch_id && !commit_pr) {
|
|
56
73
|
if (actions.isDebug()) {
|
|
57
74
|
actions.output(React.createElement(FormatText, { wrapper: React.createElement(Ink.Text, { color: colors.yellow, wrap: "truncate-end" }), message: "Cleaning up unused group {group}", values: {
|
|
@@ -89,6 +106,8 @@ async function run() {
|
|
|
89
106
|
// trying to use `await cli(...)` here will silently fail since
|
|
90
107
|
// all children processes receive the SIGINT signal
|
|
91
108
|
const spawn_options = { ignoreExitCode: true };
|
|
109
|
+
// always clean up any patch files
|
|
110
|
+
cli.sync(`rm ${PATCH_FILE}`, spawn_options);
|
|
92
111
|
// always put self back in original branch
|
|
93
112
|
cli.sync(`git checkout ${branch_name}`, spawn_options);
|
|
94
113
|
// ...and cleanup temporary branch
|
|
@@ -113,3 +132,4 @@ async function run() {
|
|
|
113
132
|
actions.exit(6);
|
|
114
133
|
}
|
|
115
134
|
}
|
|
135
|
+
const PATCH_FILE = "mypatch.patch";
|
package/dist/app/ManualRebase.js
CHANGED
|
@@ -57,11 +57,16 @@ async function run(props) {
|
|
|
57
57
|
const selected_url = get_group_url(group);
|
|
58
58
|
// cherry-pick and amend commits one by one
|
|
59
59
|
for (const commit of group.commits) {
|
|
60
|
-
await cli(`git
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
await cli(`git format-patch -1 ${commit.sha} --stdout > ${PATCH_FILE}`);
|
|
61
|
+
await cli(`git apply ${PATCH_FILE}`);
|
|
62
|
+
await cli(`rm ${PATCH_FILE}`);
|
|
63
|
+
await cli(`git add --all`);
|
|
64
|
+
const new_message = await Metadata.write(commit.message, group.id);
|
|
65
|
+
const git_commit_comand = [`git commit -m "${new_message}"`];
|
|
66
|
+
if (argv.verify === false) {
|
|
67
|
+
git_commit_comand.push("--no-verify");
|
|
64
68
|
}
|
|
69
|
+
await cli(git_commit_comand);
|
|
65
70
|
}
|
|
66
71
|
actions.output(React.createElement(FormatText, { wrapper: React.createElement(Ink.Text, { color: colors.yellow, wrap: "truncate-end" }), message: "Syncing {group}\u2026", values: {
|
|
67
72
|
group: (React.createElement(Brackets, null, group.pr?.title || group.title || group.id)),
|
|
@@ -72,7 +77,7 @@ async function run(props) {
|
|
|
72
77
|
if (argv.verify === false) {
|
|
73
78
|
git_push_command.push("--no-verify");
|
|
74
79
|
}
|
|
75
|
-
await cli(git_push_command
|
|
80
|
+
await cli(git_push_command);
|
|
76
81
|
if (group.pr) {
|
|
77
82
|
// ensure base matches pr in github
|
|
78
83
|
await github.pr_edit({
|
|
@@ -160,6 +165,8 @@ async function run(props) {
|
|
|
160
165
|
// trying to use `await cli(...)` here will silently fail since
|
|
161
166
|
// all children processes receive the SIGINT signal
|
|
162
167
|
const spawn_options = { ignoreExitCode: true };
|
|
168
|
+
// always clean up any patch files
|
|
169
|
+
cli.sync(`rm ${PATCH_FILE}`, spawn_options);
|
|
163
170
|
// always put self back in original branch
|
|
164
171
|
cli.sync(`git checkout ${branch_name}`, spawn_options);
|
|
165
172
|
// ...and cleanup temporary branch
|
|
@@ -185,3 +192,4 @@ async function run(props) {
|
|
|
185
192
|
}
|
|
186
193
|
}
|
|
187
194
|
const get_group_url = (group) => group.pr?.url || group.id;
|
|
195
|
+
const PATCH_FILE = "mypatch.patch";
|
package/dist/app/Output.js
CHANGED
|
@@ -3,7 +3,17 @@ import * as Ink from "ink";
|
|
|
3
3
|
import { Store } from "./Store.js";
|
|
4
4
|
export function Output() {
|
|
5
5
|
const output = Store.useState((state) => state.output);
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
const pending_output = Store.useState((state) => state.pending_output);
|
|
7
|
+
const pending_output_items = Object.values(pending_output);
|
|
8
|
+
return (React.createElement(React.Fragment, null,
|
|
9
|
+
React.createElement(Ink.Static, { items: output }, (node, i) => {
|
|
10
|
+
return React.createElement(Ink.Box, { key: i }, node);
|
|
11
|
+
}),
|
|
12
|
+
pending_output_items.map((node_list, i) => {
|
|
13
|
+
return (React.createElement(Ink.Box, { key: i },
|
|
14
|
+
React.createElement(Ink.Text, null, node_list.map((text, j) => {
|
|
15
|
+
return (React.createElement(React.Fragment, { key: j },
|
|
16
|
+
React.createElement(Ink.Text, null, text)));
|
|
17
|
+
}))));
|
|
18
|
+
})));
|
|
9
19
|
}
|
package/dist/app/Store.js
CHANGED
|
@@ -18,11 +18,13 @@ const BaseStore = createStore()(immer((set, get) => ({
|
|
|
18
18
|
commit_map: null,
|
|
19
19
|
step: "loading",
|
|
20
20
|
output: [],
|
|
21
|
+
pending_output: {},
|
|
21
22
|
pr: {},
|
|
22
23
|
actions: {
|
|
23
24
|
exit(code, clear = true) {
|
|
24
25
|
set((state) => {
|
|
25
|
-
|
|
26
|
+
const node = React.createElement(Exit, { clear: clear, code: code });
|
|
27
|
+
state.mutate.output(state, { node });
|
|
26
28
|
});
|
|
27
29
|
},
|
|
28
30
|
clear() {
|
|
@@ -33,28 +35,37 @@ const BaseStore = createStore()(immer((set, get) => ({
|
|
|
33
35
|
},
|
|
34
36
|
newline() {
|
|
35
37
|
set((state) => {
|
|
36
|
-
|
|
38
|
+
const node = "";
|
|
39
|
+
state.mutate.output(state, { node });
|
|
37
40
|
});
|
|
38
41
|
},
|
|
39
42
|
json(value) {
|
|
40
43
|
set((state) => {
|
|
41
|
-
|
|
44
|
+
const node = JSON.stringify(value, null, 2);
|
|
45
|
+
state.mutate.output(state, { node });
|
|
42
46
|
});
|
|
43
47
|
},
|
|
44
48
|
error(message) {
|
|
45
49
|
set((state) => {
|
|
46
|
-
|
|
50
|
+
const node = React.createElement(Ink.Text, { color: colors.red }, message);
|
|
51
|
+
state.mutate.output(state, { node });
|
|
47
52
|
});
|
|
48
53
|
},
|
|
49
54
|
output(node) {
|
|
50
55
|
set((state) => {
|
|
51
|
-
state.mutate.output(state, node);
|
|
56
|
+
state.mutate.output(state, { node });
|
|
52
57
|
});
|
|
53
58
|
},
|
|
54
|
-
debug(node) {
|
|
59
|
+
debug(node, id) {
|
|
55
60
|
if (get().actions.isDebug()) {
|
|
61
|
+
const debug = true;
|
|
56
62
|
set((state) => {
|
|
57
|
-
|
|
63
|
+
if (id) {
|
|
64
|
+
state.mutate.pending_output(state, { id, node, debug });
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
state.mutate.output(state, { node, debug });
|
|
68
|
+
}
|
|
58
69
|
});
|
|
59
70
|
}
|
|
60
71
|
},
|
|
@@ -74,15 +85,36 @@ const BaseStore = createStore()(immer((set, get) => ({
|
|
|
74
85
|
},
|
|
75
86
|
},
|
|
76
87
|
mutate: {
|
|
77
|
-
output(state,
|
|
78
|
-
switch (typeof node) {
|
|
88
|
+
output(state, args) {
|
|
89
|
+
switch (typeof args.node) {
|
|
79
90
|
case "boolean":
|
|
80
91
|
case "number":
|
|
81
92
|
case "string":
|
|
82
|
-
state.output.push(React.createElement(Ink.Text,
|
|
93
|
+
state.output.push(React.createElement(Ink.Text, { dimColor: args.debug }, String(args.node)));
|
|
83
94
|
return;
|
|
84
95
|
}
|
|
85
|
-
state.output.push(node);
|
|
96
|
+
state.output.push(args.node);
|
|
97
|
+
},
|
|
98
|
+
pending_output(state, args) {
|
|
99
|
+
const { id } = args;
|
|
100
|
+
if (!id) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (!state.pending_output[id]) {
|
|
104
|
+
state.pending_output[id] = [];
|
|
105
|
+
}
|
|
106
|
+
switch (typeof args.node) {
|
|
107
|
+
case "boolean":
|
|
108
|
+
case "number":
|
|
109
|
+
case "string": {
|
|
110
|
+
state.pending_output[id].push(React.createElement(Ink.Text, { dimColor: args.debug }, String(args.node)));
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
state.pending_output[id].push(args.node);
|
|
115
|
+
},
|
|
116
|
+
end_pending_output(state, id) {
|
|
117
|
+
delete state.pending_output[id];
|
|
86
118
|
},
|
|
87
119
|
},
|
|
88
120
|
select: {
|
package/dist/command.js
CHANGED
|
@@ -19,13 +19,13 @@ export async function command() {
|
|
|
19
19
|
.option("verify", {
|
|
20
20
|
type: "boolean",
|
|
21
21
|
default: true,
|
|
22
|
-
description: "
|
|
22
|
+
description: "Skip git hooks such as pre-commit and pre-push",
|
|
23
23
|
})
|
|
24
24
|
.option("verbose", {
|
|
25
25
|
type: "boolean",
|
|
26
26
|
alias: ["v"],
|
|
27
27
|
default: false,
|
|
28
|
-
description: "
|
|
28
|
+
description: "Print more detailed logs for debugging internals",
|
|
29
29
|
})
|
|
30
30
|
.option("branch", {
|
|
31
31
|
type: "string",
|
package/dist/core/cli.js
CHANGED
|
@@ -1,35 +1,53 @@
|
|
|
1
1
|
import * as child from "node:child_process";
|
|
2
2
|
import { Store } from "../app/Store.js";
|
|
3
|
-
|
|
3
|
+
let i = 0;
|
|
4
|
+
export async function cli(unsafe_command, unsafe_options) {
|
|
4
5
|
const state = Store.getState();
|
|
5
6
|
const options = Object.assign({}, unsafe_options);
|
|
7
|
+
let command;
|
|
8
|
+
if (Array.isArray(unsafe_command)) {
|
|
9
|
+
command = unsafe_command.join(" ");
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
command = unsafe_command;
|
|
13
|
+
}
|
|
6
14
|
return new Promise((resolve, reject) => {
|
|
7
15
|
const childProcess = child.spawn("sh", ["-c", command], options);
|
|
8
16
|
let stdout = "";
|
|
9
17
|
let stderr = "";
|
|
10
18
|
let output = "";
|
|
19
|
+
const id = `${++i}-${command}`;
|
|
20
|
+
state.actions.debug(`[start] ${command}`);
|
|
21
|
+
state.actions.debug(`[start] ${command}\n`, id);
|
|
22
|
+
function write_output(value) {
|
|
23
|
+
output += value;
|
|
24
|
+
state.actions.debug(value, id);
|
|
25
|
+
}
|
|
11
26
|
childProcess.stdout?.on("data", (data) => {
|
|
12
|
-
|
|
13
|
-
|
|
27
|
+
const value = String(data);
|
|
28
|
+
stdout += value;
|
|
29
|
+
write_output(value);
|
|
14
30
|
});
|
|
15
31
|
childProcess.stderr?.on("data", (data) => {
|
|
16
|
-
|
|
17
|
-
|
|
32
|
+
const value = String(data);
|
|
33
|
+
stderr += value;
|
|
34
|
+
write_output(value);
|
|
18
35
|
});
|
|
19
36
|
childProcess.on("close", (code) => {
|
|
37
|
+
const result = {
|
|
38
|
+
command,
|
|
39
|
+
code: code || 0,
|
|
40
|
+
stdout: stdout.trimEnd(),
|
|
41
|
+
stderr: stderr.trimEnd(),
|
|
42
|
+
output: output.trimEnd(),
|
|
43
|
+
};
|
|
44
|
+
state.actions.set((state) => state.mutate.end_pending_output(state, id));
|
|
45
|
+
state.actions.debug(`[end] ${command}`);
|
|
46
|
+
state.actions.debug(result.output);
|
|
20
47
|
if (!options.ignoreExitCode && code !== 0) {
|
|
21
48
|
reject(new Error(`[${command}] (${code})`));
|
|
22
49
|
}
|
|
23
50
|
else {
|
|
24
|
-
const result = {
|
|
25
|
-
command,
|
|
26
|
-
code: code || 0,
|
|
27
|
-
stdout: stdout.trimEnd(),
|
|
28
|
-
stderr: stderr.trimEnd(),
|
|
29
|
-
output: output.trimEnd(),
|
|
30
|
-
};
|
|
31
|
-
state.actions.debug(`$ ${command}`);
|
|
32
|
-
state.actions.debug(result.output);
|
|
33
51
|
resolve(result);
|
|
34
52
|
}
|
|
35
53
|
});
|