Skip to content

Commit f1690c1

Browse files
committed
Merge branch 'vars.CONFIG'
Modify GitGitGadget to accept the project configuration as a repository variable of the `gitgitgadget-workflows` repository fork (in JSON format). Eventually the GitGitGadget's own fork (targeting `gitgitgadget/git`) will be switched to configure its settings in the very same way. Signed-off-by: Johannes Schindelin <[email protected]>
2 parents dc8810b + b49c1c5 commit f1690c1

File tree

14 files changed

+810
-67
lines changed

14 files changed

+810
-67
lines changed

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
"tbdiff",
8484
"Thái",
8585
"Truthy",
86+
"typia",
8687
"unportable",
8788
"vger",
8889
"VSTS",

handle-new-mails/action.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ name: 'Handle new mails'
22
description: 'Processes new mails on the Git mailing list'
33
author: 'Johannes Schindelin'
44
inputs:
5+
config:
6+
description: 'The GitGitGadget configuration to use (see https://github.com/gitgitgadget/gitgitgadget/blob/HEAD/lib/project-config.ts)'
7+
default: '' # sadly, ${{ vars.CONFIG }} does not work, and documentation about what contexts are permissible here is sorely missing
58
pr-repo-token:
69
description: 'The access token to work on the repository that holds PRs and state'
710
required: true

handle-pr-comment/action.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ name: 'Handle PR Comment'
22
description: 'Handles slash commands such as /submit and /preview'
33
author: 'Johannes Schindelin'
44
inputs:
5+
config:
6+
description: 'The GitGitGadget configuration to use (see https://github.com/gitgitgadget/gitgitgadget/blob/HEAD/lib/project-config.ts)'
7+
default: '' # sadly, ${{ vars.CONFIG }} does not work, and documentation about what contexts are permissible here is sorely missing
58
pr-repo-token:
69
description: 'The access token to work on the repository that holds PRs and state'
710
required: true

handle-pr-push/action.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ name: 'Handle PR Pushes'
22
description: 'Handles when a PR was pushed'
33
author: 'Johannes Schindelin'
44
inputs:
5+
config:
6+
description: 'The GitGitGadget configuration to use (see https://github.com/gitgitgadget/gitgitgadget/blob/HEAD/lib/project-config.ts)'
7+
default: '' # sadly, ${{ vars.CONFIG }} does not work, and documentation about what contexts are permissible here is sorely missing
58
pr-repo-token:
69
description: 'The access token to work on the repository that holds PRs and state'
710
required: true

lib/ci-helper.ts

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as core from "@actions/core";
22
import * as fs from "fs";
33
import * as os from "os";
4+
import typia from "typia";
45
import * as util from "util";
56
import { spawnSync } from "child_process";
67
import addressparser from "nodemailer/lib/addressparser/index.js";
@@ -53,8 +54,24 @@ export class CIHelper {
5354
return configFile ? await getExternalConfig(configFile) : getConfig();
5455
}
5556

