Skip to content

Commit 9340912

Browse files
committed
CIHelper: offer to update the mail-to-commit and commit-to-mail notes
This ports the Azure Pipeline "Update GitGitGadget's commit to mail notes" (https://dev.azure.com/gitgitgadget/git/_build?definitionId=9) logic to a method of the `CIHelper` class that will be used in a GitHub Action so that the Azure Pipeline can be retired. This was the only Azure Pipeline of GitGitGadget which did not, actually, use the `misc-helper.ts` script, but instead was opaquely implemented as a lengthy shell scriptlet inside the Pipeline definition that called two of the shell scripts in the `gitgitgadget/gitgitgadget` repository: `lookup-commit.sh` and `update-mail-to-commit-notes.sh`. Since the scripts that are called by the new method expect a persisted, non-partial clone of the Git mailing list, we need to play a couple of games here to make it work in a GitHub workflow (that runs on ephemeral runners where the repository has to be cloned afresh in every run). Also: These shell scripts, by virtue of needing to be interpreted by a shell interpreter outside of node.js, need to be copied into the `dist/script/` subdirectory before publishing the GitHub Action, so that they can be found and interpreted as expected. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
1 parent 40f0d48 commit 9340912

File tree

2 files changed

+64
-3
lines changed

2 files changed

+64
-3
lines changed

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@
9696
"CIsmtphost",
9797
"CIsmtppass",
9898
"CIsmtpopts",
99+
"GITGIT_(|DIR|GIT_REMOTE|MAIL_REMOTE|MAIL_EPOCH)",
99100
"mailrepo",
100101
"parsePRURLInput",
101102
"users\\.noreply\\.github\\.com",

lib/ci-helper.ts

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ import * as core from "@actions/core";
22
import * as fs from "fs";
33
import * as os from "os";
44
import * as util from "util";
5+
import { spawnSync } from "child_process";
56
import addressparser from "nodemailer/lib/addressparser/index.js";
67
import path from "path";
78
import { ILintError, LintCommit } from "./commit-lint.js";
8-
import { commitExists, git, emptyTreeName } from "./git.js";
9+
import { commitExists, git, emptyTreeName, revParse } from "./git.js";
910
import { GitNotes } from "./git-notes.js";
1011
import { GitGitGadget, IGitGitGadgetOptions } from "./gitgitgadget.js";
1112
import { getConfig } from "./gitgitgadget-config.js";
@@ -70,6 +71,7 @@ export class CIHelper {
7071
public async setupGitHubAction(setupOptions?: {
7172
needsMailingListMirror?: boolean;
7273
needsUpstreamBranches?: boolean;
74+
needsMailToCommitNotes?: boolean;
7375
}): Promise<void> {
7476
// help dugite realize where `git` is...
7577
const gitExecutable = os.type() === "Windows_NT" ? "git.exe" : "git";
@@ -133,14 +135,18 @@ export class CIHelper {
133135
await git(["config", key, value], { workDir: this.workDir });
134136
}
135137
console.time("fetch Git notes");
138+
const notesRefs = [GitNotes.defaultNotesRef];
139+
if (setupOptions?.needsMailToCommitNotes) {
140+
notesRefs.push("refs/notes/mail-to-commit", "refs/notes/commit-to-mail");
141+
}
136142
await git(
137143
[
138144
"fetch",
139145
"--filter=blob:limit=1g", // let's fetch the notes with all of their blobs
140146
"--no-tags",
141147
"origin",
142148
"--depth=1",
143-
`+${GitNotes.defaultNotesRef}:${GitNotes.defaultNotesRef}`,
149+
...notesRefs.map((ref) => `+${ref}:${ref}`),
144150
],
145151
{
146152
workDir: this.workDir,
@@ -217,7 +223,7 @@ export class CIHelper {
217223
"remote.mirror.promisor=false", // let's fetch the mails with all of their contents
218224
"fetch",
219225
"mirror",
220-
"--depth=50",
226+
`--depth=${setupOptions?.needsMailToCommitNotes ? 5000 : 50}`,
221227
"+REF:REF".replace("REF", this.config.mailrepo.mirrorRef || `refs/heads/lore-${epoch}`),
222228
],
223229
{
@@ -317,6 +323,60 @@ export class CIHelper {
317323
await this.commit2mailNotes.appendCommitNote(gitGitCommit, messageId);
318324
}
319325

326+
/**
327+
* Update the `commit-to-mail` and `mail-to-commit` Git notes refs.
328+
*/
329+
public async updateMailToCommitNotes(): Promise<void> {
330+
// We'll assume that the `commit-to-mail` and `mail-to-commit` notes refs are up to date
331+
const commit2MailTipCommit = await revParse("refs/notes/commit-to-mail", this.workDir);
332+
const dir = fileURLToPath(new URL(".", import.meta.url));
333+
const lookupCommitScriptPath = path.resolve(dir, "..", "script", "lookup-commit.sh");
334+
console.time("lookup-commit.sh");
335+
const lookupCommitResult = spawnSync("sh", ["-x", lookupCommitScriptPath, "--notes", "update"], {
336+
stdio: "inherit",
337+
env: {
338+
...process.env,
339+
GITGIT_DIR: this.workDir,
340+
GITGIT_GIT_REMOTE: this.urlRepo,
341+
LORE_GIT_DIR: this.mailingListMirror,
342+
GITGIT_MAIL_REMOTE: this.config.mailrepo.url,
343+
GITGIT_MAIL_EPOCH: "1",
344+
},
345+
});
346+
console.timeEnd("lookup-commit.sh");
347+
if (lookupCommitResult.status !== 0) throw new Error("lookup-commit.sh failed");
348+
// If there were no updates, we are done
349+
if (commit2MailTipCommit === (await revParse("refs/notes/commit-to-mail", this.workDir))) return;
350+
351+
const updateMailToCommitNotesScriptPath = path.resolve(dir, "..", "script", "update-mail-to-commit-notes.sh");
352+
console.time("update-mail-to-commit-notes.sh");
353+
const updateMailToCommitNotesResult = spawnSync("sh", ["-x", updateMailToCommitNotesScriptPath], {
354+
stdio: "inherit",
355+
env: {
356+
...process.env,
357+
GITGIT_DIR: this.workDir,
358+
GITGIT_GIT_REMOTE: this.urlRepo,
359+
},
360+
});
361+
console.timeEnd("update-mail-to-commit-notes.sh");
362+
if (updateMailToCommitNotesResult.status !== 0) throw new Error("update-mail-to-commit-notes.sh failed");
363+
364+
const mail2commitNotes = new GitNotes(this.workDir, "refs/notes/mail-to-commit");
365+
await mail2commitNotes.push(this.urlRepo, this.notesPushToken);
366+
const commit2MailNotes = new GitNotes(this.workDir, "refs/notes/commit-to-mail");
367+
await commit2MailNotes.push(this.urlRepo, this.notesPushToken);
368+
369+
const commit2MailTipPatch = await git(["show", "refs/notes/commit-to-mail"], { workDir: this.workDir });
370+
// Any unhandled commit will get annotated with "no match"
371+
// To list all of them, the tip commit's diff is parsed and the commit hash is
372+
// extracted from the "filename" on the `+++ b/` line.
373+
const noMatch = commit2MailTipPatch
374+
.split("\ndiff --git ")
375+
.filter((d) => d.endsWith("+no match"))
376+
.map((d) => d.split("\n+++ b/")[1].split("\n")[0].replace("/", ""));
377+
if (noMatch.length) throw new Error(`Could not find mail(s) for: ${noMatch.join("\n")}`);
378+
}
379+
320380
/**
321381
* Given a Message-Id, identify the upstream commit (if any), and if there
322382
* is one, and if it was not yet recorded in GitGitGadget's metadata, record

0 commit comments

Comments
 (0)