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 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 js_max_bits = 53;
39158
- const timestamp_bits = Math.floor(Math.log2(timestamp)) + 1;
39159
- const padding_bits = js_max_bits - timestamp_bits;
39160
- const random = crypto2.randomInt(0, Math.pow(2, padding_bits));
39161
- const combined = interleave_bits(timestamp, random);
39162
- return encode(combined);
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
- while (value > 0) {
39189
- result = chars[value % chars.length] + result;
39190
- value = Math.floor(value / chars.length);
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
- return result.padStart(max_char_size, "=");
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.3.2").showHidden("show-hidden", "Show hidden options via `git stack help --show-hidden`").help("help", "Show usage via `git stack help`").argv;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-stack-cli",
3
- "version": "2.3.2",
3
+ "version": "2.4.1",
4
4
  "description": "",
5
5
  "author": "magus",
6
6
  "license": "MIT",
@@ -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
+ });
@@ -1,87 +1,70 @@
1
1
  import crypto from "node:crypto";
2
2
 
3
3
  export function short_id() {
4
- const timestamp = Date.now();
4
+ // wall-clock milliseconds
5
+ const timestamp = BigInt(Date.now());
5
6
 
6
- // 9 223 372 036 854 775 808
7
- // 9 trillion possible values
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
- const timestamp_bits = Math.floor(Math.log2(timestamp)) + 1;
10
+ // concatenate timestamp (high bits) and random value
11
+ const combined = (timestamp << BigInt(RANDOM_BITS)) | random;
12
12
 
13
- // padding needed to reach 53 bits
14
- const padding_bits = js_max_bits - timestamp_bits;
13
+ const id = encode(combined, ID_LENGTH);
15
14
 
16
- // random between 0 and 2^padding_bits - 1
17
- const random = crypto.randomInt(0, Math.pow(2, padding_bits));
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
- function rand_index(list: Array<any>) {
32
- return Math.floor(Math.random() * list.length);
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
- const a_index = rand_index(a_binary);
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
- a_binary.splice(a_index, 0, selected);
30
+ // drop the digit we just divided by
31
+ value /= BASE;
32
+ }
52
33
  }
53
34
 
54
- // convert binary list back to integer
55
-
56
- const a_value = parseInt(a_binary.join(""), 2);
35
+ // left pad with 0 to guarantee chars
36
+ if (length) {
37
+ result = result.padStart(length, PAD);
38
+ }
57
39
 
58
- return a_value;
40
+ return result;
59
41
  }
60
42
 
61
- function encode(value: number) {
62
- // base64 encode (64 characters)
63
- // max character necessary to encode is equal to maximum number
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
- // pad the result to necessary characters
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);