diff --git a/action.js b/action.js index 358ca56..5211c1c 100644 --- a/action.js +++ b/action.js @@ -35,8 +35,10 @@ const inputs = { } let fetchedHistory = [] +let fetchedTags = [] const Scheme = { + Absolute: 'absolute', Continuous: 'continuous', Semantic: 'semantic', } @@ -86,27 +88,51 @@ async function existingTags() { } }` ).then((result) => { - return result.repository.refs.nodes + fetchedTags = result.repository.refs.nodes + return fetchedTags }) .catch((e) => { core.setFailed(`Failed to fetch tags: ${e}`) }) } +async function fetchWorkflowCommitDate() { + return await octokit.graphql( + `{ + repository(owner: "${owner}", name: "${repo}") { + commit: object(oid: "${process.env['GITHUB_SHA']}") { + ... on Commit { + sha: oid + committedDate + } + } + } + }` + ).then((data) => data.repository.commit.committedDate) +} + async function latestTagForBranch(allTags, branch) { core.info( `Fetching commits for ref ${branch}. This may take a while on large repositories.` ) const query = - `query commits($cursor: String) { + `query commits($cursor: String, $until: GitTimestamp) { repository(owner: "${owner}", name: "${repo}") { branch: ref(qualifiedName: "${branch}") { head: target { ... on Commit { - history(first: 100, after: $cursor) { + history(first: 100, after: $cursor, until: $until) { commits: nodes { sha: oid + associatedPullRequests(first: 1) { + prs: nodes { + baseRefName + mergeCommit { + sha: oid + } + } + } } pageInfo { endCursor @@ -119,12 +145,14 @@ async function latestTagForBranch(allTags, branch) { } }` let cursor = null; + let until = (inputs.scheme == Scheme.Absolute) ? await fetchWorkflowCommitDate() : null; let latestTag let moreToFetch = true while (isNullString(latestTag) && moreToFetch) { let opts = { cursor: cursor, + until: until, } latestTag = await octokit.graphql(query, opts) @@ -203,6 +231,47 @@ function determinePrereleaseName(semTag) { } } +// increments version according to the number of merge commits between +// this one and the most recent tag in history. +// assumes repo merge strategy that generates merge commits, +// and that only merge commits will receive tags. +// also assumes continuous versioning. +function computeNextAbsolute(semTag, lastTag) { + core.info(JSON.stringify(fetchedTags)) + core.info(`last tag: ${lastTag}`) + const tag = fetchedTags.find((tag) => { + core.info(JSON.stringify(tag)) + return tag.ref === lastTag + }) + core.info(`tag ${JSON.stringify(tag)}`) + const tagSha = tag.object.sha + const tagIndex = fetchedHistory.findIndex((commit) => commit.sha === tagSha) + // we only want merge commits between now and the most recent tag + const relevantHistory = fetchedHistory.slice(0, tagIndex).filter((commit) => { + const pr = commit.associatedPullRequests.prs[0] + + // filters non-merge commits + if (!pr || !pr.mergeCommit || pr.mergeCommit.sha != commit.sha) { + return false + } + + // ensures merge was into this branch + // and not into a branch that was then merged into this one + if (pr.baseRefName != inputs.branch) { + return false + } + + return true + }) + core.info(JSON.stringify(relevantHistory)) + + let nextTag = semTag + for (const i = relevantHistory.length; i > 0; i++) { + nextTag = computeNextContinuous(nextTag) + } + return semver.parse(nextTag) +} + function computeNextContinuous(semTag) { const bumpType = determineContinuousBumpType(semTag) const preName = determinePrereleaseName(semTag) @@ -300,6 +369,8 @@ async function computeNextTag() { } switch (inputs.scheme) { + case 'absolute': + return computeNextAbsolute(semTag, lastTag) case 'continuous': return computeNextContinuous(semTag) case 'semantic':