57+
public static validateConfig = typia.createValidate<IConfig>();
58+
59+
protected static getConfigAsGitHubActionInput(): IConfig | undefined {
60+
if (process.env.GITHUB_ACTIONS !== "true") return undefined;
61+
const json = core.getInput("config");
62+
if (!json) return undefined;
63+
const config = JSON.parse(json) as IConfig | undefined;
64+
const result = CIHelper.validateConfig(config);
65+
if (result.success) return config;
66+
throw new Error(
67+
`Invalid config:\n- ${result.errors
68+
.map((e) => `${e.path} (value: ${e.value}, expected: ${e.expected}): ${e.description}`)
69+
.join("\n- ")}`,
70+
);
71+
}
72+
5673
public constructor(workDir: string = "pr-repo.git", config?: IConfig, skipUpdate?: boolean, gggConfigDir = ".") {
57-
this.config = config !== undefined ? setConfig(config) : getConfig();
74+
this.config = config !== undefined ? setConfig(config) : CIHelper.getConfigAsGitHubActionInput() || getConfig();
5875
this.gggConfigDir = gggConfigDir;
5976
this.workDir = workDir;
6077
this.notes = new GitNotes(workDir);
@@ -101,7 +118,7 @@ export class CIHelper {
101118

102119
// get the access tokens via the inputs of the GitHub Action
103120
this.setAccessToken(this.config.repo.owner, core.getInput("pr-repo-token"));
104-
this.setAccessToken(this.config.repo.baseOwner, core.getInput("upstream-repo-token"));
121+
this.setAccessToken(this.config.repo.upstreamOwner, core.getInput("upstream-repo-token"));
105122
if (this.config.repo.testOwner) {
106123
this.setAccessToken(this.config.repo.testOwner, core.getInput("test-repo-token"));
107124
}
@@ -128,7 +145,7 @@ export class CIHelper {
128145
["remote.origin.url", `https://github.com/${this.config.repo.owner}/${this.config.repo.name}`],
129146
["remote.origin.promisor", "true"],
130147
["remote.origin.partialCloneFilter", "blob:none"],
131-
["remote.upstream.url", `https://github.com/${this.config.repo.baseOwner}/${this.config.repo.name}`],
148+
["remote.upstream.url", `https://github.com/${this.config.repo.upstreamOwner}/${this.config.repo.name}`],
132149
["remote.upstream.promisor", "true"],
133150
["remote.upstream.partialCloneFilter", "blob:none"],
134151
]) {
@@ -175,7 +192,7 @@ export class CIHelper {
175192
console.time("get open PR head commits");
176193
const openPRCommits = (
177194
await Promise.all(
178-
this.config.repo.owners.map(async (repositoryOwner) => {
195+
this.config.app.installedOn.map(async (repositoryOwner) => {
179196
return await this.github.getOpenPRs(repositoryOwner);
180197
}),
181198
)
@@ -256,7 +273,7 @@ export class CIHelper {
256273
const prCommentUrl = core.getInput("pr-comment-url");
257274
const [, owner, repo, prNumber, commentId] =
258275
prCommentUrl.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)#issuecomment-(\d+)$/) || [];
259-
if (!this.config.repo.owners.includes(owner) || repo !== this.config.repo.name) {
276+
if (!this.config.app.installedOn.includes(owner) || repo !== this.config.repo.name) {
260277
throw new Error(`Invalid PR comment URL: ${prCommentUrl}`);
261278
}
262279
return { owner, repo, prNumber: parseInt(prNumber, 10), commentId: parseInt(commentId, 10) };
@@ -266,7 +283,7 @@ export class CIHelper {
266283
const prUrl = core.getInput("pr-url");
267284

268285
const [, owner, repo, prNumber] = prUrl.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)$/) || [];
269-
if (!this.config.repo.owners.includes(owner) || repo !== this.config.repo.name) {
286+
if (!this.config.app.installedOn.includes(owner) || repo !== this.config.repo.name) {
270287
throw new Error(`Invalid PR URL: ${prUrl}`);
271288
}
272289
return { owner, repo, prNumber: parseInt(prNumber, 10) };
@@ -411,7 +428,7 @@ export class CIHelper {
411428
mailMeta.originalCommit,
412429
upstreamCommit,
413430
this.config.repo.owner,
414-
this.config.repo.baseOwner,
431+
this.config.repo.upstreamOwner,
415432
);
416433
}
417434

@@ -654,7 +671,7 @@ export class CIHelper {
654671

655672
// Add comment on GitHub
656673
const comment = `This patch series was integrated into ${branch} via https://github.com/${
657-
this.config.repo.baseOwner
674+
this.config.repo.upstreamOwner
658675
}/${this.config.repo.name}/commit/${mergeCommit}.`;
659676
const url = await this.github.addPRComment(prKey, comment);
660677
console.log(`Added comment ${url.id} about ${branch}: ${url.url}`);
@@ -892,7 +909,7 @@ export class CIHelper {
892909
await addComment(
893910
`Submitted as [${
894911
metadata?.coverLetterMessageId
895-
}](https://${this.config.mailrepo.host}/${this.config.mailrepo.name}/${
912+
}](${this.config.mailrepo.url.replace(/\/+$/, "")}/${
896913
metadata?.coverLetterMessageId
897914
})\n\nTo fetch this version into \`FETCH_HEAD\`:${
898915
code
@@ -1139,7 +1156,7 @@ export class CIHelper {
11391156
const handledPRs = new Set<string>();
11401157
const handledMessageIDs = new Set<string>();
11411158

1142-
for (const repositoryOwner of this.config.repo.owners) {
1159+
for (const repositoryOwner of this.config.app.installedOn) {
11431160
const pullRequests = await this.github.getOpenPRs(repositoryOwner);
11441161

11451162
for (const pr of pullRequests) {
@@ -1197,7 +1214,7 @@ export class CIHelper {
11971214
private async getPRInfo(prKey: pullRequestKey): Promise<IPullRequestInfo> {
11981215
const pr = await this.github.getPRInfo(prKey);
11991216

1200-
if (!this.config.repo.owners.includes(pr.baseOwner) || pr.baseRepo !== this.config.repo.name) {
1217+
if (!this.config.app.installedOn.includes(pr.baseOwner) || pr.baseRepo !== this.config.repo.name) {
12011218
throw new Error(`Unsupported repository: ${pr.pullRequestURL}`);
12021219
}
12031220

lib/gitgitgadget-config.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@ const defaultConfig: IConfig = {
44
repo: {
55
name: "git",
66
owner: "gitgitgadget",
7-
baseOwner: "git",
7+
upstreamOwner: "git",
88
testOwner: "dscho",
9-
owners: ["gitgitgadget", "git", "dscho"],
109
branches: ["maint", "seen"],
1110
closingBranches: ["maint", "master"],
1211
trackingBranches: ["maint", "seen", "master", "next"],
@@ -27,13 +26,16 @@ const defaultConfig: IConfig = {
2726
mail: {
2827
author: "GitGitGadget",
2928
sender: "GitGitGadget",
29+
smtpUser: "[email protected]",
30+
smtpHost: "smtp.gmail.com",
3031
},
3132
app: {
3233
appID: 12836,
3334
installationID: 195971,
3435
name: "gitgitgadget",
3536
displayName: "GitGitGadget",
3637
altname: "gitgitgadget-git",
38+
installedOn: ["gitgitgadget", "git", "dscho"],
3739
},
3840
lint: {
3941
maxCommitsIgnore: ["https://github.com/gitgitgadget/git/pull/923"],
@@ -42,6 +44,18 @@ const defaultConfig: IConfig = {
4244
user: {
4345
allowUserAsLogin: false,
4446
},
47+
syncUpstreamBranches: [
48+
{
49+
sourceRepo: "gitster/git",
50+
targetRepo: "gitgitgadget/git",
51+
sourceRefRegex: "^refs/heads/(maint-\\d|[a-z][a-z]/)",
52+
},
53+
{
54+
sourceRepo: "j6t/git-gui",
55+
targetRepo: "gitgitgadget/git",
56+
targetRefNamespace: "git-gui/",
57+
},
58+
],
4559
};
4660

4761
export default defaultConfig;

lib/project-config.ts

Lines changed: 64 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -8,49 +8,70 @@ export type projectInfo = {
88
urlPrefix: string; // url to 'listserv' of mail (should it be in mailrepo?)
99
};
1010

11+
export interface IRepoConfig {
12+
name: string; // name of the repo
13+
owner: string; // owner of repo holding the notes (tracking data)
14+
upstreamOwner: string; // owner of upstream ("base") repo
15+
testOwner?: string; // owner of the test repo (if any)
16+
branches: string[]; // remote branches to fetch - just use trackingBranches?
17+
closingBranches: string[]; // close if the pr is added to this branch
18+
trackingBranches: string[]; // comment if the pr is added to this branch
19+
maintainerBranch?: string; // branch/owner manually implementing changes
20+
host: string;
21+
}
22+
23+
export interface IMailRepoConfig {
24+
name: string;
25+
owner: string;
26+
branch: string;
27+
host: string;
28+
url: string;
29+
public_inbox_epoch?: number;
30+
mirrorURL?: string;
31+
mirrorRef?: string;
32+
descriptiveName: string;
33+
}
34+
export interface IMailConfig {
35+
author: string;
36+
sender: string;
37+
smtpUser: string;
38+
smtpHost: string;
39+
}
40+
41+
export interface IAppConfig {
42+
appID: number;
43+
installationID: number;
44+
name: string;
45+
displayName: string; // name to use in comments to identify app
46+
installedOn: string[]; // owners of clones being monitored (PR checking)
47+
altname: string | undefined; // is this even needed?
48+
}
49+
50+
export interface ILintConfig {
51+
maxCommitsIgnore?: string[]; // array of pull request urls to skip check
52+
maxCommits: number; // limit on number of commits in a pull request
53+
}
54+
55+
export interface IUserConfig {
56+
allowUserAsLogin: boolean; // use GitHub login as name if name is private
57+
}
58+
59+
export interface ISyncUpstreamBranchesConfig {
60+
sourceRepo: string; // e.g. "gitster/git"
61+
targetRepo: string; // e.g. "gitgitgadget/git"
62+
sourceRefRegex?: string; // e.g. "^refs/heads/(maint-\\d|[a-z][a-z]/)"
63+
targetRefNamespace?: string; // e.g. "git-gui/"
64+
}
65+
1166
export interface IConfig {
12-
repo: {
13-
name: string; // name of the repo
14-
owner: string; // owner of repo holding the notes (tracking data)
15-
baseOwner: string; // owner of upstream ("base") repo
16-
testOwner?: string; // owner of the test repo (if any)
17-
owners: string[]; // owners of clones being monitored (PR checking)
18-
branches: string[]; // remote branches to fetch - just use trackingBranches?
19-
closingBranches: string[]; // close if the pr is added to this branch
20-
trackingBranches: string[]; // comment if the pr is added to this branch
21-
maintainerBranch?: string; // branch/owner manually implementing changes
22-
host: string;
23-
};
24-
mailrepo: {
25-
name: string;
26-
owner: string;
27-
branch: string;
28-
host: string;
29-
url: string;
30-
public_inbox_epoch?: number;
31-
mirrorURL?: string;
32-
mirrorRef?: string;
33-
descriptiveName: string;
34-
};
35-
mail: {
36-
author: string;
37-
sender: string;
38-
};
67+
repo: IRepoConfig;
68+
mailrepo: IMailRepoConfig;
69+
mail: IMailConfig;
3970
project?: projectInfo | undefined; // project-options values
40-
app: {
41-
appID: number;
42-
installationID: number;
43-
name: string;
44-
displayName: string; // name to use in comments to identify app
45-
altname: string | undefined; // is this even needed?
46-
};
47-
lint: {
48-
maxCommitsIgnore?: string[]; // array of pull request urls to skip check
49-
maxCommits: number; // limit on number of commits in a pull request
50-
};
51-
user: {
52-
allowUserAsLogin: boolean; // use GitHub login as name if name is private
53-
};
71+
app: IAppConfig;
72+
lint: ILintConfig;
73+
user: IUserConfig;
74+
syncUpstreamBranches: ISyncUpstreamBranchesConfig[]; // branches to sync from upstream to our repo
5475
}
5576

5677
let config: IConfig; // singleton
@@ -80,8 +101,8 @@ export async function getExternalConfig(file: string): Promise<IConfig> {
80101
throw new Error(`Invalid 'owner' ${newConfig.repo.owner} in ${filePath}`);
81102
}
82103

83-
if (!newConfig.repo.baseOwner.match(/^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}$/i)) {
84-
throw new Error(`Invalid 'baseOwner' ${newConfig.repo.baseOwner} in ${filePath}`);
104+
if (!newConfig.repo.upstreamOwner.match(/^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}$/i)) {
105+
throw new Error(`Invalid 'baseOwner' ${newConfig.repo.upstreamOwner} in ${filePath}`);
85106
}
86107

87108
return newConfig;

0 commit comments

Comments
 (0)