Skip to content

Commit 86837d7

Browse files
committed
prepush
Signed-off-by: Tyler Smalley <tyler.smalley@elastic.co>
1 parent a08d2d2 commit 86837d7

8 files changed

Lines changed: 161 additions & 26 deletions

File tree

src/dev/run_check.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,12 +129,17 @@ const formatHeader = (baseContext: ValidationBaseContext) => {
129129

130130
const { contract, runContext } = baseContext;
131131

132-
if (runContext.kind === 'affected' && runContext.resolvedBase && contract.scope === 'branch') {
132+
if (
133+
runContext.kind === 'affected' &&
134+
runContext.resolvedBase &&
135+
(contract.scope === 'branch' || contract.scope === 'prepush')
136+
) {
137+
const label = contract.scope === 'prepush' ? 'prepush' : 'branch';
133138
const base = runContext.resolvedBase.baseRef;
134139
const sha = runContext.resolvedBase.base.slice(0, 12);
135140
const commits =
136141
runContext.branchCommitCount !== undefined ? ` ${runContext.branchCommitCount} commits` : '';
137-
return `check branch ${base} (${sha})..HEAD${commits}`;
142+
return `check ${label} ${base} (${sha})..HEAD${commits}`;
138143
}
139144

140145
return `check scope=${contract.scope}`;
@@ -541,6 +546,7 @@ run(
541546
node scripts/check --scope local
542547
node scripts/check --profile agent
543548
node scripts/check --scope branch --base-ref origin/main
549+
node scripts/check --scope prepush
544550
`,
545551
flags: {
546552
string: [...VALIDATION_RUN_STRING_FLAGS],
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
import { REPO_ROOT } from '@kbn/repo-info';
11+
12+
export interface UpstreamPushRef {
13+
base: string;
14+
baseRef: string;
15+
}
16+
17+
const tryResolveRef = async (ref: string): Promise<UpstreamPushRef | null> => {
18+
const execa = (await import('execa')).default;
19+
try {
20+
const { stdout: sha } = await execa('git', ['rev-parse', ref], {
21+
cwd: REPO_ROOT,
22+
reject: true,
23+
});
24+
const base = sha.trim();
25+
if (!base) return null;
26+
27+
const { stdout: symbolic } = await execa('git', ['rev-parse', '--abbrev-ref', ref], {
28+
cwd: REPO_ROOT,
29+
reject: false,
30+
});
31+
return { base, baseRef: symbolic.trim() || ref };
32+
} catch {
33+
return null;
34+
}
35+
};
36+
37+
/**
38+
* Resolves the remote tracking ref for the current branch that would be the
39+
* target of a `git push`. Tries `@{push}` first (honors pushDefault config),
40+
* then falls back to `@{upstream}`. Returns null when no tracking branch is
41+
* configured.
42+
*/
43+
export const getUpstreamPushRef = async (): Promise<UpstreamPushRef | null> => {
44+
return (await tryResolveRef('@{push}')) ?? (await tryResolveRef('@{upstream}'));
45+
};

src/platform/packages/shared/kbn-dev-utils/src/git/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
export { getRemoteDefaultBranchRefs } from './get_remote_default_branch_refs';
1111
export { countCommitsBetweenRefs } from './count_commits_between_refs';
12+
export { getUpstreamPushRef } from './get_upstream_push_ref';
13+
export type { UpstreamPushRef } from './get_upstream_push_ref';
1214
export { hasStagedChanges } from './has_staged_changes';
1315
export { isShallowRepository } from './is_shallow_repository';
1416
export { resolveNearestMergeBase } from './resolve_nearest_merge_base';

src/platform/packages/shared/kbn-dev-utils/src/validation_run_contract.test.ts

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ describe('VALIDATION_PROFILE_DEFAULTS', () => {
4040
testMode: 'affected',
4141
downstream: 'deep',
4242
},
43+
prepush: {
44+
scope: 'prepush',
45+
testMode: 'affected',
46+
downstream: 'none',
47+
},
4348
full: {
4449
scope: 'full',
4550
testMode: 'all',
@@ -127,10 +132,10 @@ describe('parseAndResolveValidationContract', () => {
127132

128133
it('throws for unsupported flag values', () => {
129134
expect(() => parseAndResolveValidationContract({ profile: 'fast' })).toThrow(
130-
"Unsupported --profile value 'fast'. Expected one of: precommit, quick, agent, branch, pr, full."
135+
"Unsupported --profile value 'fast'. Expected one of: precommit, quick, agent, branch, pr, prepush, full."
131136
);
132137
expect(() => parseAndResolveValidationContract({ scope: 'workspace' })).toThrow(
133-
"Unsupported --scope value 'workspace'. Expected one of: staged, local, branch, full."
138+
"Unsupported --scope value 'workspace'. Expected one of: staged, local, branch, prepush, full."
134139
);
135140
expect(() => parseAndResolveValidationContract({ testMode: 'delta' })).toThrow(
136141
"Unsupported --test-mode value 'delta'. Expected one of: related, affected, all."
@@ -140,19 +145,47 @@ describe('parseAndResolveValidationContract', () => {
140145
);
141146
});
142147

143-
it('rejects refs outside branch scope', () => {
148+
it('rejects refs outside branch/prepush scope', () => {
144149
expect(() =>
145150
parseAndResolveValidationContract({
146151
scope: 'local',
147152
baseRef: 'origin/main',
148153
})
149-
).toThrow('--base-ref can only be used when --scope is branch.');
154+
).toThrow('--base-ref can only be used when --scope is branch or prepush.');
150155

151156
expect(() =>
152157
parseAndResolveValidationContract({
153158
scope: 'staged',
154159
headRef: 'HEAD~1',
155160
})
156-
).toThrow('--head-ref can only be used when --scope is branch.');
161+
).toThrow('--head-ref can only be used when --scope is branch or prepush.');
162+
});
163+
164+
it('allows base-ref and head-ref with prepush scope', () => {
165+
expect(
166+
parseAndResolveValidationContract({
167+
scope: 'prepush',
168+
baseRef: 'origin/main',
169+
headRef: 'HEAD',
170+
})
171+
).toEqual({
172+
profile: 'branch',
173+
scope: 'prepush',
174+
testMode: 'affected',
175+
downstream: 'none',
176+
baseRef: 'origin/main',
177+
headRef: 'HEAD',
178+
});
179+
});
180+
181+
it('resolves prepush profile with prepush scope defaults', () => {
182+
expect(parseAndResolveValidationContract({ profile: 'prepush' })).toEqual({
183+
profile: 'prepush',
184+
scope: 'prepush',
185+
testMode: 'affected',
186+
downstream: 'none',
187+
baseRef: undefined,
188+
headRef: 'HEAD',
189+
});
157190
});
158191
});

src/platform/packages/shared/kbn-dev-utils/src/validation_run_contract.ts

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,17 @@
99

1010
import { createFailError } from '@kbn/dev-cli-errors';
1111

12-
export type ValidationScope = 'staged' | 'local' | 'branch' | 'full';
12+
export type ValidationScope = 'staged' | 'local' | 'branch' | 'prepush' | 'full';
1313
export type ValidationTestMode = 'related' | 'affected' | 'all';
1414
export type ValidationDownstreamMode = 'none' | 'direct' | 'deep';
15-
export type ValidationProfile = 'precommit' | 'quick' | 'agent' | 'branch' | 'pr' | 'full';
15+
export type ValidationProfile =
16+
| 'precommit'
17+
| 'quick'
18+
| 'agent'
19+
| 'branch'
20+
| 'pr'
21+
| 'prepush'
22+
| 'full';
1623

1724
export interface ValidationProfileDefaults {
1825
scope: ValidationScope;
@@ -43,6 +50,11 @@ export const VALIDATION_PROFILE_DEFAULTS: Readonly<
4350
testMode: 'affected',
4451
downstream: 'none',
4552
},
53+
prepush: {
54+
scope: 'prepush',
55+
testMode: 'affected',
56+
downstream: 'none',
57+
},
4658
pr: {
4759
scope: 'branch',
4860
testMode: 'affected',
@@ -90,6 +102,7 @@ const parseValidationScope = createEnumParser<ValidationScope>('scope', [
90102
'staged',
91103
'local',
92104
'branch',
105+
'prepush',
93106
'full',
94107
]);
95108

@@ -111,6 +124,7 @@ const parseValidationProfile = createEnumParser<ValidationProfile>('profile', [
111124
'agent',
112125
'branch',
113126
'pr',
127+
'prepush',
114128
'full',
115129
]);
116130

@@ -140,7 +154,8 @@ const resolveValidationContract = ({
140154
testMode: testMode ?? defaults.testMode,
141155
downstream: downstream ?? defaults.downstream,
142156
baseRef,
143-
headRef: resolvedScope === 'branch' ? headRef ?? 'HEAD' : headRef,
157+
headRef:
158+
resolvedScope === 'branch' || resolvedScope === 'prepush' ? headRef ?? 'HEAD' : headRef,
144159
};
145160
};
146161

@@ -150,11 +165,11 @@ const validateResolvedValidationContract = ({
150165
baseRef,
151166
headRef,
152167
}: Pick<ResolvedValidationContract, 'scope' | 'baseRef' | 'headRef'>): void => {
153-
if (scope !== 'branch' && baseRef !== undefined) {
154-
throw createFailError('--base-ref can only be used when --scope is branch.');
168+
if (scope !== 'branch' && scope !== 'prepush' && baseRef !== undefined) {
169+
throw createFailError('--base-ref can only be used when --scope is branch or prepush.');
155170
}
156-
if (scope !== 'branch' && headRef !== undefined) {
157-
throw createFailError('--head-ref can only be used when --scope is branch.');
171+
if (scope !== 'branch' && scope !== 'prepush' && headRef !== undefined) {
172+
throw createFailError('--head-ref can only be used when --scope is branch or prepush.');
158173
}
159174
};
160175

src/platform/packages/shared/kbn-dev-validation-runner/src/resolve_validation_run_context.ts

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import { createFailError } from '@kbn/dev-cli-errors';
1111
import {
1212
countCommitsBetweenRefs,
13+
getUpstreamPushRef,
1314
hasStagedChanges,
1415
isShallowRepository,
1516
parseAndResolveValidationContract,
@@ -119,21 +120,31 @@ export const resolveValidationRunContext = async ({
119120
}
120121
}
121122

122-
// Resolve base for branch scope (for logging, reproduction, and Moon queries).
123+
// Resolve base for branch/prepush scope (for logging, reproduction, and Moon queries).
123124
let resolvedBase: MoonAffectedBase | undefined;
124-
if (contract.scope === 'branch') {
125+
if (contract.scope === 'branch' || contract.scope === 'prepush') {
125126
try {
126127
const normalizedBaseRef = contract.baseRef?.trim() || undefined;
127128
if (normalizedBaseRef) {
128129
resolvedBase = { base: normalizedBaseRef, baseRef: normalizedBaseRef };
130+
} else if (contract.scope === 'prepush') {
131+
const upstreamRef = await getUpstreamPushRef();
132+
if (upstreamRef) {
133+
resolvedBase = upstreamRef;
134+
} else {
135+
// No tracking branch configured; fall back to merge-base with remote default.
136+
resolvedBase = await resolveMoonAffectedBase({
137+
headRef: contract.headRef?.trim() || 'HEAD',
138+
});
139+
}
129140
} else {
130141
resolvedBase = await resolveMoonAffectedBase({
131142
headRef: contract.headRef?.trim() || 'HEAD',
132143
});
133144
}
134145
} catch (error) {
135146
onWarning?.(
136-
`Failed to resolve merge-base for affected ${runnerDescription} (${getErrorMessage(
147+
`Failed to resolve base for affected ${runnerDescription} (${getErrorMessage(
137148
error
138149
)}). Falling back to full ${runnerDescription}.`
139150
);
@@ -146,19 +157,25 @@ export const resolveValidationRunContext = async ({
146157
}
147158

148159
let branchCommitCount: number | undefined;
149-
if (contract.scope === 'branch' && resolvedBase) {
160+
if ((contract.scope === 'branch' || contract.scope === 'prepush') && resolvedBase) {
150161
try {
151162
branchCommitCount = await countCommitsBetweenRefs({
152163
base: resolvedBase.base,
153164
head: contract.headRef?.trim() || 'HEAD',
154165
});
155166
} catch {
156-
// Branch change count is a logging enhancement only.
167+
// Commit count is a logging enhancement only.
157168
}
158169
}
159170

171+
// prepush resolves via the upstream tracking ref but uses the same Moon query as branch.
172+
const moonScope =
173+
contract.scope === 'prepush'
174+
? 'branch'
175+
: (contract.scope as Exclude<typeof contract.scope, 'full' | 'prepush'>);
176+
160177
const changedFiles = await getMoonChangedFiles({
161-
scope: contract.scope as Exclude<typeof contract.scope, 'full'>,
178+
scope: moonScope,
162179
base: resolvedBase?.base,
163180
head: contract.headRef?.trim() || undefined,
164181
});

src/platform/packages/shared/kbn-dev-validation-runner/src/run_validation_command.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export const describeValidationScope = (baseContext: ValidationBaseContext): str
6666
`head=HEAD`
6767
);
6868

69-
if (contract.scope === 'branch') {
69+
if (contract.scope === 'branch' || contract.scope === 'prepush') {
7070
parts.push(`commits=${runContext.branchCommitCount ?? 'unknown'}`);
7171
}
7272
} else if (runContext.kind === 'full' && runContext.reason) {
@@ -83,7 +83,11 @@ export const describeValidationNoTargetsScope = (baseContext: ValidationBaseCont
8383
}
8484

8585
const { contract, runContext } = baseContext;
86-
if (contract.scope === 'branch' && runContext.kind === 'affected' && runContext.resolvedBase) {
86+
if (
87+
(contract.scope === 'branch' || contract.scope === 'prepush') &&
88+
runContext.kind === 'affected' &&
89+
runContext.resolvedBase
90+
) {
8791
return `between ${runContext.resolvedBase.baseRef} (${formatShortRevision(
8892
runContext.resolvedBase.base
8993
)}) and HEAD`;
@@ -97,6 +101,10 @@ export const describeValidationNoTargetsScope = (baseContext: ValidationBaseCont
97101
return 'in local changes';
98102
}
99103

104+
if (contract.scope === 'prepush') {
105+
return 'in unpushed commits';
106+
}
107+
100108
return 'for the selected validation scope';
101109
};
102110

@@ -122,7 +130,11 @@ export const describeValidationScoping = ({
122130
const { contract, runContext } = baseContext;
123131
const contractSummary = `scope=${contract.scope}, test-mode=${contract.testMode}, downstream=${contract.downstream}`;
124132

125-
if (runContext.kind === 'affected' && runContext.resolvedBase && contract.scope === 'branch') {
133+
if (
134+
runContext.kind === 'affected' &&
135+
runContext.resolvedBase &&
136+
(contract.scope === 'branch' || contract.scope === 'prepush')
137+
) {
126138
const changeSummary =
127139
runContext.branchCommitCount === undefined
128140
? 'commits'
@@ -140,6 +152,10 @@ export const describeValidationScoping = ({
140152
return `Checking ${targetSummary} affected by local changes (${contractSummary}).`;
141153
}
142154

155+
if (runContext.kind === 'affected' && contract.scope === 'prepush') {
156+
return `Checking ${targetSummary} affected by unpushed commits (${contractSummary}).`;
157+
}
158+
143159
return `Checking ${targetSummary} (${contractSummary}).`;
144160
};
145161

src/platform/packages/shared/kbn-dev-validation-runner/src/validation_run_cli.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,10 @@ const VALIDATION_RUN_FLAGS = new Set(VALIDATION_RUN_STRING_FLAGS.map((flag) => `
2828
export const VALIDATION_RUN_HELP: FlagHelpItem[] = [
2929
{
3030
flag: '--profile [name]',
31-
description: 'Validation profile: precommit, quick, agent, branch, pr, full (default: branch)',
31+
description:
32+
'Validation profile: precommit, quick, agent, branch, pr, prepush, full (default: branch)',
3233
},
33-
{ flag: '--scope [scope]', description: 'Scope: staged, local, branch, full' },
34+
{ flag: '--scope [scope]', description: 'Scope: staged, local, branch, prepush, full' },
3435
{ flag: '--test-mode [mode]', description: 'Test selection mode: related, affected, all' },
3536
{ flag: '--base-ref [git-ref]', description: 'Base revision for branch scope' },
3637
{ flag: '--head-ref [git-ref]', description: 'Head revision for branch scope (default: HEAD)' },
@@ -128,7 +129,7 @@ export const buildValidationCliArgs = ({
128129
reproductionArgs.push('--downstream', contract.downstream);
129130
}
130131

131-
if (contract.scope === 'branch' && resolvedBase) {
132+
if ((contract.scope === 'branch' || contract.scope === 'prepush') && resolvedBase) {
132133
if (resolvedBase.baseRef === 'GITHUB_PR_MERGE_BASE') {
133134
logArgs.push('--base-ref', '$GITHUB_PR_MERGE_BASE');
134135
} else {

0 commit comments

Comments
 (0)