@shivaedev/quality 0.3.0 → 0.3.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/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.1 - 2026-10-03
4
+
5
+ ### Changed
6
+
7
+ - `quality baseline check` fetches the full history with `git fetch --unshallow`
8
+ when a shallow clone does not reach the merge base, and looks for it again
9
+ before it exits 2. The fetch never stops to ask for a
10
+ password.
11
+
3
12
  ## 0.3.0 - 2026-10-03
4
13
 
5
14
  ### Added
package/README.md CHANGED
@@ -252,7 +252,7 @@ quality baseline check
252
252
 
253
253
  It compares with the merge base, never the tip of the target branch, so a branch that is behind never fails for debt the target branch paid off since. An entry whose file git sees as moved since the merge base is compared with the entry at the old path. When the base holds the earlier JSON format at `quality/baseline.json`, the check converts it the way `migrate` does, so the change that migrates the baseline passes.
254
254
 
255
- The check reads git, not the sources, so it is cheap enough for every commit; only a base in the earlier format makes it run the rules. The target is `--against <ref>`, or else `origin/HEAD`, `origin/main` and then `origin/master`, whichever exists first. It exits 2 when there is no git work tree, no target or no merge base. A shallow clone usually has no merge base: in GitHub Actions, check out with `fetch-depth: 0`, or fetch enough history for `git merge-base HEAD <target>` to succeed.
255
+ The check reads git, not the sources, so it is cheap enough for every commit; only a base in the earlier format makes it run the rules. The target is `--against <ref>`, or else `origin/HEAD`, `origin/main` and then `origin/master`, whichever exists first. In a shallow clone that does not reach the merge base, it runs `git fetch --unshallow`, which never stops to ask for a password, and looks again. It exits 2 when there is no git work tree, no target or no merge base, including when that fetch fails; then check out with `fetch-depth: 0` in GitHub Actions, or fetch enough history for `git merge-base HEAD <target>` to succeed.
256
256
 
257
257
  ### Adopting a rule
258
258
 
@@ -1 +1 @@
1
- {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../src/git/base.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,KAAK,GAAG,EAAO,MAAM,cAAc,CAAC;AAE7C,MAAM,WAAW,IAAI;IACpB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB;AAkCD,eAAO,MAAM,WAAW,GAAI,MAAM,MAAM,EAAE,SAAS,MAAM,GAAG,SAAS,KAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,YAAY,EAAE,GAAG,CAY1G,CAAC"}
1
+ {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../src/git/base.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,MAAM,EAAE,MAAM,QAAQ,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,KAAK,GAAG,EAAO,MAAM,cAAc,CAAC;AAE7C,MAAM,WAAW,IAAI;IACpB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB;AAsED,eAAO,MAAM,WAAW,GAAI,MAAM,MAAM,EAAE,SAAS,MAAM,GAAG,SAAS,KAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,YAAY,EAAE,GAAG,CAW1G,CAAC"}
package/dist/git/base.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Effect } from "effect";
1
+ import { Console, Effect } from "effect";
2
2
  import { SetupFailure } from "../failure.js";
3
3
  import { git } from "./command.js";
4
4
  const FALLBACK_BRANCHES = ["origin/main", "origin/master"];
@@ -16,9 +16,35 @@ const defaultBranch = (root) => Effect.gen(function* () {
16
16
  }
17
17
  return yield* fail("cannot find the default branch: origin/HEAD, origin/main and origin/master are missing. Pass --against <ref>, or run `git remote set-head origin --auto`.");
18
18
  });
