Skip to content

Commit 6f2efce

Browse files
authored
Merge pull request #89 from devverseio/fix/prevent-synchronize-if-coo-authored-with-bot
fix: prevent synchronize if co authored with bot
2 parents 411fb7a + 309db3c commit 6f2efce

File tree

5 files changed

+76
-8
lines changed

5 files changed

+76
-8
lines changed

src/handlers/app.handler.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,12 @@ export class AppHandler {
5151

5252
// Handler initialization
5353
this.handlers = {
54-
reviewHandler: new ReviewHandler(this.services.aiService, this.services.queueService, this.services.cacheService),
54+
reviewHandler: new ReviewHandler(
55+
this.services.aiService,
56+
this.services.queueService,
57+
this.services.cacheService,
58+
this.services.appService
59+
),
5560
walkthroughHandler: new WalkthroughHandler(
5661
this.services.aiService,
5762
this.services.queueService,
@@ -97,6 +102,7 @@ export class AppHandler {
97102
apiKey = await this.services.configService.getApiKey(context);
98103
config = await this.services.configService.getConfig(context, apiKey);
99104

105+
await this.services.appService.init(context);
100106
await callback(apiKey, config);
101107
} catch (error) {
102108
if ('repository' in context.payload) {
@@ -124,7 +130,6 @@ export class AppHandler {
124130
): Promise<void> {
125131
await this.safeEventHandler(context, async (apiKey, config) => {
126132
await this.handlers.contributorHandler.safeCacheContributors(context, apiKey);
127-
await this.services.appService.init(context);
128133
await this.handlers.chatHandler.handle(context, apiKey, config);
129134
});
130135
}

src/handlers/review.handler.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,26 @@ import { aiConfig } from '~configs';
88
import { SystemError } from '~errors';
99
import { cleanDiffString } from '~helpers';
1010
import { reviewObjectSchema } from '~schemas';
11-
import { AiService, CacheService, GitHubService, QueueService, TemplateService } from '~services';
11+
import { AiService, AppService, CacheService, GitHubService, QueueService, TemplateService } from '~services';
1212
import { reviewCommentTemplate, reviewPromptTemplate, reviewSystemTemplate } from '~templates';
1313
import { ReviewSkipConditions } from '~utils';
1414

1515
export class ReviewHandler implements Handler<'pull_request'> {
1616
constructor(
1717
private readonly aiService: AiService,
1818
private readonly queueService: QueueService,
19-
private readonly cacheService: CacheService
19+
private readonly cacheService: CacheService,
20+
private readonly appService: AppService
2021
) {}
2122

2223
async handle(context: Context<'pull_request'>, apiKey: string, config: Config): Promise<void> {
23-
const shouldSkip = await new ReviewSkipConditions(context, config, this.cacheService, apiKey).shouldSkip();
24+
const shouldSkip = await new ReviewSkipConditions(
25+
context,
26+
config,
27+
this.cacheService,
28+
this.appService,
29+
apiKey
30+
).shouldSkip();
2431

2532
if (shouldSkip) {
2633
return;

src/services/app.service.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { GitHubService } from './github.service';
77
export class AppService {
88
private static instance: AppService;
99
private appData?: RestEndpointMethodTypes['apps']['getAuthenticated']['response']['data'];
10+
private user?: RestEndpointMethodTypes['users']['getByUsername']['response']['data'];
1011

1112
private constructor() {}
1213

@@ -19,11 +20,13 @@ export class AppService {
1920
}
2021

2122
async init(context: Context): Promise<void> {
22-
if (!this.appData) {
23+
if (!this.appData || !this.user) {
2324
try {
2425
const { data } = await GitHubService.getAuthenticated(context);
2526

2627
this.appData = data;
28+
29+
this.user = await GitHubService.getByUsername(this.appData!.slug + '[bot]', context);
2730
} catch (error) {
2831
throw new SystemError('Failed to get authenticated app data', Severity.ERROR, error);
2932
}
@@ -41,4 +44,16 @@ export class AppService {
4144

4245
return this.appData!.slug;
4346
}
47+
48+
getLogin(): string {
49+
this.ensureInitialized();
50+
51+
return this.user!.login;
52+
}
53+
54+
getEmail(): string {
55+
this.ensureInitialized();
56+
57+
return `${this.user!.id}+${this.user!.login}@users.noreply.github.com`;
58+
}
4459
}

src/services/github.service.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,19 @@ export class GitHubService {
2727
}
2828
}
2929

30+
static getCommit = (
31+
commitSha: string,
32+
context: Context
33+
): Promise<RestEndpointMethodTypes['repos']['getCommit']['response']> => {
34+
const { owner, repo } = context.repo();
35+
36+
return context.octokit.repos.getCommit({
37+
owner,
38+
repo,
39+
ref: commitSha
40+
});
41+
};
42+
3043
static getGitHubRepoVariable(
3144
variableName: string,
3245
context: Context
@@ -57,6 +70,17 @@ export class GitHubService {
5770
return context.octokit.apps.getAuthenticated();
5871
}
5972

73+
static getByUsername = async (
74+
username: string,
75+
context: Context
76+
): Promise<RestEndpointMethodTypes['users']['getByUsername']['response']['data']> => {
77+
const { data } = await context.octokit.users.getByUsername({
78+
username
79+
});
80+
81+
return data;
82+
};
83+
6084
static paginateListComments(
6185
context: Context
6286
): Promise<RestEndpointMethodTypes['issues']['listComments']['response']['data']> {

src/utils/review-skip-conditions.util.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@ import { RestEndpointMethodTypes } from '@octokit/plugin-rest-endpoint-methods';
22
import { Context } from 'probot';
33
import { Config } from '~common/types';
44
import { getUserContributorsCacheKey } from '~helpers';
5-
import { CacheService } from '~services';
5+
import { AppService, CacheService, GitHubService } from '~services';
66

77
export class ReviewSkipConditions {
88
constructor(
99
private readonly context: Context<'pull_request'>,
1010
private readonly config: Config,
1111
private readonly cacheService: CacheService,
12+
private readonly appService: AppService,
1213
private readonly apiKey: string
1314
) {}
1415

@@ -24,12 +25,28 @@ export class ReviewSkipConditions {
2425
this.isMerged(),
2526
this.isUserIgnored(),
2627
this.isContributorOnly(),
27-
this.isAuthorOnly()
28+
this.isAuthorOnly(),
29+
this.isCoAuthoredWithBot()
2830
];
2931

3032
return (await Promise.all(skipChecks)).some(Boolean);
3133
}
3234

35+
private async isCoAuthoredWithBot(): Promise<boolean> {
36+
if (this.context.payload.action === 'synchronize') {
37+
const commitSha = this.context.payload.after;
38+
const commit = await GitHubService.getCommit(commitSha, this.context);
39+
const { message } = commit.data.commit;
40+
const login = this.appService.getLogin();
41+
const email = this.appService.getEmail();
42+
const coAuthoredMessage = `${login} <${email}>`;
43+
44+
return message.toLowerCase().includes(coAuthoredMessage.toLowerCase());
45+
}
46+
47+
return false;
48+
}
49+
3350
private async isContributorOnly(): Promise<boolean> {
3451
const { 'contributors-only': contributorsOnly, always } = this.config.review.allow['pull-requests'];
3552

0 commit comments

Comments
 (0)