git-stack-cli 2.8.0 → 2.8.2

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.8.0",
3
+ "version": "2.8.2",
4
4
  "description": "",
5
5
  "author": "magus",
6
6
  "license": "MIT",
@@ -6,6 +6,9 @@ import { spawn } from "~/core/spawn";
6
6
 
7
7
  process.env.NODE_ENV = "production";
8
8
 
9
+ // ensure npm is authenticated
10
+ await spawn(`npm whoami`);
11
+
9
12
  // get paths relative to this script
10
13
  const REPO_ROOT = (await spawn.sync("git rev-parse --show-toplevel")).stdout;
11
14
  const DIST_DIR = path.join(REPO_ROOT, "dist");
@@ -54,9 +57,6 @@ for (const filepath of package_json.files) {
54
57
  }
55
58
  }
56
59
 
57
- // ensure npm is authenticated
58
- await spawn(`npm whoami`);
59
-
60
60
  process.chdir(REPO_ROOT);
61
61
 
62
62
  await spawn.sync(`git commit -a -m ${version}`);
@@ -0,0 +1,33 @@
1
+ import { test, expect } from "bun:test";
2
+
3
+ import * as GatherMetadata from "~/app/GatherMetadata";
4
+
5
+ test("invalid origin", () => {
6
+ const origin_url = "";
7
+ const repo_path = GatherMetadata.get_repo_path(origin_url);
8
+ expect(repo_path).toBe(null);
9
+ });
10
+
11
+ test("https .git", () => {
12
+ const origin_url = "https://github.com/magus/git-multi-diff-playground.git";
13
+ const repo_path = GatherMetadata.get_repo_path(origin_url);
14
+ expect(repo_path).toBe("magus/git-multi-diff-playground");
15
+ });
16
+
17
+ test("https without .git", () => {
18
+ const origin_url = "https://github.com/magus/git-multi-diff-playground";
19
+ const repo_path = GatherMetadata.get_repo_path(origin_url);
20
+ expect(repo_path).toBe("magus/git-multi-diff-playground");
21
+ });
22
+
23
+ test("git@ .git", () => {
24
+ const origin_url = "git@github.com:magus/git-multi-diff-playground.git";
25
+ const repo_path = GatherMetadata.get_repo_path(origin_url);
26
+ expect(repo_path).toBe("magus/git-multi-diff-playground");
27
+ });
28
+
29
+ test("git@ without .git", () => {
30
+ const origin_url = "git@github.com:magus/git-multi-diff-playground";
31
+ const repo_path = GatherMetadata.get_repo_path(origin_url);
32
+ expect(repo_path).toBe("magus/git-multi-diff-playground");
33
+ });
@@ -97,10 +97,21 @@ async function run() {
97
97
  }
98
98
  }
99
99
 
100
+ export function get_repo_path(origin_url: string): null | string {
101
+ try {
102
+ return match_group(origin_url, RE.repo_path, "repo_path");
103
+ } catch {
104
+ // pass
105
+ }
106
+ return null;
107
+ }
108
+
100
109
  const RE = {
101
110
  // git@github.com:magus/git-multi-diff-playground.git
102
111
  // https://github.com/magus/git-multi-diff-playground.git
103
- repo_path: /(?<repo_path>[^:^/]+\/[^/]+)\.git/,
112
+ // the .git suffix is optional for remote urls, and may not always be provided
113
+ // https://regex101.com/r/pICG7G/1
114
+ repo_path: /(?<repo_path>[^:^/]+\/[^/]+?)(\.git)?$/,
104
115
  };
105
116
 
