Skip to content

Commit ee6d82f

Browse files
fix: backfill files by commit for rebased PRs (#2141)
* fix: backfill files by commit for rebased PRs Fixes #2140 * fix: always set commit PR from associated PRs --------- Co-authored-by: Jeff Ching <chingor@google.com>
1 parent 87f4a93 commit ee6d82f

4 files changed

Lines changed: 198 additions & 8 deletions

File tree

__snapshots__/github.js

Lines changed: 37 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/github.ts

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -469,17 +469,42 @@ export class GitHub {
469469
}
470470
const history = response.repository.ref.target.history;
471471
const commits = (history.nodes || []) as GraphQLCommit[];
472+
// Count the number of pull requests associated with each merge commit. This is
473+
// used in the next step to make sure we only find pull requests with a
474+
// merge commit that contain 1 merged commit.
475+
const mergeCommitCount: Record<string, number> = {};
476+
for (const commit of commits) {
477+
for (const pr of commit.associatedPullRequests.nodes) {
478+
if (pr.mergeCommit?.oid) {
479+
mergeCommitCount[pr.mergeCommit.oid] ??= 0;
480+
mergeCommitCount[pr.mergeCommit.oid]++;
481+
}
482+
}
483+
}
472484
const commitData: Commit[] = [];
473485
for (const graphCommit of commits) {
474486
const commit: Commit = {
475487
sha: graphCommit.sha,
476488
message: graphCommit.message,
477489
};
478-
const pullRequest = graphCommit.associatedPullRequests.nodes.find(pr => {
479-
return pr.mergeCommit && pr.mergeCommit.oid === graphCommit.sha;
480-
});
490+
const mergePullRequest = graphCommit.associatedPullRequests.nodes.find(
491+
pr => {
492+
return (
493+
// Only match the pull request with a merge commit if there is a
494+
// single merged commit in the PR. This means merge commits and squash
495+
// merges will be matched, but rebase merged PRs will only be matched
496+
// if they contain a single commit. This is so PRs that are rebased
497+
// and merged will have ßSfiles backfilled from each commit instead of
498+
// the whole PR.
499+
pr.mergeCommit &&
500+
pr.mergeCommit.oid === graphCommit.sha &&
501+
mergeCommitCount[pr.mergeCommit.oid] === 1
502+
);
503+
}
504+
);
505+
const pullRequest =
506+
mergePullRequest || graphCommit.associatedPullRequests.nodes[0];
481507
if (pullRequest) {
482-
const files = (pullRequest.files?.nodes || []).map(node => node.path);
483508
commit.pullRequest = {
484509
sha: commit.sha,
485510
number: pullRequest.number,
@@ -488,17 +513,24 @@ export class GitHub {
488513
title: pullRequest.title,
489514
body: pullRequest.body,
490515
labels: pullRequest.labels.nodes.map(node => node.name),
491-
files,
516+
files: (pullRequest.files?.nodes || []).map(node => node.path),
492517
};
493-
if (pullRequest.files?.pageInfo?.hasNextPage && options.backfillFiles) {
518+
}
519+
if (mergePullRequest) {
520+
if (
521+
mergePullRequest.files?.pageInfo?.hasNextPage &&
522+
options.backfillFiles
523+
) {
494524
this.logger.info(
495-
`PR #${pullRequest.number} has many files, backfilling`
525+
`PR #${mergePullRequest.number} has many files, backfilling`
496526
);
497527
commit.files = await this.getCommitFiles(graphCommit.sha);
498528
} else {
499529
// We cannot directly fetch files on commits via graphql, only provide file
500530
// information for commits with associated pull requests
501-
commit.files = files;
531+
commit.files = (mergePullRequest.files?.nodes || []).map(
532+
node => node.path
533+
);
502534
}
503535
} else if (options.backfillFiles) {
504536
// In this case, there is no squashed merge commit. This could be a simple
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
{
2+
"repository": {
3+
"ref": {
4+
"target": {
5+
"history": {
6+
"nodes": [
7+
{
8+
"associatedPullRequests": {
9+
"nodes": [
10+
{
11+
"number": 7,
12+
"title": "feat: feature that will be rebase merged",
13+
"baseRefName": "main",
14+
"headRefName": "feature-branch-rebase-merge",
15+
"labels": {
16+
"nodes": []
17+
},
18+
"body": "",
19+
"mergeCommit": {
20+
"oid": "b29149f890e6f76ee31ed128585744d4c598924c"
21+
},
22+
"files": {
23+
"nodes": []
24+
}
25+
}
26+
]
27+
},
28+
"sha": "b29149f890e6f76ee31ed128585744d4c598924c",
29+
"message": "feat: feature-branch-rebase-merge commit 1"
30+
},
31+
{
32+
"associatedPullRequests": {
33+
"nodes": [
34+
{
35+
"number": 7,
36+
"title": "feat: feature that will be rebase merged",
37+
"baseRefName": "main",
38+
"headRefName": "feature-branch-rebase-merge",
39+
"labels": {
40+
"nodes": []
41+
},
42+
"body": "",
43+
"mergeCommit": {
44+
"oid": "b29149f890e6f76ee31ed128585744d4c598924c"
45+
},
46+
"files": {
47+
"nodes": []
48+
}
49+
}
50+
]
51+
},
52+
"sha": "27d7d7232e2e312d1380e906984f0823f5decf61",
53+
"message": "feat: feature-branch-rebase-merge commit 2"
54+
},
55+
{
56+
"associatedPullRequests": {
57+
"nodes": [
58+
{
59+
"number": 6,
60+
"title": "feat: other pr",
61+
"baseRefName": "main",
62+
"headRefName": "feature-branch-other",
63+
"labels": {
64+
"nodes": []
65+
},
66+
"body": "",
67+
"mergeCommit": {
68+
"oid": "2b4e0b3be2e231cd87cc44c411bd8f84b4587ab5"
69+
},
70+
"files": {
71+
"nodes": []
72+
}
73+
}
74+
]
75+
},
76+
"sha": "2b4e0b3be2e231cd87cc44c411bd8f84b4587ab5",
77+
"message": "fix: feature-branch-other"
78+
}
79+
],
80+
"pageInfo": {
81+
"hasNextPage": false,
82+
"endCursor": "e6daec403626c9987c7af0d97b34f324cd84320a 12"
83+
}
84+
}
85+
}
86+
}
87+
}
88+
}

test/github.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,39 @@ describe('GitHub', () => {
546546
snapshot(commitsSinceSha);
547547
req.done();
548548
});
549+
550+
it('backfills commit files for pull requests rebased and merged', async () => {
551+
const graphql = JSON.parse(
552+
readFileSync(resolve(fixturesPath, 'commits-since-rebase.json'), 'utf8')
553+
);
554+
req
555+
.post('/graphql')
556+
.reply(200, {
557+
data: graphql,
558+
})
559+
.get(
560+
'/repos/fake/fake/commits/b29149f890e6f76ee31ed128585744d4c598924c'
561+
)
562+
.reply(200, {files: [{filename: 'abc'}]})
563+
.get(
564+
'/repos/fake/fake/commits/27d7d7232e2e312d1380e906984f0823f5decf61'
565+
)
566+
.reply(200, {files: [{filename: 'def'}]});
567+
const targetBranch = 'main';
568+
const commitsSinceSha = await github.commitsSince(
569+
targetBranch,
570+
commit => {
571+
// this commit is the 3rd most recent
572+
return commit.sha === '2b4e0b3be2e231cd87cc44c411bd8f84b4587ab5';
573+
},
574+
{backfillFiles: true}
575+
);
576+
expect(commitsSinceSha.length).to.eql(2);
577+
expect(commitsSinceSha[0].files).to.eql(['abc']);
578+
expect(commitsSinceSha[1].files).to.eql(['def']);
579+
snapshot(commitsSinceSha);
580+
req.done();
581+
});
549582
});
550583

551584
describe('mergeCommitIterator', () => {

0 commit comments

Comments
 (0)