Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 33 additions & 7 deletions 40 azure-function/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// @ts-check
/*
* This is the Azure Function backing the GitGitGadget GitHub App.
*
Expand Down Expand Up @@ -90,12 +91,30 @@ module.exports = async (context, req) => {
}

try {
/*
* The Azure Pipeline needs to be installed as a PR build on _the very
* same_ repository that triggers this function. That is, when the
* Azure Function triggers GitGitGadget for gitgitgadget/git, it needs
* to know that pipelineId 3 is installed on gitgitgadget/git, and
* trigger that very pipeline.
*
* So whenever we extend GitGitGadget to handle another repository, we
* will have to add an Azure Pipeline, install it on that repository as
* a PR build, and add the information here.
*/
const pipelines = {
'dscho': 12,
'git': 13,
'gitgitgadget': 3,
};

const eventType = context.req.headers['x-github-event'];
context.log(`Got eventType: ${eventType}`);
if (req.body.repository.owner.login !== 'gitgitgadget') {
const repositoryOwner = req.body.repository.owner.login;
if (pipelines[repositoryOwner] === undefined) {
context.res = {
status: 403,
body: 'Refusing to work on a repository other than gitgitgadget/git'
body: 'Refusing to work on a repository other than gitgitgadget/git or git/git'
};
} else if ((new Set(['check_run', 'status']).has(eventType))) {
context.res = {
Expand All @@ -108,13 +127,17 @@ module.exports = async (context, req) => {
}

const comment = req.body.comment;
const pullRequestURL = req.body.issue.pull_request.html_url;
const prNumber = pullRequestURL.match(/https:\/\/github\.com\/gitgitgadget\/git\/pull\/([0-9]*)$/)
if (!comment.id || !prNumber) {
const prNumber = req.body.issue.number;
if (!comment || !comment.id || !prNumber) {
context.log(`Invalid payload:\n${JSON.stringify(req.body, null, 4)}`);
throw new Error('Invalid payload');
}

/* GitGitGadget works on dscho/git only for testing */
if (repositoryOwner === 'dscho' && comment.user.login !== 'dscho') {
throw new Error(`Ignoring comment from ${comment.user.login}`);
}

/* Only trigger the Pipeline for valid commands */
if (!comment.body || !comment.body.match(/^\/(submit|preview|allow|disallow|test)\b/)) {
context.res = {
Expand All @@ -124,12 +147,15 @@ module.exports = async (context, req) => {
return;
}

const sourceBranch = `refs/pull/${prNumber[1]}/head`;
const sourceBranch = `refs/pull/${prNumber}/head`;
const parameters = {
'pr.comment.id': comment.id,
dscho marked this conversation as resolved.
Show resolved Hide resolved
};
const pipelineId = pipelines[repositoryOwner];
if (!pipelineId || pipelineId < 1)
throw new Error(`No pipeline set up for org ${repositoryOwner}`);
context.log(`Queuing with branch ${sourceBranch} and parameters ${JSON.stringify(parameters)}`);
await triggerAzurePipeline(triggerToken, 'gitgitgadget', 'git', 3, sourceBranch, parameters);
await triggerAzurePipeline(triggerToken, 'gitgitgadget', 'git', pipelineId, sourceBranch, parameters);

context.res = {
// status: 200, /* Defaults to 200 */
Expand Down
29 changes: 18 additions & 11 deletions 29 lib/ci-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,11 @@ export class CIHelper {
}
await this.notes.set(messageID, mailMeta, true);

if (!this.testing && mailMeta.pullRequestURL) {
if (!this.testing && mailMeta.pullRequestURL &&
mailMeta.pullRequestURL
.startsWith("https://github.com/gitgitgadget/") ) {
await this.github.annotateCommit(mailMeta.originalCommit,
upstreamCommit);
upstreamCommit, "gitgitgadget");
}

return true;
Expand Down Expand Up @@ -485,8 +487,10 @@ export class CIHelper {
*
* @param commentID the ID of the PR comment to handle
*/
public async handleComment(commentID: number): Promise<void> {
const comment = await this.github.getPRComment(commentID);
public async handleComment(repositoryOwner: string, commentID: number):
Promise<void> {
const comment =
await this.github.getPRComment(repositoryOwner, commentID);
const match = comment.body.match(/^\s*(\/[-a-z]+)(\s+(.*?))?\s*$/);
if (!match) {
console.log(`Not a command; doing nothing: '${comment.body}'`);
Expand All @@ -495,8 +499,8 @@ export class CIHelper {
const command = match[1];
const argument = match[3];

const pullRequestURL =
`https://github.com/gitgitgadget/git/pull/${comment.prNumber}`;
const pullRequestURL = `https://github.com/${
repositoryOwner}/git/pull/${comment.prNumber}`;
console.log(`Handling command ${command} with argument ${argument} at ${
pullRequestURL}#issuecomment-${commentID}`);

Expand All @@ -513,7 +517,8 @@ export class CIHelper {
}

const getPRAuthor = async (): Promise<string> => {
const pr = await this.github.getPRInfo(comment.prNumber);
const pr = await this.github.getPRInfo(repositoryOwner,
comment.prNumber);
return pr.author;
};

Expand Down Expand Up @@ -603,12 +608,12 @@ export class CIHelper {
}
}

public async handlePush(prNumber: number) {
const pr = await this.github.getPRInfo(prNumber);
public async handlePush(repositoryOwner: string, prNumber: number) {
const pr = await this.github.getPRInfo(repositoryOwner, prNumber);
const gitGitGadget = await GitGitGadget.get(".");
if (!pr.hasComments && !gitGitGadget.isUserAllowed(pr.author)) {
const pullRequestURL =
`https://github.com/gitgitgadget/git/pull/${prNumber}`;
`https://github.com/${repositoryOwner}/git/pull/${prNumber}`;
const welcome = (await readFile("res/WELCOME.md")).toString()
.replace(/\${username}/g, pr.author);
this.github.addPRComment(pullRequestURL, welcome);
Expand All @@ -632,7 +637,9 @@ export class CIHelper {

private async getPRInfo(prNumber: number, pullRequestURL: string):
Promise<IPullRequestInfo> {
const pr = await this.github.getPRInfo(prNumber);
const [owner] =
GitGitGadget.parsePullRequestURL(pullRequestURL);
const pr = await this.github.getPRInfo(owner, prNumber);

if (!pr.baseLabel || !pr.baseCommit ||
!pr.headLabel || !pr.headCommit) {
Expand Down
21 changes: 16 additions & 5 deletions 21 lib/gitgitgadget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,8 @@ export class GitGitGadget {
return await this.genAndSend(pr, userInfo, {}, send);
}

protected async updateNotesAndPullRef(pullRequestNumber: number,
protected async updateNotesAndPullRef(repositoryOwner: string,
pullRequestNumber: number,
additionalRef?: string):
Promise<string> {
const pullRequestRef = `refs/pull/${pullRequestNumber}/head`;
Expand All @@ -205,16 +206,25 @@ export class GitGitGadget {
this.publishTagsAndNotesToRemote,
"--",
`+${this.notes.notesRef}:${this.notes.notesRef}`,
`+${pullRequestRef}:${pullRequestRef}`,
`+${pullRequestMerge}:${pullRequestMerge}`,
`+refs/heads/maint:refs/remotes/upstream/maint`,
`+refs/heads/master:refs/remotes/upstream/master`,
`+refs/heads/next:refs/remotes/upstream/next`,
`+refs/heads/pu:refs/remotes/upstream/pu`,
];
const prArgs = [
`+${pullRequestRef}:${pullRequestRef}`,
`+${pullRequestMerge}:${pullRequestMerge}`,
];
if (additionalRef) {
args.push(`+${additionalRef}:${additionalRef}`);
}
if (repositoryOwner === "gitgitgadget") {
args.push(...prArgs);
} else {
prArgs.unshift("fetch", `https://github.com/${repositoryOwner}/git`,
"--");
await git(prArgs, { workDir: this.workDir });
dscho marked this conversation as resolved.
Show resolved Hide resolved
}
await git(args, { workDir: this.workDir });

// re-read options
Expand Down Expand Up @@ -248,7 +258,8 @@ export class GitGitGadget {
send: SendFunction):
Promise<string | undefined> {

if (pr.baseOwner !== "gitgitgadget" || pr.baseRepo !== "git") {
if (!new Set(["gitgitgadget", "dscho", "git"]).has(pr.baseOwner) ||
pr.baseRepo !== "git") {
throw new Error(`Unsupported repository: ${pr.pullRequestURL}`);
}

Expand All @@ -263,7 +274,7 @@ export class GitGitGadget {
const previousTag = metadata && metadata.latestTag ?
`refs/tags/${metadata.latestTag}` : undefined;
// update work repo from base
await this.updateNotesAndPullRef(pr.number, previousTag);
await this.updateNotesAndPullRef(pr.baseOwner, pr.number, previousTag);

const series =
await PatchSeries.getFromNotes(this.notes, pr.pullRequestURL,
Expand Down
56 changes: 33 additions & 23 deletions 56 lib/github-glue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,15 @@ export interface IGitHubUser {

export class GitHubGlue {
public workDir?: string;
protected readonly client = new octokit();
protected authenticated = false;
protected client = new octokit();
protected authenticated?: string;

public constructor(workDir?: string) {
this.workDir = workDir;
}

public async annotateCommit(originalCommit: string, gitGitCommit: string):
Promise<number> {
public async annotateCommit(originalCommit: string, gitGitCommit: string,
repositoryOwner: string): Promise<number> {
const output =
await git(["show", "-s", "--format=%h %cI", gitGitCommit],
{ workDir: this.workDir });
Expand All @@ -52,7 +52,7 @@ export class GitHubGlue {
const [, short, completedAt] = match;
const url = `https://github.com/git/git/commit/${gitGitCommit}`;

await this.ensureAuthenticated();
await this.ensureAuthenticated(repositoryOwner);
const checks = await this.client.checks.create({
completed_at: completedAt,
conclusion: "success",
Expand All @@ -64,7 +64,7 @@ export class GitHubGlue {
summary: `Integrated into git.git as [${short}](${url}).`,
title: `In git.git: ${short}`,
},
owner: "gitgitgadget",
owner: repositoryOwner,
repo: "git",
status: "completed",
});
Expand All @@ -80,9 +80,9 @@ export class GitHubGlue {
*/
public async addPRComment(pullRequestURL: string, comment: string):
Promise<{id: number, url: string}> {
await this.ensureAuthenticated();
const [owner, repo, nr] =
GitGitGadget.parsePullRequestURL(pullRequestURL);
dscho marked this conversation as resolved.
Show resolved Hide resolved
await this.ensureAuthenticated(owner);
const status = await this.client.issues.createComment({
body: comment,
number: nr,
Expand All @@ -108,9 +108,9 @@ export class GitHubGlue {
gitWorkDir: string | undefined,
comment: string):
Promise<{id: number, url: string}> {
await this.ensureAuthenticated();
const [owner, repo, nr] =
GitGitGadget.parsePullRequestURL(pullRequestURL);
await this.ensureAuthenticated(owner);

const files = await git(["diff", "--name-only",
`${commit}^..${commit}`, "--"],
Expand Down Expand Up @@ -143,9 +143,9 @@ export class GitHubGlue {
public async addPRCommentReply(pullRequestURL: string, id: number,
comment: string):
Promise<{id: number, url: string}> {
await this.ensureAuthenticated();
const [owner, repo, nr] =
GitGitGadget.parsePullRequestURL(pullRequestURL);
await this.ensureAuthenticated(owner);

const status = await this.client.pulls.createCommentReply({
body: comment,
Expand All @@ -165,7 +165,7 @@ export class GitHubGlue {
const [owner, repo, prNo] =
GitGitGadget.parsePullRequestURL(pullRequestURL);

await this.ensureAuthenticated();
await this.ensureAuthenticated(owner);
const result = await this.client.issues.addLabels({
labels,
number: prNo,
Expand All @@ -180,7 +180,7 @@ export class GitHubGlue {
const [owner, repo, prNo] =
GitGitGadget.parsePullRequestURL(pullRequestURL);

await this.ensureAuthenticated();
await this.ensureAuthenticated(owner);
await this.client.pulls.update({
number: prNo,
owner,
Expand All @@ -199,10 +199,11 @@ export class GitHubGlue {

// The following public methods do not require authentication

public async getOpenPRs(): Promise<IPullRequestInfo[]> {
public async getOpenPRs(repositoryOwner: string):
Promise<IPullRequestInfo[]> {
const result: IPullRequestInfo[] = [];
const response = await this.client.pulls.list({
owner: "gitgitgadget",
owner: repositoryOwner,
per_page: 1000,
repo: "git",
state: "open",
Expand Down Expand Up @@ -234,9 +235,10 @@ export class GitHubGlue {
* @param prNumber the Pull Request's number
* @returns information about that Pull Request
*/
public async getPRInfo(prNumber: number): Promise<IPullRequestInfo> {
public async getPRInfo(repositoryOwner: string, prNumber: number):
Promise<IPullRequestInfo> {
const response = await this.client.pulls.get({
owner: "gitgitgadget",
owner: repositoryOwner,
pull_number: prNumber,
repo: "git",
});
Expand All @@ -263,10 +265,11 @@ export class GitHubGlue {
* @param commentID the ID of the PR/issue comment
* @returns the text in the comment
*/
public async getPRComment(commentID: number): Promise<IPRComment> {
public async getPRComment(repositoryOwner: string, commentID: number):
Promise<IPRComment> {
const response = await this.client.issues.getComment({
comment_id: commentID,
owner: "gitgitgadget",
owner: repositoryOwner,
repo: "git",
});
const match = response.data.html_url.match(/\/pull\/([0-9]+)/);
Expand All @@ -284,7 +287,8 @@ export class GitHubGlue {
* @param login the GitHub login
*/
public async getGitHubUserInfo(login: string): Promise<IGitHubUser> {
await this.ensureAuthenticated(); // required to get email
// required to get email
await this.ensureAuthenticated(this.authenticated || "gitgitgadget");

const response = await this.client.users.getByUsername({
username: login,
Expand All @@ -309,17 +313,23 @@ export class GitHubGlue {
return response.data.name;
}

protected async ensureAuthenticated(): Promise<void> {
if (!this.authenticated) {
const token = await gitConfig("gitgitgadget.githubToken");
protected async ensureAuthenticated(repositoryOwner: string):
Promise<void> {
if (repositoryOwner !== this.authenticated) {
if (this.authenticated) {
this.client = new octokit();
}
const infix = repositoryOwner === "gitgitgadget" ?
"" : `.${repositoryOwner}`;
const token = await gitConfig(`gitgitgadget${infix}.githubToken`);
if (!token) {
throw new Error(`Need a GitHub token`);
throw new Error(`Need a GitHub token for ${repositoryOwner}`);
}
this.client.authenticate({
token,
type: "token",
});
this.authenticated = true;
this.authenticated = repositoryOwner;
}
}
}
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.