Skip to content

Commit 30956fe

Browse files
authored
Merge pull request #1980 from gitgitgadget/prepare-config-handling
Prepare config handling for the upcoming GitHub Action
2 parents 6b6f802 + a52831f commit 30956fe

File tree

8 files changed

+99
-59
lines changed

8 files changed

+99
-59
lines changed

lib/ci-helper.ts

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@ import { ILintError, LintCommit } from "./commit-lint.js";
55
import { commitExists, git, emptyTreeName } from "./git.js";
66
import { GitNotes } from "./git-notes.js";
77
import { GitGitGadget, IGitGitGadgetOptions } from "./gitgitgadget.js";
8+
import { getConfig } from "./gitgitgadget-config.js";
89
import { GitHubGlue, IGitHubUser, IPRComment, IPRCommit, IPullRequestInfo, RequestError } from "./github-glue.js";
910
import { toPrettyJSON } from "./json-util.js";
1011
import { MailArchiveGitHelper } from "./mail-archive-helper.js";
1112
import { MailCommitMapping } from "./mail-commit-mapping.js";
1213
import { IMailMetadata } from "./mail-metadata.js";
1314
import { IPatchSeriesMetadata } from "./patch-series-metadata.js";
14-
import { IConfig, getConfig } from "./project-config.js";
15+
import { IConfig, getExternalConfig, setConfig } from "./project-config.js";
1516
import { getPullRequestKeyFromURL, pullRequestKey } from "./pullRequestKey.js";
17+
import { ISMTPOptions } from "./send-mail.js";
1618

