Skip to content

Commit 2ff2fbd

Browse files
authored
telemetry for incorrect merge commit (#253)
1 parent df86c82 commit 2ff2fbd

File tree

4 files changed

+218
-7
lines changed

4 files changed

+218
-7
lines changed

dist/index.js

Lines changed: 95 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3359,7 +3359,7 @@ module.exports = {"name":"@octokit/rest","version":"16.43.1","publishConfig":{"a
33593359
/***/ }),
33603360

33613361
/***/ 227:
3362-
/***/ (function(__unusedmodule, exports) {
3362+
/***/ (function(__unusedmodule, exports, __webpack_require__) {
33633363

33643364
"use strict";
33653365

@@ -3372,7 +3372,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
33723372
step((generator = generator.apply(thisArg, _arguments || [])).next());
33733373
});
33743374
};
3375+
var __importStar = (this && this.__importStar) || function (mod) {
3376+
if (mod && mod.__esModule) return mod;
3377+
var result = {};
3378+
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
3379+
result["default"] = mod;
3380+
return result;
3381+
};
33753382
Object.defineProperty(exports, "__esModule", { value: true });
3383+
const url_1 = __webpack_require__(835);
3384+
const core = __importStar(__webpack_require__(470));
3385+
const github = __importStar(__webpack_require__(469));
33763386
function getCheckoutInfo(git, ref, commit) {
33773387
return __awaiter(this, void 0, void 0, function* () {
33783388
if (!git) {
@@ -3468,6 +3478,85 @@ function getRefSpec(ref, commit) {
34683478
}
34693479
}
34703480
exports.getRefSpec = getRefSpec;
3481+
function checkCommitInfo(token, commitInfo, repositoryOwner, repositoryName, ref, commit) {
3482+
return __awaiter(this, void 0, void 0, function* () {
3483+
try {
3484+
// GHES?
3485+
if (isGhes()) {
3486+
return;
3487+
}
3488+
// Auth token?
3489+
if (!token) {
3490+
return;
3491+
}
3492+
// Public PR synchronize, for workflow repo?
3493+
if (fromPayload('repository.private') !== false ||
3494+
github.context.eventName !== 'pull_request' ||
3495+
fromPayload('action') !== 'synchronize' ||
3496+
repositoryOwner !== github.context.repo.owner ||
3497+
repositoryName !== github.context.repo.repo ||
3498+
ref !== github.context.ref ||
3499+
!ref.startsWith('refs/pull/') ||
3500+
commit !== github.context.sha) {
3501+
return;
3502+
}
3503+
// Head SHA
3504+
const expectedHeadSha = fromPayload('after');
3505+
if (!expectedHeadSha) {
3506+
core.debug('Unable to determine head sha');
3507+
return;
3508+
}
3509+
// Base SHA
3510+
const expectedBaseSha = fromPayload('pull_request.base.sha');
3511+
if (!expectedBaseSha) {
3512+
core.debug('Unable to determine base sha');
3513+
return;
3514+
}
3515+
// Expected message?
3516+
const expectedMessage = `Merge ${expectedHeadSha} into ${expectedBaseSha}`;
3517+
if (commitInfo.indexOf(expectedMessage) >= 0) {
3518+
return;
3519+
}
3520+
// Extract details from message
3521+
const match = commitInfo.match(/Merge ([0-9a-f]{40}) into ([0-9a-f]{40})/);
3522+
if (!match) {
3523+
core.debug('Unexpected message format');
3524+
return;
3525+
}
3526+
// Post telemetry
3527+
const actualHeadSha = match[1];
3528+
if (actualHeadSha !== expectedHeadSha) {
3529+
core.debug(`Expected head sha ${expectedHeadSha}; actual head sha ${actualHeadSha}`);
3530+
const octokit = new github.GitHub(token, {
3531+
userAgent: `actions-checkout-tracepoint/1.0 (code=STALE_MERGE;owner=${repositoryOwner};repo=${repositoryName};pr=${fromPayload('number')};run_id=${process.env['GITHUB_RUN_ID']};expected_head_sha=${expectedHeadSha};actual_head_sha=${actualHeadSha})`
3532+
});
3533+
yield octokit.repos.get({ owner: repositoryOwner, repo: repositoryName });
3534+
}
3535+
}
3536+
catch (err) {
3537+
core.debug(`Error when validating commit info: ${err.stack}`);
3538+
}
3539+
});
3540+
}
3541+
exports.checkCommitInfo = checkCommitInfo;
3542+
function fromPayload(path) {
3543+
return select(github.context.payload, path);
3544+
}
3545+
function select(obj, path) {
3546+
if (!obj) {
3547+
return undefined;
3548+
}
3549+
const i = path.indexOf('.');
3550+
if (i < 0) {
3551+
return obj[path];
3552+
}
3553+
const key = path.substr(0, i);
3554+
return select(obj[key], path.substr(i + 1));
3555+
}
3556+
function isGhes() {
3557+
const ghUrl = new url_1.URL(process.env['GITHUB_SERVER_URL'] || 'https://github.com');
3558+
return ghUrl.hostname.toUpperCase() !== 'GITHUB.COM';
3559+
}
34713560

34723561

34733562
/***/ }),
@@ -5718,7 +5807,8 @@ class GitCommandManager {
57185807
}
57195808
log1() {
57205809
return __awaiter(this, void 0, void 0, function* () {
5721-
yield this.execGit(['log', '-1']);
5810+
const output = yield this.execGit(['log', '-1']);
5811+
return output.stdout;
57225812
});
57235813
}
57245814
remoteAdd(remoteName, remoteUrl) {
@@ -6057,7 +6147,9 @@ function getSource(settings) {
60576147
}
60586148
}
60596149
// Dump some info about the checked out commit
6060-
yield git.log1();
6150+
const commitInfo = yield git.log1();
6151+
// Check for incorrect pull request merge commit
6152+
yield refHelper.checkCommitInfo(settings.authToken, commitInfo, settings.repositoryOwner, settings.repositoryName, settings.ref, settings.commit);
60616153
}
60626154
finally {
60636155
// Remove auth

src/git-command-manager.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export interface IGitCommandManager {
2929
isDetached(): Promise<boolean>
3030
lfsFetch(ref: string): Promise<void>
3131
lfsInstall(): Promise<void>
32-
log1(): Promise<void>
32+
log1(): Promise<string>
3333
remoteAdd(remoteName: string, remoteUrl: string): Promise<void>
3434
removeEnvironmentVariable(name: string): void
3535
setEnvironmentVariable(name: string, value: string): void
@@ -225,8 +225,9 @@ class GitCommandManager {
225225
await this.execGit(['lfs', 'install', '--local'])
226226
}
227227

228-
async log1(): Promise<void> {
229-
await this.execGit(['log', '-1'])
228+
async log1(): Promise<string> {
229+
const output = await this.execGit(['log', '-1'])
230+
return output.stdout
230231
}
231232

232233
async remoteAdd(remoteName: string, remoteUrl: string): Promise<void> {

src/git-source-provider.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,17 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
170170
}
171171

172172
// Dump some info about the checked out commit
173-
await git.log1()
173+
const commitInfo = await git.log1()
174+
175+
// Check for incorrect pull request merge commit
176+
await refHelper.checkCommitInfo(
177+
settings.authToken,
178+
commitInfo,
179+
settings.repositoryOwner,
180+
settings.repositoryName,
181+
settings.ref,
182+
settings.commit
183+
)
174184
} finally {
175185
// Remove auth
176186
if (!settings.persistCredentials) {

src/ref-helper.ts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
import {URL} from 'url'
12
import {IGitCommandManager} from './git-command-manager'
3+
import * as core from '@actions/core'
4+
import * as github from '@actions/github'
25

36
export interface ICheckoutInfo {
47
ref: string
@@ -107,3 +110,108 @@ export function getRefSpec(ref: string, commit: string): string[] {
107110
return [`+${ref}:${ref}`]
108111
}
109112
}
113+
114+
export async function checkCommitInfo(
115+
token: string,
116+
commitInfo: string,
117+
repositoryOwner: string,
118+
repositoryName: string,
119+
ref: string,
120+
commit: string
121+
): Promise<void> {
122+
try {
123+
// GHES?
124+
if (isGhes()) {
125+
return
126+
}
127+
128+
// Auth token?
129+
if (!token) {
130+
return
131+
}
132+
133+
// Public PR synchronize, for workflow repo?
134+
if (
135+
fromPayload('repository.private') !== false ||
136+
github.context.eventName !== 'pull_request' ||
137+
fromPayload('action') !== 'synchronize' ||
138+
repositoryOwner !== github.context.repo.owner ||
139+
repositoryName !== github.context.repo.repo ||
140+
ref !== github.context.ref ||
141+
!ref.startsWith('refs/pull/') ||
142+
commit !== github.context.sha
143+
) {
144+
return
145+
}
146+
147+
// Head SHA
148+
const expectedHeadSha = fromPayload('after')
149+
if (!expectedHeadSha) {
150+
core.debug('Unable to determine head sha')
151+
return
152+
}
153+
154+
// Base SHA
155+
const expectedBaseSha = fromPayload('pull_request.base.sha')
156+
if (!expectedBaseSha) {
157+
core.debug('Unable to determine base sha')
158+
return
159+
}
160+
161+
// Expected message?
162+
const expectedMessage = `Merge ${expectedHeadSha} into ${expectedBaseSha}`
163+
if (commitInfo.indexOf(expectedMessage) >= 0) {
164+
return
165+
}
166+
167+
// Extract details from message
168+
const match = commitInfo.match(/Merge ([0-9a-f]{40}) into ([0-9a-f]{40})/)
169+
if (!match) {
170+
core.debug('Unexpected message format')
171+
return
172+
}
173+
174+
// Post telemetry
175+
const actualHeadSha = match[1]
176+
if (actualHeadSha !== expectedHeadSha) {
177+
core.debug(
178+
`Expected head sha ${expectedHeadSha}; actual head sha ${actualHeadSha}`
179+
)
180+
const octokit = new github.GitHub(token, {
181+
userAgent: `actions-checkout-tracepoint/1.0 (code=STALE_MERGE;owner=${repositoryOwner};repo=${repositoryName};pr=${fromPayload(
182+
'number'
183+
)};run_id=${
184+
process.env['GITHUB_RUN_ID']
185+
};expected_head_sha=${expectedHeadSha};actual_head_sha=${actualHeadSha})`
186+
})
187+
await octokit.repos.get({owner: repositoryOwner, repo: repositoryName})
188+
}
189+
} catch (err) {
190+
core.debug(`Error when validating commit info: ${err.stack}`)
191+
}
192+
}
193+
194+
function fromPayload(path: string): any {
195+
return select(github.context.payload, path)
196+
}
197+
198+
function select(obj: any, path: string): any {
199+
if (!obj) {
200+
return undefined
201+
}
202+
203+
const i = path.indexOf('.')
204+
if (i < 0) {
205+
return obj[path]
206+
}
207+
208+
const key = path.substr(0, i)
209+
return select(obj[key], path.substr(i + 1))
210+
}
211+
212+
function isGhes(): boolean {
213+
const ghUrl = new URL(
214+
process.env['GITHUB_SERVER_URL'] || 'https://github.com'
215+
)
216+
return ghUrl.hostname.toUpperCase() !== 'GITHUB.COM'
217+
}

0 commit comments

Comments
 (0)