git-stack-cli 2.3.2 → 2.4.1
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/dist/js/index.js +32 -36
- package/package.json +1 -1
- package/scripts/release-npm.ts +5 -5
- package/src/core/short_id.test.ts +30 -0
- package/src/core/short_id.ts +51 -68
package/dist/js/index.js
CHANGED
|
@@ -39153,44 +39153,40 @@ async function run4() {
|
|
|
39153
39153
|
// src/core/short_id.ts
|
|
39154
39154
|
import crypto2 from "node:crypto";
|
|
39155
39155
|
function short_id() {
|
|
39156
|
-
const timestamp = Date.now();
|
|
39157
|
-
const
|
|
39158
|
-
const
|
|
39159
|
-
const
|
|
39160
|
-
|
|
39161
|
-
|
|
39162
|
-
|
|
39163
|
-
}
|
|
39164
|
-
function binary(value) {
|
|
39165
|
-
return BigInt(value).toString(2);
|
|
39166
|
-
}
|
|
39167
|
-
function rand_index(list) {
|
|
39168
|
-
return Math.floor(Math.random() * list.length);
|
|
39169
|
-
}
|
|
39170
|
-
function interleave_bits(a, b2) {
|
|
39171
|
-
const a_binary = binary(a).split("");
|
|
39172
|
-
const b_binary = binary(b2).split("");
|
|
39173
|
-
while (b_binary.length) {
|
|
39174
|
-
const b_index = rand_index(b_binary);
|
|
39175
|
-
const [selected] = b_binary.splice(b_index, 1);
|
|
39176
|
-
const a_index = rand_index(a_binary);
|
|
39177
|
-
a_binary.splice(a_index, 0, selected);
|
|
39178
|
-
}
|
|
39179
|
-
const a_value = parseInt(a_binary.join(""), 2);
|
|
39180
|
-
return a_value;
|
|
39181
|
-
}
|
|
39182
|
-
function encode(value) {
|
|
39183
|
-
const chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-+";
|
|
39184
|
-
const bits_per_char = Math.log2(chars.length);
|
|
39185
|
-
const max_value_bits = 53;
|
|
39186
|
-
const max_char_size = Math.ceil(max_value_bits / bits_per_char);
|
|
39156
|
+
const timestamp = BigInt(Date.now());
|
|
39157
|
+
const random = BigInt(crypto2.randomInt(0, 1 << RANDOM_BITS));
|
|
39158
|
+
const combined = timestamp << BigInt(RANDOM_BITS) | random;
|
|
39159
|
+
const id = encode(combined, ID_LENGTH);
|
|
39160
|
+
return id;
|
|
39161
|
+
}
|
|
39162
|
+
function encode(value, length) {
|
|
39187
39163
|
let result = "";
|
|
39188
|
-
|
|
39189
|
-
|
|
39190
|
-
|
|
39164
|
+
if (value > 0n) {
|
|
39165
|
+
while (value > 0n) {
|
|
39166
|
+
const digit = to_number(value % BASE);
|
|
39167
|
+
result = CHARS[digit] + result;
|
|
39168
|
+
value /= BASE;
|
|
39169
|
+
}
|
|
39170
|
+
}
|
|
39171
|
+
if (length) {
|
|
39172
|
+
result = result.padStart(length, PAD);
|
|
39173
|
+
}
|
|
39174
|
+
return result;
|
|
39175
|
+
}
|
|
39176
|
+
function to_number(value) {
|
|
39177
|
+
if (value >= BigInt(Number.MIN_SAFE_INTEGER) && value <= BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
39178
|
+
return Number(value);
|
|
39191
39179
|
}
|
|
39192
|
-
|
|
39180
|
+
throw new Error("BigInt value outside safe integer range");
|
|
39193
39181
|
}
|
|
39182
|
+
var CHARS = "-0123456789_abcdefghijklmnopqrstuvwxyz".split("").sort().join("");
|
|
39183
|
+
var PAD = CHARS[0];
|
|
39184
|
+
var BASE = BigInt(CHARS.length);
|
|
39185
|
+
var CHAR_BITS = Math.log2(to_number(BASE));
|
|
39186
|
+
var TIMESTAMP_BITS = 53;
|
|
39187
|
+
var RANDOM_BITS = 30;
|
|
39188
|
+
var ID_BITS = TIMESTAMP_BITS + RANDOM_BITS;
|
|
39189
|
+
var ID_LENGTH = Math.ceil(ID_BITS / CHAR_BITS);
|
|
39194
39190
|
|
|
39195
39191
|
// src/commands/Rebase.tsx
|
|
39196
39192
|
function Rebase(props) {
|
|
@@ -45666,7 +45662,7 @@ var yargs_default = Yargs;
|
|
|
45666
45662
|
|
|
45667
45663
|
// src/command.ts
|
|
45668
45664
|
async function command2() {
|
|
45669
|
-
return yargs_default(hideBin(process.argv)).scriptName("git stack").usage("Usage: git stack [command] [options]").command("$0", "Sync commit ranges to Github", (yargs) => yargs.options(DefaultOptions)).command("fixup [commit]", "Amend staged changes to a specific commit in history", (yargs) => yargs.positional("commit", FixupOptions.commit)).command("log [args...]", "Print an abbreviated log with numbered commits, useful for git stack fixup", (yargs) => yargs.strict(false)).command("rebase", "Update local branch via rebase with latest changes from origin master branch", (yargs) => yargs).option("verbose", GlobalOptions.verbose).wrap(123).strict().version("2.
|
|
45665
|
+
return yargs_default(hideBin(process.argv)).scriptName("git stack").usage("Usage: git stack [command] [options]").command("$0", "Sync commit ranges to Github", (yargs) => yargs.options(DefaultOptions)).command("fixup [commit]", "Amend staged changes to a specific commit in history", (yargs) => yargs.positional("commit", FixupOptions.commit)).command("log [args...]", "Print an abbreviated log with numbered commits, useful for git stack fixup", (yargs) => yargs.strict(false)).command("rebase", "Update local branch via rebase with latest changes from origin master branch", (yargs) => yargs).option("verbose", GlobalOptions.verbose).wrap(123).strict().version("2.4.1").showHidden("show-hidden", "Show hidden options via `git stack help --show-hidden`").help("help", "Show usage via `git stack help`").argv;
|
|
45670
45666
|
}
|
|
45671
45667
|
var GlobalOptions = {
|
|
45672
45668
|
verbose: {
|
package/package.json
CHANGED
package/scripts/release-npm.ts
CHANGED
|
@@ -55,11 +55,6 @@ for (const filepath of package_json.files) {
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
process.env.GS_RELEASE_NPM = "true";
|
|
59
|
-
console.info("Publishing to NPM requires a one-time password");
|
|
60
|
-
const otp = await input("Enter OTP: ");
|
|
61
|
-
await spawn(["npm", "publish", `--otp=${otp}`]);
|
|
62
|
-
|
|
63
58
|
process.chdir(REPO_ROOT);
|
|
64
59
|
|
|
65
60
|
await spawn.sync(`git commit -a -m ${version}`);
|
|
@@ -70,6 +65,11 @@ await spawn.sync(`git push`);
|
|
|
70
65
|
await spawn.sync(`git tag -a ${version} -m ${version}`);
|
|
71
66
|
await spawn.sync(`git push origin ${version}`);
|
|
72
67
|
|
|
68
|
+
process.env.GS_RELEASE_NPM = "true";
|
|
69
|
+
console.info("Publishing to NPM requires a one-time password");
|
|
70
|
+
const otp = await input("Enter OTP: ");
|
|
71
|
+
await spawn(["npm", "publish", `--otp=${otp}`]);
|
|
72
|
+
|
|
73
73
|
console.debug();
|
|
74
74
|
console.debug("✅", "published", version);
|
|
75
75
|
console.debug();
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { test, expect } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { short_id } from "~/core/short_id";
|
|
4
|
+
|
|
5
|
+
const GENERATE_COUNT = 1000;
|
|
6
|
+
|
|
7
|
+
test("short_id is 15 characters", () => {
|
|
8
|
+
const id = short_id();
|
|
9
|
+
expect(id.length).toBe(16);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test("unique enough in practice", () => {
|
|
13
|
+
const id_set = new Set();
|
|
14
|
+
for (let i = 0; i < GENERATE_COUNT; i++) {
|
|
15
|
+
const id = short_id();
|
|
16
|
+
id_set.add(id);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
expect(id_set.size).toBe(GENERATE_COUNT);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("sorts lexicographically", () => {
|
|
23
|
+
const id_list = [];
|
|
24
|
+
for (let i = 0; i < GENERATE_COUNT; i++) {
|
|
25
|
+
const id = short_id();
|
|
26
|
+
id_list.push(id);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
expect(id_list.sort()).toEqual(id_list);
|
|
30
|
+
});
|
package/src/core/short_id.ts
CHANGED
|
@@ -1,87 +1,70 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
2
|
|
|
3
3
|
export function short_id() {
|
|
4
|
-
|
|
4
|
+
// wall-clock milliseconds
|
|
5
|
+
const timestamp = BigInt(Date.now());
|
|
5
6
|
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
// (2^53) * (2^10) = 2^63 = 9,223,372,036,854,775,808
|
|
9
|
-
const js_max_bits = 53;
|
|
7
|
+
// random int value between 0 and 2^RANDOM_BITS - 1
|
|
8
|
+
const random = BigInt(crypto.randomInt(0, 1 << RANDOM_BITS));
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
// concatenate timestamp (high bits) and random value
|
|
11
|
+
const combined = (timestamp << BigInt(RANDOM_BITS)) | random;
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
const padding_bits = js_max_bits - timestamp_bits;
|
|
13
|
+
const id = encode(combined, ID_LENGTH);
|
|
15
14
|
|
|
16
|
-
//
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
// combine timestamp and random value
|
|
20
|
-
const combined = interleave_bits(timestamp, random);
|
|
21
|
-
|
|
22
|
-
// console.debug({ combined, timestamp, random, padding_bits, timestamp_bits });
|
|
23
|
-
|
|
24
|
-
return encode(combined);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function binary(value: number) {
|
|
28
|
-
return BigInt(value).toString(2);
|
|
15
|
+
// console.debug({ id, timestamp, random, combined });
|
|
16
|
+
return id;
|
|
29
17
|
}
|
|
30
18
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
function interleave_bits(a: number, b: number) {
|
|
36
|
-
const a_binary = binary(a).split("");
|
|
37
|
-
|
|
38
|
-
const b_binary = binary(b).split("");
|
|
39
|
-
|
|
40
|
-
while (b_binary.length) {
|
|
41
|
-
// pull random bit out of b_binary
|
|
42
|
-
|
|
43
|
-
const b_index = rand_index(b_binary);
|
|
44
|
-
|
|
45
|
-
const [selected] = b_binary.splice(b_index, 1);
|
|
46
|
-
|
|
47
|
-
// insert random bit into a_binary
|
|
19
|
+
// converting value into base based on available chars
|
|
20
|
+
function encode(value: bigint, length?: number) {
|
|
21
|
+
let result = "";
|
|
48
22
|
|
|
49
|
-
|
|
23
|
+
// avoid zero division
|
|
24
|
+
if (value > 0n) {
|
|
25
|
+
while (value > 0n) {
|
|
26
|
+
// convert least significant digit
|
|
27
|
+
const digit = to_number(value % BASE);
|
|
28
|
+
result = CHARS[digit] + result;
|
|
50
29
|
|
|
51
|
-
|
|
30
|
+
// drop the digit we just divided by
|
|
31
|
+
value /= BASE;
|
|
32
|
+
}
|
|
52
33
|
}
|
|
53
34
|
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
35
|
+
// left pad with 0 to guarantee chars
|
|
36
|
+
if (length) {
|
|
37
|
+
result = result.padStart(length, PAD);
|
|
38
|
+
}
|
|
57
39
|
|
|
58
|
-
return
|
|
40
|
+
return result;
|
|
59
41
|
}
|
|
60
42
|
|
|
61
|
-
function
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
// of bits in value divided by bits per character in encoding
|
|
65
|
-
//
|
|
66
|
-
// Example
|
|
67
|
-
// in base64 each characters can represent 6 bits (2^6 = 64)
|
|
68
|
-
// 53 bits / 6 bits = 8.833333333333334 characters (9 characters)
|
|
69
|
-
//
|
|
70
|
-
// prettier-ignore
|
|
71
|
-
const chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-+";
|
|
72
|
-
|
|
73
|
-
const bits_per_char = Math.log2(chars.length);
|
|
74
|
-
const max_value_bits = 53;
|
|
75
|
-
const max_char_size = Math.ceil(max_value_bits / bits_per_char);
|
|
76
|
-
|
|
77
|
-
let result = "";
|
|
78
|
-
|
|
79
|
-
while (value > 0) {
|
|
80
|
-
result = chars[value % chars.length] + result;
|
|
81
|
-
|
|
82
|
-
value = Math.floor(value / chars.length);
|
|
43
|
+
function to_number(value: bigint) {
|
|
44
|
+
if (value >= BigInt(Number.MIN_SAFE_INTEGER) && value <= BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
45
|
+
return Number(value);
|
|
83
46
|
}
|
|
84
47
|
|
|
85
|
-
|
|
86
|
-
return result.padStart(max_char_size, "=");
|
|
48
|
+
throw new Error("BigInt value outside safe integer range");
|
|
87
49
|
}
|
|
50
|
+
|
|
51
|
+
// valid characters to use for the short id
|
|
52
|
+
// use only lowercase letters and numbers to avoid
|
|
53
|
+
// confusing file system issues with git branch names
|
|
54
|
+
const CHARS = "-0123456789_abcdefghijklmnopqrstuvwxyz".split("").sort().join("");
|
|
55
|
+
const PAD = CHARS[0];
|
|
56
|
+
const BASE = BigInt(CHARS.length);
|
|
57
|
+
// bits carried by each char log2(BASE) ≈ 5.32
|
|
58
|
+
const CHAR_BITS = Math.log2(to_number(BASE));
|
|
59
|
+
|
|
60
|
+
// javascript Date.now returns a Number that will overflow eventually
|
|
61
|
+
// 2^53 max integer, overflows in 2^53 milliseconds (285_616 years)
|
|
62
|
+
// (1n<<53n) / 1_000n / 60n / 60n / 24n / 365n ≈ 285616n
|
|
63
|
+
const TIMESTAMP_BITS = 53;
|
|
64
|
+
|
|
65
|
+
// collision probability for identical timestamps (milliseconds)
|
|
66
|
+
// ≈ 1 / 2^30 ≈ 1 / 1_073_741_824 (1.1 billion)
|
|
67
|
+
const RANDOM_BITS = 30;
|
|
68
|
+
|
|
69
|
+
const ID_BITS = TIMESTAMP_BITS + RANDOM_BITS;
|
|
70
|
+
const ID_LENGTH = Math.ceil(ID_BITS / CHAR_BITS);
|