1719
const readFile = util.promisify(fs.readFile);
1820
type CommentFunction = (comment: string) => Promise<void>;
@@ -25,7 +27,7 @@ type CommentFunction = (comment: string) => Promise<void>;
2527
* commit.
2628
*/
2729
export class CIHelper {
28-
public readonly config: IConfig = getConfig();
30+
public readonly config: IConfig;
2931
public readonly workDir: string;
3032
public readonly notes: GitNotes;
3133
public readonly urlBase: string;
@@ -38,9 +40,15 @@ export class CIHelper {
3840
private gggNotesUpdated: boolean;
3941
private mail2CommitMapUpdated: boolean;
4042
private notesPushToken: string | undefined;
43+
private smtpOptions?: ISMTPOptions;
4144
protected maxCommitsExceptions: string[];
4245

43-
public constructor(workDir: string, skipUpdate?: boolean, gggConfigDir = ".") {
46+
public static async getConfig(configFile?: string): Promise<IConfig> {
47+
return configFile ? await getExternalConfig(configFile) : getConfig();
48+
}
49+
50+
public constructor(workDir: string = "git.git", config?: IConfig, skipUpdate?: boolean, gggConfigDir = ".") {
51+
this.config = config !== undefined ? setConfig(config) : getConfig();
4452
this.gggConfigDir = gggConfigDir;
4553
this.workDir = workDir;
4654
this.notes = new GitNotes(workDir);
@@ -61,6 +69,10 @@ export class CIHelper {
6169
}
6270
}
6371

72+
public setSMTPOptions(smtpOptions: ISMTPOptions): void {
73+
this.smtpOptions = smtpOptions;
74+
}
75+
6476
/*
6577
* Given a commit that was contributed as a patch via GitGitGadget (i.e.
6678
* a commit with a Message-ID recorded in `refs/notes/gitgitgadget`),
@@ -576,7 +588,13 @@ export class CIHelper {
576588
};
577589

578590
try {
579-
const gitGitGadget = await GitGitGadget.get(this.gggConfigDir, this.workDir, this.notesPushToken);
591+
const gitGitGadget = await GitGitGadget.get(
592+
this.gggConfigDir,
593+
this.workDir,
594+
this.urlRepo,
595+
this.notesPushToken,
596+
this.smtpOptions,
597+
);
580598
if (!gitGitGadget.isUserAllowed(comment.author)) {
581599
throw new Error(`User ${comment.author} is not yet permitted to use ${this.config.app.displayName}`);
582600
}
@@ -782,7 +800,13 @@ export class CIHelper {
782800
await this.github.addPRComment(prKey, redacted);
783801
};
784802

785-
const gitGitGadget = await GitGitGadget.get(this.gggConfigDir, this.workDir, this.notesPushToken);
803+
const gitGitGadget = await GitGitGadget.get(
804+
this.gggConfigDir,
805+
this.workDir,
806+
this.urlRepo,
807+
this.notesPushToken,
808+
this.smtpOptions,
809+
);
786810
if (!pr.hasComments && !gitGitGadget.isUserAllowed(pr.author)) {
787811
const welcome = (await readFile("res/WELCOME.md")).toString().replace(/\${username}/g, pr.author);
788812
await this.github.addPRComment(prKey, welcome);

lib/gitgitgadget.ts

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,18 @@ export class GitGitGadget {
3636
return workDir;
3737
}
3838

39-
public static async get(gitGitGadgetDir: string, workDir?: string, notesPushToken?: string): Promise<GitGitGadget> {
39+
public static async get(
40+
gitGitGadgetDir: string,
41+
workDir?: string,
42+
publishTagsAndNotesToRemote?: string,
43+
notesPushToken?: string,
44+
smtpOptions?: ISMTPOptions,
45+
): Promise<GitGitGadget> {
4046
if (!workDir) {
4147
workDir = await this.getWorkDir(gitGitGadgetDir);
4248
}
4349

44-
const publishTagsAndNotesToRemote = await getVar("publishRemote", gitGitGadgetDir);
50+
if (!publishTagsAndNotesToRemote) publishTagsAndNotesToRemote = await getVar("publishRemote", gitGitGadgetDir);
4551
if (!publishTagsAndNotesToRemote) {
4652
throw new Error("No remote to which to push configured");
4753
}
@@ -59,25 +65,26 @@ export class GitGitGadget {
5965

6066
const notes = new GitNotes(workDir);
6167

62-
const smtpUser = await getVar("smtpUser", gitGitGadgetDir);
63-
const smtpHost = await getVar("smtpHost", gitGitGadgetDir);
64-
const smtpPass = await getVar("smtpPass", gitGitGadgetDir);
65-
const smtpOpts = await getVar("smtpOpts", gitGitGadgetDir);
66-
67-
if (!smtpUser || !smtpHost || !smtpPass) {
68-
throw new Error("No SMTP settings configured");
68+
if (!smtpOptions) {
69+
const smtpUser = await getVar("smtpUser", gitGitGadgetDir);
70+
const smtpHost = await getVar("smtpHost", gitGitGadgetDir);
71+
const smtpPass = await getVar("smtpPass", gitGitGadgetDir);
72+
const smtpOpts = await getVar("smtpOpts", gitGitGadgetDir);
73+
74+
if (smtpUser && smtpHost && smtpPass) smtpOptions = { smtpHost, smtpOpts, smtpPass, smtpUser };
75+
else if (smtpUser || smtpHost || smtpPass) {
76+
const missing: string[] = [
77+
smtpUser ? "" : "smtpUser",
78+
smtpHost ? "" : "smtpHost",
79+
smtpPass ? "" : "smtpPass",
80+
].filter((e) => e);
81+
throw new Error(`Partial SMTP configuration detected (${missing.join(", ")} missing)`);
82+
}
6983
}
7084

7185
const [options, allowedUsers] = await GitGitGadget.readOptions(notes);
7286

73-
return new GitGitGadget(
74-
notes,
75-
options,
76-
allowedUsers,
77-
{ smtpHost, smtpOpts, smtpPass, smtpUser },
78-
publishTagsAndNotesToRemote,
79-
notesPushToken,
80-
);
87+
return new GitGitGadget(notes, options, allowedUsers, smtpOptions, publishTagsAndNotesToRemote, notesPushToken);
8188
}
8289

8390
protected static async readOptions(notes: GitNotes): Promise<[IGitGitGadgetOptions, Set<string>]> {
@@ -96,7 +103,7 @@ export class GitGitGadget {
96103
protected options: IGitGitGadgetOptions;
97104
protected allowedUsers: Set<string>;
98105

99-
protected readonly smtpOptions: ISMTPOptions;
106+
protected readonly smtpOptions?: ISMTPOptions;
100107

101108
protected readonly publishTagsAndNotesToRemote: string;
102109
private readonly publishToken: string | undefined;
@@ -105,7 +112,7 @@ export class GitGitGadget {
105112
notes: GitNotes,
106113
options: IGitGitGadgetOptions,
107114
allowedUsers: Set<string>,
108-
smtpOptions: ISMTPOptions,
115+
smtpOptions: ISMTPOptions | undefined,
109116
publishTagsAndNotesToRemote: string,
110117
publishToken?: string,
111118
) {
@@ -166,6 +173,10 @@ export class GitGitGadget {
166173

167174
// Send emails only to the user
168175
public async preview(pr: IPullRequestInfo, userInfo: IGitHubUser): Promise<IPatchSeriesMetadata | undefined> {
176+
const smtpOptions = this.smtpOptions;
177+
if (!smtpOptions) {
178+
throw new Error("No SMTP options configured");
179+
}
169180
if (!userInfo.email) {
170181
throw new Error(`No email in user info for ${userInfo.login}`);
171182
}
@@ -176,16 +187,20 @@ export class GitGitGadget {
176187
mbox.cc = [];
177188
mbox.to = email;
178189
console.log(mbox);
179-
return await sendMail(mbox, this.smtpOptions);
190+
return await sendMail(mbox, smtpOptions);
180191
};
181192

182193
return await this.genAndSend(pr, userInfo, { noUpdate: true }, send);
183194
}
184195

185196
// Send emails out for review
186197
public async submit(pr: IPullRequestInfo, userInfo: IGitHubUser): Promise<IPatchSeriesMetadata | undefined> {
198+
const smtpOptions = this.smtpOptions;
199+
if (!smtpOptions) {
200+
throw new Error("No SMTP options configured");
201+
}
187202
const send = async (mail: string): Promise<string> => {
188-
return await parseHeadersAndSendMail(mail, this.smtpOptions);
203+
return await parseHeadersAndSendMail(mail, smtpOptions);
189204
};
190205

191206
return await this.genAndSend(pr, userInfo, {}, send);

lib/misc-action.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export async function handleAction(parms: actionInterface): Promise<void> {
3535
throw new Error(`git WorkDir '${parms.repositoryDir}' not found.`);
3636
}
3737

38-
const ci = new CIHelper(parms.repositoryDir, parms.skipUpdate ? true : false, parms.configRepositoryDir);
38+
const ci = new CIHelper(parms.repositoryDir, config, parms.skipUpdate ? true : false, parms.configRepositoryDir);
3939

4040
if (parms.action === "update-open-prs") {
4141
const result = await ci.updateOpenPrs();

lib/project-config.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,25 @@ export function getConfig(): IConfig {
6464
return config;
6565
}
6666

67+
export async function getExternalConfig(file: string): Promise<IConfig> {
68+
const filePath = path.resolve(file);
69+
const newConfig = await loadConfig(filePath);
70+
71+
if (!Object.prototype.hasOwnProperty.call(newConfig, "project")) {
72+
throw new Error(`User configurations must have a 'project:'. Not found in ${filePath}`);
73+
}
74+
75+
if (!newConfig.repo.owner.match(/^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}$/i)) {
76+
throw new Error(`Invalid 'owner' ${newConfig.repo.owner} in ${filePath}`);
77+
}
78+
79+
if (!newConfig.repo.baseOwner.match(/^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}$/i)) {
80+
throw new Error(`Invalid 'baseOwner' ${newConfig.repo.baseOwner} in ${filePath}`);
81+
}
82+
83+
return newConfig;
84+
}
85+
6786
type importedConfig = { default: IConfig };
6887

6988
/**

lib/pull-action.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export async function handlePRUpdate(parms: PRUpdateInterface): Promise<void> {
3838
throw new Error(`git WorkDir '${parms.repositoryDir}' not found.`);
3939
}
4040

41-
const ci = new CIHelper(parms.repositoryDir, parms.skipUpdate ? true : false, parms.configRepositoryDir);
41+
const ci = new CIHelper(parms.repositoryDir, config, parms.skipUpdate ? true : false, parms.configRepositoryDir);
4242

4343
if (parms.action === "comment") {
4444
if (parms.commentId) {

script/misc-helper.ts

Lines changed: 8 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,11 @@ import { CIHelper } from "../lib/ci-helper.js";
55
import { isDirectory } from "../lib/fs-util.js";
66
import { git, gitConfig } from "../lib/git.js";
77
import { IGitGitGadgetOptions, getVar } from "../lib/gitgitgadget.js";
8-
import { getConfig } from "../lib/gitgitgadget-config.js";
98
import { GitHubGlue } from "../lib/github-glue.js";
109
import { toPrettyJSON } from "../lib/json-util.js";
1110
import { IGitMailingListMirrorState, stateKey } from "../lib/mail-archive-helper.js";
1211
import { IPatchSeriesMetadata } from "../lib/patch-series-metadata.js";
13-
import { IConfig, loadConfig, setConfig } from "../lib/project-config.js";
14-
import path from "path";
12+
import { IConfig } from "../lib/project-config.js";
1513

1614
let commander = new Command();
1715
const publishRemoteKey = "publishRemote";
@@ -47,9 +45,7 @@ interface ICommanderOptions {
4745
const commandOptions = commander.opts<ICommanderOptions>();
4846

4947
(async (): Promise<void> => {
50-
const config: IConfig = commandOptions.config
51-
? setConfig(await getExternalConfig(commandOptions.config))
52-
: getConfig();
48+
const config: IConfig = await CIHelper.getConfig(commandOptions.config);
5349

5450
const getGitGitWorkDir = async (): Promise<string> => {
5551
if (!commandOptions.gitWorkDir) {
@@ -70,7 +66,12 @@ const commandOptions = commander.opts<ICommanderOptions>();
7066
return commandOptions.gitWorkDir;
7167
};
7268

73-
const ci = new CIHelper(await getGitGitWorkDir(), commandOptions.skipUpdate, commandOptions.gitgitgadgetWorkDir);
69+
const ci = new CIHelper(
70+
await getGitGitWorkDir(),
71+
config,
72+
commandOptions.skipUpdate,
73+
commandOptions.gitgitgadgetWorkDir,
74+
);
7475

7576
const configureNotesPushToken = async (): Promise<void> => {
7677
const token = await gitConfig("gitgitgadget.githubToken");
@@ -488,22 +489,3 @@ const commandOptions = commander.opts<ICommanderOptions>();
488489
process.stderr.write(`Caught error ${reason}:\n${reason.stack}\n`);
489490
process.exit(1);
490491
});
491-
492-
async function getExternalConfig(file: string): Promise<IConfig> {
493-
const filePath = path.resolve(file);
494-
const newConfig = await loadConfig(filePath);
495-
496-
if (!Object.prototype.hasOwnProperty.call(newConfig, "project")) {
497-
throw new Error(`User configurations must have a 'project:'. Not found in ${filePath}`);
498-
}
499-
500-
if (!newConfig.repo.owner.match(/^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}$/i)) {
501-
throw new Error(`Invalid 'owner' ${newConfig.repo.owner} in ${filePath}`);
502-
}
503-
504-
if (!newConfig.repo.baseOwner.match(/^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}$/i)) {
505-
throw new Error(`Invalid 'baseOwner' ${newConfig.repo.baseOwner} in ${filePath}`);
506-
}
507-
508-
return newConfig;
509-
}

tests-config/ci-helper.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ class TestCIHelper extends CIHelper {
9494
public addPRLabelsCalls: Array<[_: string, labels: string[]]>;
9595

9696
public constructor(workDir: string, debug = false, gggDir = ".") {
97-
super(workDir, debug, gggDir);
97+
super(workDir, config, debug, gggDir);
9898
this.testing = true;
9999
this.ghGlue = this.github;
100100

@@ -240,7 +240,7 @@ test("identify merge that integrated some commit", async () => {
240240
const commitD = await repo.merge("d", commitF);
241241
await repo.git(["update-ref", `refs/remotes/upstream/${config.repo.trackingBranches[2]}`, commitD]);
242242

243-
const ci = new CIHelper(repo.workDir, true);
243+
const ci = new CIHelper(repo.workDir, config, true);
244244
expect(commitB).not.toBeUndefined();
245245
expect(await ci.identifyMergeCommit(config.repo.trackingBranches[2], commitG)).toEqual(commitD);
246246
expect(await ci.identifyMergeCommit(config.repo.trackingBranches[2], commitE)).toEqual(commitC);

tests/ci-helper.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ function testQ(label: string, fn: AsyncFn) {
3232
});
3333
}
3434

35-
getConfig();
35+
const config = getConfig();
3636

3737
const eMailOptions = {
3838
smtpserver: new testSmtpServer(),
@@ -61,7 +61,7 @@ class TestCIHelper extends CIHelper {
6161
public addPRLabelsCalls: Array<[_: string, labels: string[]]>;
6262

6363
public constructor(workDir: string, debug = false, gggDir = ".") {
64-
super(workDir, debug, gggDir);
64+
super(workDir, config, debug, gggDir);
6565
this.testing = true;
6666
this.ghGlue = this.github;
6767

@@ -203,7 +203,7 @@ testQ("identify merge that integrated some commit", async () => {
203203
const commitD = await repo.merge("d", commitF);
204204
await repo.git(["update-ref", "refs/remotes/upstream/seen", commitD]);
205205

206-
const ci = new CIHelper(repo.workDir, true);
206+
const ci = new CIHelper(repo.workDir, config, true);
207207
expect(commitB).not.toBeUndefined();
208208
expect(await ci.identifyMergeCommit("seen", commitG)).toEqual(commitD);
209209
expect(await ci.identifyMergeCommit("seen", commitE)).toEqual(commitC);

0 commit comments

Comments
 (0)