git-stack-cli 1.0.5 → 1.0.7
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 +1 -1
- package/dist/cjs/index.cjs +51 -16
- package/package.json +1 -1
- package/scripts/release-brew.ts +21 -13
- package/src/app/DependencyCheck.tsx +14 -18
- package/src/app/Store.tsx +14 -2
- package/src/core/match_group.ts +8 -0
- package/src/github/gh.auth_status.test.ts +24 -0
- package/src/github/gh.ts +19 -0
package/README.md
CHANGED
|
@@ -44,7 +44,7 @@ Often pushing all your commits to a single pull request is the simplest and fast
|
|
|
44
44
|
This comes at a price, your teammates have to review larger, less related pieces of code and you will lose some of your atomic commit history if you "Squash and merge".
|
|
45
45
|
|
|
46
46
|
When you decide to break changes up into multiple diffs that depend on one another this process is commonly referred to as **[stacked diffs](https://graphite.dev/guides/stacked-diffs)** (pull requests that depend on other pull requests).
|
|
47
|
-
This
|
|
47
|
+
This approach is popular at many major companies such as Twitter, Facebook, etc.
|
|
48
48
|
Managing stacked diffs manually involves managing multiple local branches, jumping between them, rebasing, etc.
|
|
49
49
|
This process gets even more complicated when you start getting feedback in code review and have to update individual branches.
|
|
50
50
|
Managing even a few stacked diffs requires a relatively strong knowledge of `git`, even with tricks like [`--update-refs`](https://git-scm.com/docs/git-rebase#Documentation/git-rebase.txt---update-refs).
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -26287,10 +26287,21 @@ const BaseStore = createStore()(immer((set, get) => ({
|
|
|
26287
26287
|
if (!id) {
|
|
26288
26288
|
return;
|
|
26289
26289
|
}
|
|
26290
|
+
// set `withoutTimestamp` to skip <LogTimestamp> for all subsequent pending outputs
|
|
26291
|
+
// we only want to timestamp for the first part (when we initialize the [])
|
|
26292
|
+
// if we have many incremental outputs on the same line we do not want multiple timestamps
|
|
26293
|
+
//
|
|
26294
|
+
// await Promise.all([
|
|
26295
|
+
// cli(`for i in $(seq 1 5); do echo $i; sleep 1; done`),
|
|
26296
|
+
// cli(`for i in $(seq 5 1); do printf "$i "; sleep 1; done; echo`),
|
|
26297
|
+
// ]);
|
|
26298
|
+
//
|
|
26299
|
+
let withoutTimestamp = true;
|
|
26290
26300
|
if (!state.pending_output[id]) {
|
|
26301
|
+
withoutTimestamp = false;
|
|
26291
26302
|
state.pending_output[id] = [];
|
|
26292
26303
|
}
|
|
26293
|
-
const renderOutput = renderOutputArgs(args);
|
|
26304
|
+
const renderOutput = renderOutputArgs({ ...args, withoutTimestamp });
|
|
26294
26305
|
state.pending_output[id].push(renderOutput);
|
|
26295
26306
|
},
|
|
26296
26307
|
end_pending_output(state, id) {
|
|
@@ -26313,7 +26324,7 @@ function renderOutputArgs(args) {
|
|
|
26313
26324
|
}
|
|
26314
26325
|
if (args.debug) {
|
|
26315
26326
|
return (reactExports.createElement(reactExports.Fragment, null,
|
|
26316
|
-
reactExports.createElement(LogTimestamp, null),
|
|
26327
|
+
args.withoutTimestamp ? null : reactExports.createElement(LogTimestamp, null),
|
|
26317
26328
|
output));
|
|
26318
26329
|
}
|
|
26319
26330
|
return output;
|
|
@@ -26846,6 +26857,30 @@ function match_group(value, re, group) {
|
|
|
26846
26857
|
invariant(result, `match.groups must contain [${group}] ${debug}`);
|
|
26847
26858
|
return result;
|
|
26848
26859
|
}
|
|
26860
|
+
match_group.safe = (value, re, group) => {
|
|
26861
|
+
try {
|
|
26862
|
+
return match_group(value, re, group);
|
|
26863
|
+
}
|
|
26864
|
+
catch (err) {
|
|
26865
|
+
return null;
|
|
26866
|
+
}
|
|
26867
|
+
};
|
|
26868
|
+
|
|
26869
|
+
function auth_status(output) {
|
|
26870
|
+
let username;
|
|
26871
|
+
username = match_group.safe(output, RE$4.logged_in_as, "username");
|
|
26872
|
+
if (username)
|
|
26873
|
+
return username;
|
|
26874
|
+
username = match_group.safe(output, RE$4.logged_in_account, "username");
|
|
26875
|
+
if (username)
|
|
26876
|
+
return username;
|
|
26877
|
+
return null;
|
|
26878
|
+
}
|
|
26879
|
+
const RE$4 = {
|
|
26880
|
+
// Logged in to github.com as magus
|
|
26881
|
+
logged_in_as: /Logged in to github.com as (?<username>[^\s]+)/,
|
|
26882
|
+
logged_in_account: /Logged in to github.com account (?<username>[^\s]+)/,
|
|
26883
|
+
};
|
|
26849
26884
|
|
|
26850
26885
|
function DependencyCheck(props) {
|
|
26851
26886
|
const actions = Store.useActions();
|
|
@@ -26903,15 +26938,19 @@ function DependencyCheck(props) {
|
|
|
26903
26938
|
"Checking ",
|
|
26904
26939
|
reactExports.createElement(Command, null, "gh auth status"),
|
|
26905
26940
|
"...")), function: async () => {
|
|
26906
|
-
const
|
|
26907
|
-
|
|
26908
|
-
|
|
26909
|
-
|
|
26910
|
-
|
|
26911
|
-
|
|
26912
|
-
|
|
26913
|
-
|
|
26914
|
-
|
|
26941
|
+
const options = { ignoreExitCode: true };
|
|
26942
|
+
const auth_status$1 = await cli(`gh auth status`, options);
|
|
26943
|
+
if (auth_status$1.code === 0) {
|
|
26944
|
+
const username = auth_status(auth_status$1.stdout);
|
|
26945
|
+
if (username) {
|
|
26946
|
+
actions.set((state) => {
|
|
26947
|
+
state.username = username;
|
|
26948
|
+
});
|
|
26949
|
+
return;
|
|
26950
|
+
}
|
|
26951
|
+
}
|
|
26952
|
+
if (actions.isDebug()) {
|
|
26953
|
+
actions.error("gh auth status could not find username");
|
|
26915
26954
|
}
|
|
26916
26955
|
actions.output(reactExports.createElement(Text, { color: colors.yellow },
|
|
26917
26956
|
reactExports.createElement(Command, null, "gh"),
|
|
@@ -26920,10 +26959,6 @@ function DependencyCheck(props) {
|
|
|
26920
26959
|
actions.exit(4);
|
|
26921
26960
|
} }, props.children)))));
|
|
26922
26961
|
}
|
|
26923
|
-
const RE$4 = {
|
|
26924
|
-
// Logged in to github.com as magus
|
|
26925
|
-
auth_username: /Logged in to github.com as (?<username>[^\s]+)/,
|
|
26926
|
-
};
|
|
26927
26962
|
|
|
26928
26963
|
function GatherMetadata(props) {
|
|
26929
26964
|
const argv = Store.useState((state) => state.argv);
|
|
@@ -34080,7 +34115,7 @@ async function command() {
|
|
|
34080
34115
|
.wrap(null)
|
|
34081
34116
|
// disallow unknown options
|
|
34082
34117
|
.strict()
|
|
34083
|
-
.version("1.0.
|
|
34118
|
+
.version("1.0.7" )
|
|
34084
34119
|
.showHidden("show-hidden", "Show hidden options via `git stack help --show-hidden`")
|
|
34085
34120
|
.help("help", "Show usage via `git stack help`").argv);
|
|
34086
34121
|
}
|
package/package.json
CHANGED
package/scripts/release-brew.ts
CHANGED
|
@@ -50,7 +50,7 @@ previous_formula = previous_formula.replace(
|
|
|
50
50
|
);
|
|
51
51
|
|
|
52
52
|
await file.write_text(
|
|
53
|
-
path.join(HOMEBREW_DIR, "Formula", `git-stack@${previous_version}`),
|
|
53
|
+
path.join(HOMEBREW_DIR, "Formula", `git-stack@${previous_version}.rb`),
|
|
54
54
|
previous_formula
|
|
55
55
|
);
|
|
56
56
|
|
|
@@ -94,20 +94,28 @@ core = core.replace(re_token("linux_sha256"), linux_asset.sha256);
|
|
|
94
94
|
|
|
95
95
|
await file.write_text(path.join("Formula", "git-stack.core.rb"), core);
|
|
96
96
|
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
// finally upload the assets to the github release
|
|
98
|
+
process.chdir(STANDALONE_DIR);
|
|
99
|
+
await spawn.sync(`gh release upload ${version} ${linux_asset.filepath}`);
|
|
100
|
+
await spawn.sync(`gh release upload ${version} ${macos_asset.filepath}`);
|
|
101
|
+
await spawn.sync(`gh release upload ${version} ${win_asset.filepath}`);
|
|
100
102
|
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
103
|
+
// commit homebrew repo changes
|
|
104
|
+
process.chdir(HOMEBREW_DIR);
|
|
105
|
+
await spawn.sync(`git add .`);
|
|
106
|
+
await spawn.sync(`git commit -m ${version}`);
|
|
107
|
+
await spawn.sync(`git push`);
|
|
105
108
|
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
// commmit changes to main repo
|
|
110
|
+
process.chdir(PROJECT_DIR);
|
|
111
|
+
await spawn.sync([
|
|
112
|
+
"git",
|
|
113
|
+
"commit",
|
|
114
|
+
"-a",
|
|
115
|
+
"-m",
|
|
116
|
+
`homebrew-git-stack ${version}`,
|
|
117
|
+
]);
|
|
118
|
+
await spawn.sync(`git push`);
|
|
111
119
|
|
|
112
120
|
console.debug();
|
|
113
121
|
console.debug("✅", "published", version);
|
|
@@ -10,8 +10,8 @@ import { Url } from "~/app/Url";
|
|
|
10
10
|
import { cli } from "~/core/cli";
|
|
11
11
|
import { colors } from "~/core/colors";
|
|
12
12
|
import { is_command_available } from "~/core/is_command_available";
|
|
13
|
-
import { match_group } from "~/core/match_group";
|
|
14
13
|
import { semver_compare } from "~/core/semver_compare";
|
|
14
|
+
import * as gh from "~/github/gh";
|
|
15
15
|
|
|
16
16
|
type Props = {
|
|
17
17
|
children: React.ReactNode;
|
|
@@ -112,22 +112,23 @@ export function DependencyCheck(props: Props) {
|
|
|
112
112
|
</Ink.Text>
|
|
113
113
|
}
|
|
114
114
|
function={async () => {
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
});
|
|
115
|
+
const options = { ignoreExitCode: true };
|
|
116
|
+
const auth_status = await cli(`gh auth status`, options);
|
|
118
117
|
|
|
119
118
|
if (auth_status.code === 0) {
|
|
120
|
-
const username =
|
|
121
|
-
auth_status.stdout,
|
|
122
|
-
RE.auth_username,
|
|
123
|
-
"username"
|
|
124
|
-
);
|
|
119
|
+
const username = gh.auth_status(auth_status.stdout);
|
|
125
120
|
|
|
126
|
-
|
|
127
|
-
state
|
|
128
|
-
|
|
121
|
+
if (username) {
|
|
122
|
+
actions.set((state) => {
|
|
123
|
+
state.username = username;
|
|
124
|
+
});
|
|
129
125
|
|
|
130
|
-
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (actions.isDebug()) {
|
|
131
|
+
actions.error("gh auth status could not find username");
|
|
131
132
|
}
|
|
132
133
|
|
|
133
134
|
actions.output(
|
|
@@ -148,8 +149,3 @@ export function DependencyCheck(props: Props) {
|
|
|
148
149
|
</Await>
|
|
149
150
|
);
|
|
150
151
|
}
|
|
151
|
-
|
|
152
|
-
const RE = {
|
|
153
|
-
// Logged in to github.com as magus
|
|
154
|
-
auth_username: /Logged in to github.com as (?<username>[^\s]+)/,
|
|
155
|
-
};
|
package/src/app/Store.tsx
CHANGED
|
@@ -21,6 +21,7 @@ type MutateOutputArgs = {
|
|
|
21
21
|
node: React.ReactNode;
|
|
22
22
|
id?: string;
|
|
23
23
|
debug?: boolean;
|
|
24
|
+
withoutTimestamp?: boolean;
|
|
24
25
|
};
|
|
25
26
|
|
|
26
27
|
export type State = {
|
|
@@ -194,11 +195,22 @@ const BaseStore = createStore<State>()(
|
|
|
194
195
|
return;
|
|
195
196
|
}
|
|
196
197
|
|
|
198
|
+
// set `withoutTimestamp` to skip <LogTimestamp> for all subsequent pending outputs
|
|
199
|
+
// we only want to timestamp for the first part (when we initialize the [])
|
|
200
|
+
// if we have many incremental outputs on the same line we do not want multiple timestamps
|
|
201
|
+
//
|
|
202
|
+
// await Promise.all([
|
|
203
|
+
// cli(`for i in $(seq 1 5); do echo $i; sleep 1; done`),
|
|
204
|
+
// cli(`for i in $(seq 5 1); do printf "$i "; sleep 1; done; echo`),
|
|
205
|
+
// ]);
|
|
206
|
+
//
|
|
207
|
+
let withoutTimestamp = true;
|
|
197
208
|
if (!state.pending_output[id]) {
|
|
209
|
+
withoutTimestamp = false;
|
|
198
210
|
state.pending_output[id] = [];
|
|
199
211
|
}
|
|
200
212
|
|
|
201
|
-
const renderOutput = renderOutputArgs(args);
|
|
213
|
+
const renderOutput = renderOutputArgs({ ...args, withoutTimestamp });
|
|
202
214
|
state.pending_output[id].push(renderOutput);
|
|
203
215
|
},
|
|
204
216
|
|
|
@@ -228,7 +240,7 @@ function renderOutputArgs(args: MutateOutputArgs) {
|
|
|
228
240
|
if (args.debug) {
|
|
229
241
|
return (
|
|
230
242
|
<React.Fragment>
|
|
231
|
-
<LogTimestamp />
|
|
243
|
+
{args.withoutTimestamp ? null : <LogTimestamp />}
|
|
232
244
|
{output}
|
|
233
245
|
</React.Fragment>
|
|
234
246
|
);
|
package/src/core/match_group.ts
CHANGED
|
@@ -8,3 +8,11 @@ export function match_group(value: string, re: RegExp, group: string) {
|
|
|
8
8
|
invariant(result, `match.groups must contain [${group}] ${debug}`);
|
|
9
9
|
return result;
|
|
10
10
|
}
|
|
11
|
+
|
|
12
|
+
match_group.safe = (value: string, re: RegExp, group: string) => {
|
|
13
|
+
try {
|
|
14
|
+
return match_group(value, re, group);
|
|
15
|
+
} catch (err) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { test, expect } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import * as gh from "./gh";
|
|
4
|
+
|
|
5
|
+
test("logged in as", () => {
|
|
6
|
+
const username = gh.auth_status(
|
|
7
|
+
" ✓ Logged in to github.com as magus (keyring)\n"
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
expect(username).toBe("magus");
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test("logged in without as", () => {
|
|
14
|
+
const username = gh.auth_status(
|
|
15
|
+
"✓ Logged in to github.com account xoxohorses (keyring)"
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
expect(username).toBe("xoxohorses");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("returns null when no match is found", () => {
|
|
22
|
+
const username = gh.auth_status("this should not match anything");
|
|
23
|
+
expect(username).toBe(null);
|
|
24
|
+
});
|
package/src/github/gh.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { match_group } from "~/core/match_group";
|
|
2
|
+
|
|
3
|
+
export function auth_status(output: string) {
|
|
4
|
+
let username;
|
|
5
|
+
|
|
6
|
+
username = match_group.safe(output, RE.logged_in_as, "username");
|
|
7
|
+
if (username) return username;
|
|
8
|
+
|
|
9
|
+
username = match_group.safe(output, RE.logged_in_account, "username");
|
|
10
|
+
if (username) return username;
|
|
11
|
+
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const RE = {
|
|
16
|
+
// Logged in to github.com as magus
|
|
17
|
+
logged_in_as: /Logged in to github.com as (?<username>[^\s]+)/,
|
|
18
|
+
logged_in_account: /Logged in to github.com account (?<username>[^\s]+)/,
|
|
19
|
+
};
|