106
117
  const BRANCH = {
@@ -39,10 +39,11 @@ async function run() {
39
39
  // ./pull_request_template.md
40
40
  // ./docs/pull_request_template.md
41
41
  for (const key of PR_TEMPLATE_KEY_LIST) {
42
- const pr_template_fn = PR_TEMPLATE[key as keyof typeof PR_TEMPLATE];
42
+ const pr_template_fn = PR_TEMPLATE[key];
43
+ const pr_template_file = pr_template_fn(repo_root);
43
44
 
44
- if (await safe_exists(pr_template_fn(repo_root))) {
45
- pr_template_body = await fs.readFile(pr_template_fn(repo_root), "utf-8");
45
+ if (await safe_exists(pr_template_file)) {
46
+ pr_template_body = await fs.readFile(pr_template_file, "utf-8");
46
47
 
47
48
  actions.output(
48
49
  <FormatText
@@ -58,10 +59,23 @@ async function run() {
58
59
  }
59
60
  }
60
61
 
61
- // ./.github/PULL_REQUEST_TEMPLATE/*.md
62
62
  let pr_templates: Array<string> = [];
63
- if (await safe_exists(PR_TEMPLATE.TemplateDir(repo_root))) {
64
- pr_templates = await fs.readdir(PR_TEMPLATE.TemplateDir(repo_root));
63
+ let pr_dir: string = "";
64
+
65
+ // ./.github/PULL_REQUEST_TEMPLATE/*.md
66
+ pr_dir = PR_TEMPLATE.DirGithub(repo_root);
67
+ if (await safe_exists(pr_dir)) {
68
+ for (const filename of await fs.readdir(pr_dir)) {
69
+ pr_templates.push(path.join(pr_dir, filename));
70
+ }
71
+ }
72
+
73
+ // ./docs/PULL_REQUEST_TEMPLATE/*.md
74
+ pr_dir = PR_TEMPLATE.DirDocs(repo_root);
75
+ if (await safe_exists(pr_dir)) {
76
+ for (const filename of await fs.readdir(pr_dir)) {
77
+ pr_templates.push(path.join(pr_dir, filename));
78
+ }
65
79
  }
66
80
 
67
81
  // check if repo has multiple pr templates
@@ -71,14 +85,20 @@ async function run() {
71
85
 
72
86
  if (pr_templates.length > 0) {
73
87
  actions.output(
74
- <FormatText
75
- wrapper={<Ink.Text color={colors.yellow} />}
76
- message="{count} queryable templates found under {dir}, but not supported."
77
- values={{
78
- count: <Ink.Text color={colors.blue}>{pr_templates.length}</Ink.Text>,
79
- dir: <Brackets>{PR_TEMPLATE.TemplateDir("")}</Brackets>,
80
- }}
81
- />,
88
+ <Ink.Box flexDirection="column">
89
+ {pr_templates.map((filepath) => {
90
+ const relpath = path.relative(repo_root, filepath);
91
+ return <Ink.Text key={filepath}>- {relpath}</Ink.Text>;
92
+ })}
93
+
94
+ <FormatText
95
+ wrapper={<Ink.Text color={colors.yellow} />}
96
+ message="{count} queryable templates found, but not supported."
97
+ values={{
98
+ count: <Ink.Text color={colors.blue}>{pr_templates.length}</Ink.Text>,
99
+ }}
100
+ />
101
+ </Ink.Box>,
82
102
  );
83
103
  }
84
104
 
@@ -91,8 +111,11 @@ const PR_TEMPLATE = Object.freeze({
91
111
  Github: (root: string) => path.join(root, ".github", "pull_request_template.md"),
92
112
  Root: (root: string) => path.join(root, "pull_request_template.md"),
93
113
  Docs: (root: string) => path.join(root, "docs", "pull_request_template.md"),
94
- TemplateDir: (root: string) => path.join(root, ".github", "PULL_REQUEST_TEMPLATE"),
114
+
115
+ DirDocs: (root: string) => path.join(root, "docs", "PULL_REQUEST_TEMPLATE/"),
116
+ DirGithub: (root: string) => path.join(root, ".github", "PULL_REQUEST_TEMPLATE/"),
95
117
  });
96
118
 
97
119
  // prettier-ignore
98
- const PR_TEMPLATE_KEY_LIST = Object.keys(PR_TEMPLATE) as Array<keyof typeof PR_TEMPLATE>;
120
+ //
121
+ const PR_TEMPLATE_KEY_LIST = ["Github", "Root", "Docs"] satisfies Array<keyof typeof PR_TEMPLATE>;
@@ -75,12 +75,15 @@ async function run() {
75
75
 
76
76
  const git_push_target_list: Array<string> = [];
77
77
 
78
- for (const group of push_group_list) {
78
+ for (let i = 0; i < push_group_list.length; i++) {
79
+ const group = push_group_list[i];
79
80
  const last_commit = last(group.commits);
80
81
  invariant(last_commit, "last_commit must exist");
81
82
 
82
83
  // push group in isolation if master_base is set
83
- if (group.master_base) {
84
+ // for the first group (i > 0) we can skip this
85
+ // since it'll be based off master anyway
86
+ if (group.master_base && i > 0) {
84
87
  await push_master_group(group);
85
88
  continue;
86
89
  }
@@ -237,8 +240,6 @@ async function run() {
237
240
  if (!is_master_base(group)) {
238
241
  // ensure base matches pr in github
239
242
  await github.pr_edit({ branch: group.id, base: group.base });
240
- } else {
241
- await github.pr_edit({ branch: group.id });
242
243
  }
243
244
  } else {
244
245
  // create pr in github
@@ -121,6 +121,7 @@ export async function range(commit_group_map?: CommitGroupMap) {
121
121
 
122
122
  for (let i = 0; i < group_value_list.length; i++) {
123
123
  const group = group_value_list[i];
124
+ const previous_group: undefined | CommitGroup = group_value_list[i - 1];
124
125
 
125
126
  if (group.id !== UNASSIGNED) {
126
127
  let pr_result = pr_lookup[group.id];
@@ -164,29 +165,61 @@ export async function range(commit_group_map?: CommitGroupMap) {
164
165
 
165
166
  if (!group.pr) {
166
167
  group.dirty = true;
167
- } else if (group.pr.commits.length !== group.commits.length) {
168
- group.dirty = true;
169
- } else if (group.pr.baseRefName !== group.base) {
170
- group.dirty = true;
171
- } else if (group.master_base) {
172
- group.pr.number;
173
- // master_base groups cannot be compared by commit sha
174
- // instead compare the literal diff local against origin
175
- // gh pr diff --color=never 110
176
- // git --no-pager diff --color=never 00c8fe0~1..00c8fe0 | pbcopy
177
- const diff_github = await github.pr_diff(group.pr.number);
178
- const diff_local = await git.get_diff(group.commits);
179
- if (diff_github !== diff_local) {
180
- group.dirty = true;
181
- }
182
168
  } else {
183
- // comapre literal commit shas in group
184
- for (let i = 0; i < group.pr.commits.length; i++) {
185
- const pr_commit = group.pr.commits[i];
186
- const local_commit = group.commits[i];
169
+ if (group.pr.baseRefName !== group.base) {
170
+ group.dirty = true;
171
+ } else if (group.master_base && i > 0) {
172
+ // special case
173
+ // master_base groups cannot be compared by commit sha
174
+ // instead compare the literal diff local against origin
175
+ // gh pr diff --color=never 110
176
+ // git --no-pager diff --color=never 00c8fe0~1..00c8fe0
177
+ const diff_github = await github.pr_diff(group.pr.number);
178
+ const diff_local = await git.get_diff(group.commits);
179
+ if (diff_github !== diff_local) {
180
+ group.dirty = true;
181
+ }
182
+ } else if (!group.master_base && previous_group && previous_group.master_base) {
183
+ // special case
184
+ // boundary between normal commits and master commits
185
+
186
+ // collect all previous groups for sha comparison
187
+ const all_commits: Array<git.Commit> = [];
188
+ const previous_groups = group_value_list.slice(0, i);
189
+ for (const g of previous_groups) {
190
+ for (const c of g.commits) {
191
+ all_commits.push(c);
192
+ }
193
+ }
194
+ for (const c of group.commits) {
195
+ all_commits.push(c);
196
+ }
187
197
 
188
- if (pr_commit.oid !== local_commit.sha) {
198
+ // compare all commits against pr commits
199
+ if (group.pr.commits.length !== all_commits.length) {
189
200
  group.dirty = true;
201
+ } else {
202
+ for (let i = 0; i < group.pr.commits.length; i++) {
203
+ const pr_commit = group.pr.commits[i];
204
+ const local_commit = all_commits[i];
205
+
206
+ if (pr_commit.oid !== local_commit.sha) {
207
+ group.dirty = true;
208
+ }
209
+ }
210
+ }
211
+ } else if (group.pr.commits.length !== group.commits.length) {
212
+ group.dirty = true;
213
+ } else {
214
+ // if we still haven't marked this dirty, check each commit
215
+ // comapre literal commit shas in group
216
+ for (let i = 0; i < group.pr.commits.length; i++) {
217
+ const pr_commit = group.pr.commits[i];
218
+ const local_commit = group.commits[i];
219
+
220
+ if (pr_commit.oid !== local_commit.sha) {
221
+ group.dirty = true;
222
+ }
190
223
  }
191
224
  }
192
225
  }
@@ -171,6 +171,10 @@ export async function pr_edit(args: EditPullRequestArgs) {
171
171
  // const actions = state.actions;
172
172
  // actions.debug(`github.pr_edit ${JSON.stringify(args)}`);
173
173
 
174
+ if (!args.base && !args.body) {
175
+ return;
176
+ }
177
+
174
178
  const command_parts = [`gh pr edit ${args.branch}`];
175
179
 
176
180
  if (args.base) {