19
- const missingMergeBase = (root, ref) => Effect.flatMap(git(root, ["rev-parse", "--is-shallow-repository"]), (shallow) => fail(shallow.stdout.trim() === "true"
19
+ const isShallow = (root) => Effect.map(git(root, ["rev-parse", "--is-shallow-repository"]), (result) => result.stdout.trim() === "true");
20
+ const mergeBase = (root, ref) => Effect.map(git(root, ["merge-base", "HEAD", ref]), (result) => (result.code === 0 ? result.stdout.trim() : undefined));
21
+ const remoteOf = (root, ref) => Effect.gen(function* () {
22
+ const name = (yield* git(root, ["rev-parse", "--symbolic-full-name", ref])).stdout.trim();
23
+ const remotes = (yield* git(root, ["remote"])).stdout.split("\n");
24
+ return remotes.filter((remote) => remote !== "" && name.startsWith(`refs/remotes/${remote}/`)).slice(0, 1);
25
+ });
26
+ const unshallow = (root, ref) => Effect.gen(function* () {
27
+ const remote = yield* remoteOf(root, ref);
28
+ yield* Console.error(`quality: the clone is shallow; fetching its full history to find the merge base of HEAD and ${ref}.`);
29
+ yield* git(root, ["fetch", "--quiet", "--unshallow", ...remote], { GIT_TERMINAL_PROMPT: "0" });
30
+ });
31
+ const missingMergeBase = (root, ref) => Effect.flatMap(isShallow(root), (shallow) => fail(shallow
20
32
  ? `the clone is shallow and does not reach the merge base of HEAD and ${ref}. Fetch the history first: \`git fetch --unshallow\`, or fetch-depth: 0 in actions/checkout.`
21
33
  : `HEAD and ${ref} share no history.`));
34
+ const reachMergeBase = (root, ref) => Effect.gen(function* () {
35
+ const found = yield* mergeBase(root, ref);
36
+ if (found !== undefined) {
37
+ return found;
38
+ }
39
+ if (yield* isShallow(root)) {
40
+ yield* unshallow(root, ref);
41
+ const deepened = yield* mergeBase(root, ref);
42
+ if (deepened !== undefined) {
43
+ return deepened;
44
+ }
45
+ }
46
+ return yield* missingMergeBase(root, ref);
47
+ });
22
48
  export const resolveBase = (root, against) => Effect.gen(function* () {
23
49
  const inside = yield* git(root, ["rev-parse", "--is-inside-work-tree"]);
24
50
  if (inside.code !== 0 || inside.stdout.trim() !== "true") {
@@ -28,7 +54,6 @@ export const resolveBase = (root, against) => Effect.gen(function* () {
28
54
  if (!(yield* isCommit(root, ref))) {
29
55
  return yield* fail(`${ref} names no commit in this clone. Fetch it first, or pass another --against <ref>.`);
30
56
  }
31
- const merged = yield* git(root, ["merge-base", "HEAD", ref]);
32
- return merged.code === 0 ? { commit: merged.stdout.trim(), ref } : yield* missingMergeBase(root, ref);
57
+ return { commit: yield* reachMergeBase(root, ref), ref };
33
58
  });
34
59
  //# sourceMappingURL=base.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"base.js","sourceRoot":"","sources":["../../src/git/base.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"base.js","sourceRoot":"","sources":["../../src/git/base.ts"],"names":[],"mappings":"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"}
@@ -7,6 +7,6 @@ export interface GitResult {
7
7
  readonly stderr: string;
8
8
  }
9
9
  export type Git = ChildProcessSpawner.ChildProcessSpawner;
10
- export declare const git: (cwd: string, args: ReadonlyArray<string>) => Effect.Effect<GitResult, SetupFailure, Git>;
10
+ export declare const git: (cwd: string, args: ReadonlyArray<string>, env?: Readonly<Record<string, string>>) => Effect.Effect<GitResult, SetupFailure, Git>;
11
11
  export declare const gitOrFail: (cwd: string, args: ReadonlyArray<string>) => Effect.Effect<string, SetupFailure, Git>;
12
12
  //# sourceMappingURL=command.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../src/git/command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAU,MAAM,QAAQ,CAAC;AACxC,OAAO,EAAgB,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC5E,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C,MAAM,WAAW,SAAS;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,GAAG,GAAG,mBAAmB,CAAC,mBAAmB,CAAC;AAE1D,eAAO,MAAM,GAAG,GAAI,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC,MAAM,CAAC,KAAG,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,YAAY,EAAE,GAAG,CAWH,CAAC;AAEvG,eAAO,MAAM,SAAS,GAAI,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC,MAAM,CAAC,KAAG,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,CAK1G,CAAC"}
1
+ {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../src/git/command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAU,MAAM,QAAQ,CAAC;AACxC,OAAO,EAAgB,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC5E,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C,MAAM,WAAW,SAAS;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,GAAG,GAAG,mBAAmB,CAAC,mBAAmB,CAAC;AAE1D,eAAO,MAAM,GAAG,GAAI,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC,MAAM,CAAC,EAAE,MAAM,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,KAAG,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,YAAY,EAAE,GAAG,CAW3C,CAAC;AAEvG,eAAO,MAAM,SAAS,GAAI,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC,MAAM,CAAC,KAAG,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,CAK1G,CAAC"}
@@ -1,9 +1,9 @@
1
1
  import { Effect, Stream } from "effect";
2
2
  import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
3
3
  import { SetupFailure } from "../failure.js";
4
- export const git = (cwd, args) => Effect.scoped(Effect.gen(function* () {
4
+ export const git = (cwd, args, env) => Effect.scoped(Effect.gen(function* () {
5
5
  const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
6
- const child = yield* spawner.spawn(ChildProcess.make("git", [...args], { cwd, stdin: "ignore" }));
6
+ const child = yield* spawner.spawn(ChildProcess.make("git", [...args], { cwd, env, extendEnv: true, stdin: "ignore" }));
7
7
  const [stdout, stderr, code] = yield* Effect.all([Stream.mkString(Stream.decodeText(child.stdout)), Stream.mkString(Stream.decodeText(child.stderr)), child.exitCode], { concurrency: "unbounded" });
8
8
  return { code: Number(code), stderr, stdout };
9
9
  })).pipe(Effect.mapError((error) => new SetupFailure({ message: `cannot run git: ${error.message}` })));
@@ -1 +1 @@
1
- {"version":3,"file":"command.js","sourceRoot":"","sources":["../../src/git/command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC5E,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAU7C,MAAM,CAAC,MAAM,GAAG,GAAG,CAAC,GAAW,EAAE,IAA2B,EAA+C,EAAE,CAC5G,MAAM,CAAC,MAAM,CACZ,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IACnB,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,mBAAmB,CAAC,mBAAmB,CAAC;IAC/D,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;IAClG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAC/C,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,KAAK,CAAC,QAAQ,CAAC,EACpH,EAAE,WAAW,EAAE,WAAW,EAAE,CAC5B,CAAC;IACF,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AAC/C,CAAC,CAAC,CACF,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,YAAY,CAAC,EAAE,OAAO,EAAE,mBAAmB,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;AAEvG,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,GAAW,EAAE,IAA2B,EAA4C,EAAE,CAC/G,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,CACzC,MAAM,CAAC,IAAI,KAAK,CAAC;IAChB,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC;IAC/B,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,YAAY,CAAC,EAAE,OAAO,EAAE,OAAO,IAAI,CAAC,CAAC,CAAC,YAAY,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,aAAa,MAAM,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,CAC7H,CAAC"}
1
+ {"version":3,"file":"command.js","sourceRoot":"","sources":["../../src/git/command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC5E,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAU7C,MAAM,CAAC,MAAM,GAAG,GAAG,CAAC,GAAW,EAAE,IAA2B,EAAE,GAAsC,EAA+C,EAAE,CACpJ,MAAM,CAAC,MAAM,CACZ,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IACnB,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,mBAAmB,CAAC,mBAAmB,CAAC;IAC/D,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;IACxH,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAC/C,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,KAAK,CAAC,QAAQ,CAAC,EACpH,EAAE,WAAW,EAAE,WAAW,EAAE,CAC5B,CAAC;IACF,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AAC/C,CAAC,CAAC,CACF,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,YAAY,CAAC,EAAE,OAAO,EAAE,mBAAmB,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;AAEvG,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,GAAW,EAAE,IAA2B,EAA4C,EAAE,CAC/G,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,CACzC,MAAM,CAAC,IAAI,KAAK,CAAC;IAChB,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC;IAC/B,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,YAAY,CAAC,EAAE,OAAO,EAAE,OAAO,IAAI,CAAC,CAAC,CAAC,YAAY,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,aAAa,MAAM,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,CAC7H,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shivaedev/quality",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Repository quality gate with typed rules, a shrink-only baseline and a registry of reasoned exceptions",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/git/base.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Effect } from "effect";
1
+ import { Console, Effect } from "effect";
2
2
  import { SetupFailure } from "../failure.ts";
3
3
  import { type Git, git } from "./command.ts";
4
4
 
@@ -30,15 +30,51 @@ const defaultBranch = (root: string): Effect.Effect<string, SetupFailure, Git> =
30
30
  );
31
31
  });
32
32
 
33
+ const isShallow = (root: string): Effect.Effect<boolean, SetupFailure, Git> =>
34
+ Effect.map(git(root, ["rev-parse", "--is-shallow-repository"]), (result) => result.stdout.trim() === "true");
35
+
36
+ const mergeBase = (root: string, ref: string): Effect.Effect<string | undefined, SetupFailure, Git> =>
37
+ Effect.map(git(root, ["merge-base", "HEAD", ref]), (result) => (result.code === 0 ? result.stdout.trim() : undefined));
38
+
39
+ const remoteOf = (root: string, ref: string): Effect.Effect<ReadonlyArray<string>, SetupFailure, Git> =>
40
+ Effect.gen(function* () {
41
+ const name = (yield* git(root, ["rev-parse", "--symbolic-full-name", ref])).stdout.trim();
42
+ const remotes = (yield* git(root, ["remote"])).stdout.split("\n");
43
+ return remotes.filter((remote) => remote !== "" && name.startsWith(`refs/remotes/${remote}/`)).slice(0, 1);
44
+ });
45
+
46
+ const unshallow = (root: string, ref: string): Effect.Effect<void, SetupFailure, Git> =>
47
+ Effect.gen(function* () {
48
+ const remote = yield* remoteOf(root, ref);
49
+ yield* Console.error(`quality: the clone is shallow; fetching its full history to find the merge base of HEAD and ${ref}.`);
50
+ yield* git(root, ["fetch", "--quiet", "--unshallow", ...remote], { GIT_TERMINAL_PROMPT: "0" });
51
+ });
52
+
33
53
  const missingMergeBase = (root: string, ref: string): Effect.Effect<never, SetupFailure, Git> =>
34
- Effect.flatMap(git(root, ["rev-parse", "--is-shallow-repository"]), (shallow) =>
54
+ Effect.flatMap(isShallow(root), (shallow) =>
35
55
  fail(
36
- shallow.stdout.trim() === "true"
56
+ shallow
37
57
  ? `the clone is shallow and does not reach the merge base of HEAD and ${ref}. Fetch the history first: \`git fetch --unshallow\`, or fetch-depth: 0 in actions/checkout.`
38
58
  : `HEAD and ${ref} share no history.`,
39
59
  ),
40
60
  );
41
61
 
62
+ const reachMergeBase = (root: string, ref: string): Effect.Effect<string, SetupFailure, Git> =>
63
+ Effect.gen(function* () {
64
+ const found = yield* mergeBase(root, ref);
65
+ if (found !== undefined) {
66
+ return found;
67
+ }
68
+ if (yield* isShallow(root)) {
69
+ yield* unshallow(root, ref);
70
+ const deepened = yield* mergeBase(root, ref);
71
+ if (deepened !== undefined) {
72
+ return deepened;
73
+ }
74
+ }
75
+ return yield* missingMergeBase(root, ref);
76
+ });
77
+
42
78
  export const resolveBase = (root: string, against: string | undefined): Effect.Effect<Base, SetupFailure, Git> =>
43
79
  Effect.gen(function* () {
44
80
  const inside = yield* git(root, ["rev-parse", "--is-inside-work-tree"]);
@@ -49,6 +85,5 @@ export const resolveBase = (root: string, against: string | undefined): Effect.E
49
85
  if (!(yield* isCommit(root, ref))) {
50
86
  return yield* fail(`${ref} names no commit in this clone. Fetch it first, or pass another --against <ref>.`);
51
87
  }
52
- const merged = yield* git(root, ["merge-base", "HEAD", ref]);
53
- return merged.code === 0 ? { commit: merged.stdout.trim(), ref } : yield* missingMergeBase(root, ref);
88
+ return { commit: yield* reachMergeBase(root, ref), ref };
54
89
  });
@@ -10,11 +10,11 @@ export interface GitResult {
10
10
 
11
11
  export type Git = ChildProcessSpawner.ChildProcessSpawner;
12
12
 
13
- export const git = (cwd: string, args: ReadonlyArray<string>): Effect.Effect<GitResult, SetupFailure, Git> =>
13
+ export const git = (cwd: string, args: ReadonlyArray<string>, env?: Readonly<Record<string, string>>): Effect.Effect<GitResult, SetupFailure, Git> =>
14
14
  Effect.scoped(
15
15
  Effect.gen(function* () {
16
16
  const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
17
- const child = yield* spawner.spawn(ChildProcess.make("git", [...args], { cwd, stdin: "ignore" }));
17
+ const child = yield* spawner.spawn(ChildProcess.make("git", [...args], { cwd, env, extendEnv: true, stdin: "ignore" }));
18
18
  const [stdout, stderr, code] = yield* Effect.all(
19
19
  [Stream.mkString(Stream.decodeText(child.stdout)), Stream.mkString(Stream.decodeText(child.stderr)), child.exitCode],
20
20
  { concurrency: "unbounded" },