Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Detect merge queue branch and retrieve real branch name from pull request #884

Merged
merged 6 commits into from
Jan 16, 2024
30 changes: 30 additions & 0 deletions node-src/git/getBranchFromMergeQueuePullRequestNumber.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import gql from 'fake-tag';
import { Context } from '../types';

const MergeQueueOriginalBranchQuery = gql`
query MergeQueueOriginalBranchQuery($number: Int!) {
app {
pullRequest(number: $number) {
branch: headRefName
}
}
}
`
interface MergeQueueOriginalBranchQueryResult {
app: {
pullRequest: {
branch: string;
};
};
}

export async function getBranchFromMergeQueuePullRequestNumber(
ctx: Pick<Context, 'options' | 'client' | 'git'>,
{ number }: { number: number }
) {
const { app } = await ctx.client.runQuery<MergeQueueOriginalBranchQueryResult>(MergeQueueOriginalBranchQuery, {
number
});

return app?.pullRequest?.branch;
}
18 changes: 17 additions & 1 deletion node-src/git/getCommitAndBranch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@ import envCi from 'env-ci';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

import * as git from './git';

import * as mergeQueue from './getBranchFromMergeQueuePullRequestNumber';
import getCommitAndBranch from './getCommitAndBranch';

vi.mock('env-ci');
vi.mock('./git');
vi.mock('./getBranchFromMergeQueuePullRequestNumber');

const getBranch = vi.mocked(git.getBranch);
const getCommit = vi.mocked(git.getCommit);
const hasPreviousCommit = vi.mocked(git.hasPreviousCommit);
const getBranchFromMergeQueue = vi.mocked(mergeQueue.getBranchFromMergeQueuePullRequestNumber);
const mergeQueueBranchMatch = vi.mocked(git.mergeQueueBranchMatch );

const log = { info: vi.fn(), warn: vi.fn(), debug: vi.fn() };

Expand All @@ -26,12 +29,14 @@ beforeEach(() => {
committerEmail: 'noreply@github.com',
});
hasPreviousCommit.mockResolvedValue(true);
mergeQueueBranchMatch.mockResolvedValue(null);
});

afterEach(() => {
envCi.mockReset();
getBranch.mockReset();
getCommit.mockReset();
getBranchFromMergeQueue.mockReset();
});

const commitInfo = {
Expand Down Expand Up @@ -228,4 +233,15 @@ describe('getCommitAndBranch', () => {
);
});
});

describe('with mergeQueue branch', () => {
it('uses PRs branchName as branch instead of temporary mergeQueue branch', async () => {
mergeQueueBranchMatch.mockResolvedValue(4);
getBranchFromMergeQueue.mockResolvedValue('branch-before-merge-queue');
const info = await getCommitAndBranch({ log }, {
branchName: 'this-is-merge-queue-branch-format/main/pr-4-48e0c83fadbf504c191bc868040b7a969a4f1feb',
});
expect(info).toMatchObject({ branch: 'branch-before-merge-queue' });
});
});
});
23 changes: 21 additions & 2 deletions node-src/git/getCommitAndBranch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import missingTravisInfo from '../ui/messages/errors/missingTravisInfo';
import customGitHubAction from '../ui/messages/info/customGitHubAction';
import travisInternalBuild from '../ui/messages/warnings/travisInternalBuild';
import noCommitDetails from '../ui/messages/warnings/noCommitDetails';
import { getBranch, getCommit, hasPreviousCommit } from './git';
import { getBranch, getCommit, hasPreviousCommit, mergeQueueBranchMatch } from './git';

import { getBranchFromMergeQueuePullRequestNumber } from './getBranchFromMergeQueuePullRequestNumber';

const ORIGIN_PREFIX_REGEXP = /^origin\//;
const notHead = (branch) => (branch && branch !== 'HEAD' ? branch : false);
Expand All @@ -21,13 +23,14 @@ interface CommitInfo {
}

export default async function getCommitAndBranch(
{ log },
ctx,
{
branchName,
patchBaseRef,
ci,
}: { branchName?: string; patchBaseRef?: string; ci?: boolean } = {}
) {
const { log } = ctx;
let commit: CommitInfo = await getCommit();
let branch = notHead(branchName) || notHead(patchBaseRef) || (await getBranch());
let slug;
Expand Down Expand Up @@ -157,6 +160,22 @@ export default async function getCommitAndBranch(
branch = branch.replace(ORIGIN_PREFIX_REGEXP, '');
}



// When a PR is put into a merge queue, and prior PR is merged, the PR is retested against the latest on main.
// To do this, GitHub creates a new commit and does a CI run with the branch changed to e.g. gh-readonly-queue/main/pr-4-da07417adc889156224d03a7466ac712c647cd36
// If you configure merge queues to rebase in this circumstance,
// we lose track of baselines as the branch name has changed so our usual rebase detection (based on branch name) doesn't work.
const mergeQueueBranchPrNumber = await mergeQueueBranchMatch(commit.commit, branch)
if (mergeQueueBranchPrNumber) {
// This is why we extract the PR number from the branch name and use it to find the branch name of the PR that was merged.
const branchFromMergeQueuePullRequestNumber = await getBranchFromMergeQueuePullRequestNumber(ctx, { number: mergeQueueBranchPrNumber });

if (branchFromMergeQueuePullRequestNumber) {
branch = branchFromMergeQueuePullRequestNumber;
}
}

log.debug(
`git info: ${JSON.stringify({
commit,
Expand Down
19 changes: 18 additions & 1 deletion node-src/git/git.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { execaCommand } from 'execa';
import { describe, expect, it, vi } from 'vitest';

import { getCommit, getSlug, hasPreviousCommit } from './git';
import { getCommit, getSlug, hasPreviousCommit, mergeQueueBranchMatch } from './git';

vi.mock('execa');

Expand Down Expand Up @@ -91,3 +91,20 @@ gpg: Can't check signature: No public key
expect(await hasPreviousCommit()).toEqual(true);
});
});


describe('mergeQueueBranchMatch', () => {
it('returns pr number if it is a merge queue branch', async () => {
const branch = "gh-readonly-queue/main/pr-4-da07417adc889156224d03a7466ac712c647cd36"
const commit = "da07417adc889156224d03a7466ac712c647cd36"
expect(await mergeQueueBranchMatch(commit, branch)).toEqual(4);
});

it('returns null if it is not a merge queue branch', async () => {
const branch = "develop"
const commit = "da07417adc889156224d03a7466ac712c647cd36"
expect(await mergeQueueBranchMatch(commit, branch)).toEqual(
null
);
});
});
7 changes: 7 additions & 0 deletions node-src/git/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,3 +281,10 @@ export async function findFiles(...patterns: string[]) {
const files = await execGitCommand(`git ls-files -z ${patterns.map((p) => `'${p}'`).join(' ')}`);
return files.split('\0').filter(Boolean);
}

export async function mergeQueueBranchMatch(commit, branch) {
const mergeQueuePattern = new RegExp(`gh-readonly-queue/.*/pr-(\\d+)-${commit}$`);
const match = branch.match(mergeQueuePattern);

return match ? Number(match[1]) : null;
}
1 change: 1 addition & 0 deletions node-src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ vi.mock('./git/git', () => ({
getRepositoryRoot: () => Promise.resolve(process.cwd()),
getUncommittedHash: () => Promise.resolve('abc123'),
getUserEmail: () => Promise.resolve('test@test.com'),
mergeQueueBranchMatch: () => Promise.resolve(null),
}));

vi.mock('./git/getParentCommits', () => ({
Expand Down
Loading