diff --git a/packages/api-client/src/schema.ts b/packages/api-client/src/schema.ts index bae7d97..626e287 100644 --- a/packages/api-client/src/schema.ts +++ b/packages/api-client/src/schema.ts @@ -235,6 +235,7 @@ export interface operations { prHeadCommit?: string | null; referenceCommit?: string | null; referenceBranch?: string | null; + parentCommits?: components["schemas"]["Sha1Hash"][] | null; /** @enum {string|null} */ mode?: "ci" | "monitoring" | null; ciProvider?: string | null; diff --git a/packages/core/src/ci-environment/git.ts b/packages/core/src/ci-environment/git.ts index 07c2955..f08fe81 100644 --- a/packages/core/src/ci-environment/git.ts +++ b/packages/core/src/ci-environment/git.ts @@ -69,7 +69,7 @@ export function getMergeBaseCommitSha(input: { base: string; head: string; }): string | null { - let depth = 50; + let depth = 200; while (depth < 1000) { const mergeBase = getMergeBaseCommitShaWithDepth({ depth, @@ -78,7 +78,18 @@ export function getMergeBaseCommitSha(input: { if (mergeBase) { return mergeBase; } - depth += 50; + depth += 200; } return null; } + +export function listParentCommits(input: { sha: string }): string[] | null { + try { + execSync(`git fetch --depth=200 origin ${input.sha}`); + const raw = execSync(`git log --format="%H" --max-count=200 ${input.sha}`); + const shas = raw.toString().trim().split("\n"); + return shas; + } catch { + return null; + } +} diff --git a/packages/core/src/ci-environment/index.ts b/packages/core/src/ci-environment/index.ts index 23ffaa5..d3b9d24 100644 --- a/packages/core/src/ci-environment/index.ts +++ b/packages/core/src/ci-environment/index.ts @@ -53,6 +53,17 @@ export function getMergeBaseCommitSha(input: { return service.getMergeBaseCommitSha(input, context); } +/** + * Get the merge base commit. + */ +export function listParentCommits(input: { sha: string }): string[] | null { + const context = createContext(); + const service = getCiService(context); + if (!service) { + return null; + } + return service.listParentCommits(input, context); +} /** * Get the CI environment. */ diff --git a/packages/core/src/ci-environment/services/bitrise.ts b/packages/core/src/ci-environment/services/bitrise.ts index d6acd7d..eccd02b 100644 --- a/packages/core/src/ci-environment/services/bitrise.ts +++ b/packages/core/src/ci-environment/services/bitrise.ts @@ -1,4 +1,4 @@ -import { getMergeBaseCommitSha } from "../git"; +import { getMergeBaseCommitSha, listParentCommits } from "../git"; import type { Service, Context } from "../types"; const getPrNumber = ({ env }: Context) => { @@ -20,10 +20,12 @@ const service: Service = { runAttempt: null, prNumber: getPrNumber({ env }), prHeadCommit: null, + prBaseBranch: null, nonce: env.BITRISEIO_PIPELINE_ID || null, }; }, getMergeBaseCommitSha, + listParentCommits, }; export default service; diff --git a/packages/core/src/ci-environment/services/buildkite.ts b/packages/core/src/ci-environment/services/buildkite.ts index bb553f1..f487328 100644 --- a/packages/core/src/ci-environment/services/buildkite.ts +++ b/packages/core/src/ci-environment/services/buildkite.ts @@ -1,5 +1,5 @@ import type { Service } from "../types"; -import { head, branch, getMergeBaseCommitSha } from "../git"; +import { head, branch, getMergeBaseCommitSha, listParentCommits } from "../git"; const service: Service = { name: "Buildkite", @@ -19,10 +19,12 @@ const service: Service = { ? Number(env.BUILDKITE_PULL_REQUEST) : null, prHeadCommit: null, + prBaseBranch: null, nonce: env.BUILDKITE_BUILD_ID || null, }; }, getMergeBaseCommitSha, + listParentCommits, }; export default service; diff --git a/packages/core/src/ci-environment/services/circleci.ts b/packages/core/src/ci-environment/services/circleci.ts index 1ef7ccb..37b8cf4 100644 --- a/packages/core/src/ci-environment/services/circleci.ts +++ b/packages/core/src/ci-environment/services/circleci.ts @@ -1,4 +1,4 @@ -import { getMergeBaseCommitSha } from "../git"; +import { getMergeBaseCommitSha, listParentCommits } from "../git"; import type { Service, Context } from "../types"; const getPrNumber = ({ env }: Context) => { @@ -26,10 +26,12 @@ const service: Service = { runAttempt: null, prNumber: getPrNumber({ env }), prHeadCommit: null, + prBaseBranch: null, nonce: env.CIRCLE_WORKFLOW_ID || env.CIRCLE_BUILD_NUM || null, }; }, getMergeBaseCommitSha, + listParentCommits, }; export default service; diff --git a/packages/core/src/ci-environment/services/git.ts b/packages/core/src/ci-environment/services/git.ts index 6d77ed3..e305f53 100644 --- a/packages/core/src/ci-environment/services/git.ts +++ b/packages/core/src/ci-environment/services/git.ts @@ -4,6 +4,7 @@ import { branch, checkIsGitRepository, getMergeBaseCommitSha, + listParentCommits, } from "../git"; const service: Service = { @@ -21,10 +22,12 @@ const service: Service = { runAttempt: null, prNumber: null, prHeadCommit: null, + prBaseBranch: null, nonce: null, }; }, getMergeBaseCommitSha, + listParentCommits, }; export default service; diff --git a/packages/core/src/ci-environment/services/github-actions.ts b/packages/core/src/ci-environment/services/github-actions.ts index 5169d1c..3596ec1 100644 --- a/packages/core/src/ci-environment/services/github-actions.ts +++ b/packages/core/src/ci-environment/services/github-actions.ts @@ -2,7 +2,7 @@ import { existsSync, readFileSync } from "node:fs"; import type { Service, Context } from "../types"; import axios from "axios"; import { debug } from "../../debug"; -import { getMergeBaseCommitSha } from "../git"; +import { getMergeBaseCommitSha, listParentCommits } from "../git"; type EventPayload = { pull_request?: { @@ -10,6 +10,10 @@ type EventPayload = { sha: string; ref: string; }; + base: { + sha: string; + ref: string; + }; number: number; }; deployment?: { @@ -159,6 +163,7 @@ const service: Service = { branch: pullRequest?.head.ref || payload.deployment.environment || null, prNumber: pullRequest?.number || null, prHeadCommit: pullRequest?.head.sha || null, + prBaseBranch: null, }; } @@ -168,9 +173,11 @@ const service: Service = { payload?.pull_request?.head.ref || getBranch(context, payload) || null, prNumber: payload?.pull_request?.number || null, prHeadCommit: payload?.pull_request?.head.sha ?? null, + prBaseBranch: payload?.pull_request?.base.ref ?? null, }; }, getMergeBaseCommitSha, + listParentCommits, }; export default service; diff --git a/packages/core/src/ci-environment/services/gitlab.ts b/packages/core/src/ci-environment/services/gitlab.ts index 51a04fe..a06fb4d 100644 --- a/packages/core/src/ci-environment/services/gitlab.ts +++ b/packages/core/src/ci-environment/services/gitlab.ts @@ -1,4 +1,4 @@ -import { getMergeBaseCommitSha } from "../git"; +import { getMergeBaseCommitSha, listParentCommits } from "../git"; import type { Service } from "../types"; const service: Service = { @@ -16,10 +16,12 @@ const service: Service = { runAttempt: null, prNumber: null, prHeadCommit: null, + prBaseBranch: null, nonce: env.CI_PIPELINE_ID || null, }; }, getMergeBaseCommitSha, + listParentCommits, }; export default service; diff --git a/packages/core/src/ci-environment/services/heroku.ts b/packages/core/src/ci-environment/services/heroku.ts index c98cd3b..3b21208 100644 --- a/packages/core/src/ci-environment/services/heroku.ts +++ b/packages/core/src/ci-environment/services/heroku.ts @@ -1,5 +1,5 @@ import type { Service } from "../types"; -import { getMergeBaseCommitSha } from "../git"; +import { getMergeBaseCommitSha, listParentCommits } from "../git"; const service: Service = { name: "Heroku", @@ -15,9 +15,11 @@ const service: Service = { runAttempt: null, prNumber: null, prHeadCommit: null, + prBaseBranch: null, nonce: env.HEROKU_TEST_RUN_ID || null, }), getMergeBaseCommitSha, + listParentCommits, }; export default service; diff --git a/packages/core/src/ci-environment/services/travis.ts b/packages/core/src/ci-environment/services/travis.ts index 798a91d..fe767b3 100644 --- a/packages/core/src/ci-environment/services/travis.ts +++ b/packages/core/src/ci-environment/services/travis.ts @@ -1,5 +1,5 @@ import type { Context, Service } from "../types"; -import { getMergeBaseCommitSha } from "../git"; +import { getMergeBaseCommitSha, listParentCommits } from "../git"; const getOwner = ({ env }: Context) => { if (!env.TRAVIS_REPO_SLUG) return null; @@ -33,10 +33,12 @@ const service: Service = { runAttempt: null, prNumber: getPrNumber(ctx), prHeadCommit: null, + prBaseBranch: null, nonce: env.TRAVIS_BUILD_ID || null, }; }, getMergeBaseCommitSha, + listParentCommits, }; export default service; diff --git a/packages/core/src/ci-environment/types.ts b/packages/core/src/ci-environment/types.ts index a630d0e..bae67a3 100644 --- a/packages/core/src/ci-environment/types.ts +++ b/packages/core/src/ci-environment/types.ts @@ -62,6 +62,11 @@ export interface CiEnvironment { */ prHeadCommit: string | null; + /** + * The branch name that the pull request is targeting. + */ + prBaseBranch: string | null; + /** * A unique string for each run of a particular workflow in a repository. */ @@ -82,4 +87,10 @@ export interface Service { }, ctx: Context, ): string | null; + listParentCommits( + input: { + sha: string; + }, + ctx: Context, + ): string[] | null; } diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index 8879afd..a5ad43c 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -85,6 +85,12 @@ const schema = { default: null, nullable: true, }, + prBaseBranch: { + env: "ARGOS_PR_BASE_BRANCH", + format: String, + default: null, + nullable: true, + }, parallel: { env: "ARGOS_PARALLEL", default: false, @@ -177,6 +183,7 @@ export interface Config { runAttempt: number | null; prNumber: number | null; prHeadCommit: string | null; + prBaseBranch: string | null; mode: "ci" | "monitoring" | null; ciProvider: string | null; threshold: number | null; @@ -202,6 +209,7 @@ export async function readConfig(options: Partial = {}) { prNumber: options.prNumber || config.get("prNumber") || ciEnv?.prNumber || null, prHeadCommit: config.get("prHeadCommit") || ciEnv?.prHeadCommit || null, + prBaseBranch: config.get("prBaseBranch") || ciEnv?.prBaseBranch || null, referenceBranch: options.referenceBranch || config.get("referenceBranch") || null, referenceCommit: diff --git a/packages/core/src/upload.ts b/packages/core/src/upload.ts index eb247ef..18c77a2 100644 --- a/packages/core/src/upload.ts +++ b/packages/core/src/upload.ts @@ -13,7 +13,7 @@ import { debug, debugTime, debugTimeEnd } from "./debug"; import { chunk } from "./util/chunk"; import { getPlaywrightTracePath, readMetadata } from "@argos-ci/util"; import { getArgosCoreSDKIdentifier } from "./version"; -import { getMergeBaseCommitSha } from "./ci-environment"; +import { getMergeBaseCommitSha, listParentCommits } from "./ci-environment"; /** * Size of the chunks used to upload screenshots to Argos. @@ -213,30 +213,56 @@ export async function upload(params: UploadParameters) { if (projectResponse.error) { throwAPIError(projectResponse.error); } + debug("Project fetched", projectResponse.data); + const { defaultBaseBranch, hasRemoteContentAccess } = projectResponse.data; - const referenceBranch = config.referenceBranch || defaultBaseBranch; + const referenceCommit = (() => { if (config.referenceCommit) { debug("Found reference commit in config", config.referenceCommit); return config.referenceCommit; } + // If we have remote access, we will fetch it from the Git Provider. if (hasRemoteContentAccess) { return null; } - const sha = getMergeBaseCommitSha({ - base: referenceBranch, - head: config.branch, - }); + // We use the pull request as base branch if possible + // else branch specified by the user or the default branch. + const base = + config.referenceBranch || config.prBaseBranch || defaultBaseBranch; + + const sha = getMergeBaseCommitSha({ base, head: config.branch }); + if (sha) { - debug("Found reference commit from git", sha); + debug("Found merge base", sha); } else { - debug("No reference commit found in git"); + debug("No merge base found"); } + return sha; })(); + const parentCommits = (() => { + // If we have remote access, we will fetch them from the Git Provider. + if (hasRemoteContentAccess) { + return null; + } + + if (referenceCommit) { + const commits = listParentCommits({ sha: referenceCommit }); + if (commits) { + debug("Found parent commits", commits); + } else { + debug("No parent commits found"); + } + return commits; + } + + return null; + })(); + // Create build debug("Creating build"); const [pwTraceKeys, screenshotKeys] = screenshots.reduce( @@ -267,8 +293,9 @@ export async function upload(params: UploadParameters) { pwTraceKeys, prNumber: config.prNumber, prHeadCommit: config.prHeadCommit, - referenceBranch, + referenceBranch: config.referenceBranch, referenceCommit, + parentCommits, argosSdk, ciProvider: config.ciProvider, runId: config.runId,