Skip to content

Commit 7d538e5

Browse files
committed
CIHelper: add a convenient function to initialize e.g. the Git worktree
In the upcoming GitHub Actions, we must assume that there is no persistent Git worktree to use, unlike the situation of GitGitGadget's Azure Pipelines, which use a single Git worktree that is retained and reused across Pipeline runs. To make this somewhat efficient, let's try to use a partial (semi-shallow) clone. Let's play nice and reuse an already-existing Git worktree (if any), to make local debugging easier. The strategy chosen here is to initialize a partial, blob-less clone, then initialize the `gitgitgadget` Git notes by shallowly fetching the ref with depth 1 (i.e. the tip commit) including all blobs (using `--filter=blob:limit=1g` instead of `--no-filter` to avoid running into bugs introduced in Git v2.48.0-rc0~58^2 (index-pack: repack local links into promisor packs, 2024-11-01) where it falls over its own feet when promisor and non-promisor packs are mixed), and then unshallows the ref with a tree-less fetch. There are a couple of other things we want to set up in the upcoming GitHub Actions, e.g. handling the installation access tokens specified as GitHub Action inputs; Let's combine all of that setup within a single method of the `CIHelper` class. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
1 parent 5183e81 commit 7d538e5

File tree

3 files changed

+68
-1
lines changed

3 files changed

+68
-1
lines changed

lib/ci-helper.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
import * as core from "@actions/core";
12
import * as fs from "fs";
3+
import * as os from "os";
24
import * as util from "util";
35
import addressparser from "nodemailer/lib/addressparser/index.js";
6+
import path from "path";
47
import { ILintError, LintCommit } from "./commit-lint.js";
58
import { commitExists, git, emptyTreeName } from "./git.js";
69
import { GitNotes } from "./git-notes.js";
@@ -62,6 +65,68 @@ export class CIHelper {
6265
this.urlRepo = `${this.urlBase}${this.config.repo.name}/`;
6366
}
6467

68+
public async setupGitHubAction(): Promise<void> {
69+
// help dugite realize where `git` is...
70+
const gitExecutable = os.type() === "Windows_NT" ? "git.exe" : "git";
71+
const stripSuffix = `bin${path.sep}${gitExecutable}`;
72+
for (const gitPath of (process.env.PATH || "/")
73+
.split(path.delimiter)
74+
.map((p) => path.normalize(`${p}${path.sep}${gitExecutable}`))
75+
// eslint-disable-next-line security/detect-non-literal-fs-filename
76+
.filter((p) => p.endsWith(`${path.sep}${stripSuffix}`) && fs.existsSync(p))) {
77+
process.env.LOCAL_GIT_DIRECTORY = gitPath.substring(0, gitPath.length - stripSuffix.length);
78+
// need to override GIT_EXEC_PATH, so that Dugite can find the `git-remote-https` executable,
79+
// see https://github.com/desktop/dugite/blob/v2.7.1/lib/git-environment.ts#L44-L64
80+
// Also: We cannot use `await git(["--exec-path"]);` because that would use Dugite, which would
81+
// override `GIT_EXEC_PATH` and then `git --exec-path` would report _that_...
82+
process.env.GIT_EXEC_PATH = spawnSync("/usr/bin/git", ["--exec-path"]).stdout.toString("utf-8").trimEnd();
83+
break;
84+
}
85+
86+
// get the access tokens via the inputs of the GitHub Action
87+
this.setAccessToken(this.config.repo.owner, core.getInput("pr-repo-token"));
88+
this.setAccessToken(this.config.repo.baseOwner, core.getInput("upstream-repo-token"));
89+
if (this.config.repo.testOwner) {
90+
this.setAccessToken(this.config.repo.testOwner, core.getInput("test-repo-token"));
91+
}
92+
93+
// eslint-disable-next-line security/detect-non-literal-fs-filename
94+
if (!fs.existsSync(this.workDir)) await git(["init", "--bare", "--initial-branch", "unused", this.workDir]);
95+
for (const [key, value] of [
96+
["gc.auto", "0"],
97+
["remote.origin.url", `https://github.com/${this.config.repo.owner}/${this.config.repo.name}`],
98+
["remote.origin.promisor", "true"],
99+
["remote.origin.partialCloneFilter", "blob:none"],
100+
]) {
101+
await git(["config", key, value], { workDir: this.workDir });
102+
}
103+
console.time("fetch Git notes");
104+
await git(
105+
[
106+
"fetch",
107+
"--filter=blob:limit=1g", // let's fetch the notes with all of their blobs
108+
"--no-tags",
109+
"origin",
110+
"--depth=1",
111+
`+${GitNotes.defaultNotesRef}:${GitNotes.defaultNotesRef}`,
112+
],
113+
{
114+
workDir: this.workDir,
115+
},
116+
);
117+
console.timeEnd("fetch Git notes");
118+
this.gggNotesUpdated = true;
119+
// "Unshallow" the refs by fetching the shallow commits with a tree-less filter.
120+
// This is needed because Git will otherwise fall over left and right when trying
121+
// to determine merge bases with really old branches.
122+
const unshallow = async (workDir: string) => {
123+
console.time(`Making ${workDir} non-shallow`);
124+
console.log(await git(["fetch", "--filter=tree:0", "origin", "--unshallow"], { workDir }));
125+
console.timeEnd(`Making ${workDir} non-shallow`);
126+
};
127+
await unshallow(this.workDir);
128+
}
129+
65130
public setAccessToken(repositoryOwner: string, token: string): void {
66131
this.github.setAccessToken(repositoryOwner, token);
67132
if (this.config.repo.owner === repositoryOwner) {

lib/gitgitgadget-config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const defaultConfig: IConfig = {
55
name: "git",
66
owner: "gitgitgadget",
77
baseOwner: "git",
8+
testOwner: "dscho",
89
owners: ["gitgitgadget", "git", "dscho"],
910
branches: ["maint", "seen"],
1011
closingBranches: ["maint", "master"],

lib/project-config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ export interface IConfig {
1212
repo: {
1313
name: string; // name of the repo
1414
owner: string; // owner of repo holding the notes (tracking data)
15-
baseOwner: string; // owner of base repo
15+
baseOwner: string; // owner of upstream ("base") repo
16+
testOwner?: string; // owner of the test repo (if any)
1617
owners: string[]; // owners of clones being monitored (PR checking)
1718
branches: string[]; // remote branches to fetch - just use trackingBranches?
1819
closingBranches: string[]; // close if the pr is added to this branch

0 commit comments

Comments
 (0)