Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions packages/nx/src/command-line/release/changelog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,8 @@ export function createAPI(
preid: string | undefined,
checkAllBranchesWhen: CheckAllBranchesWhen,
requireSemver: boolean,
strictPreid: boolean
strictPreid: boolean,
projectRoot?: string
): Promise<string | null> => {
if (fromSHACache.has(cacheKey)) {
return fromSHACache.get(cacheKey);
Expand All @@ -386,6 +387,7 @@ export function createAPI(
requireSemver,
strictPreid,
useAutomaticFromRef,
projectRoot,
});
fromSHACache.set(cacheKey, sha);
return sha;
Expand Down Expand Up @@ -491,7 +493,8 @@ export function createAPI(
projectsPreid[project.name],
releaseGroup.releaseTag.checkAllBranchesWhen,
releaseGroup.releaseTag.requireSemver,
releaseGroup.releaseTag.strictPreid
releaseGroup.releaseTag.strictPreid,
project.data.root
);

let commits: GitCommit[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { execCommand } from '../utils/exec-command';
import {
getCommitHash,
getFirstGitCommit,
getFirstProjectCommit,
getLatestGitTagForPattern,
} from '../utils/git';
import type { VersionData } from '../utils/shared';
Expand Down Expand Up @@ -133,6 +134,7 @@ export async function resolveChangelogFromSHA({
strictPreid,
useAutomaticFromRef,
resolveRepositoryTags,
projectRoot,
}: {
fromRef?: string;
tagPattern: string;
Expand All @@ -143,6 +145,8 @@ export async function resolveChangelogFromSHA({
strictPreid: boolean;
useAutomaticFromRef: boolean;
resolveRepositoryTags: RepoGitTags['resolveTags'];
/** When provided, scopes the fallback to the project's first commit instead of the repo's first commit */
projectRoot?: string;
}): Promise<string | null> {
// If user provided a from ref, resolve it to a SHA
if (fromRef) {
Expand All @@ -163,9 +167,13 @@ export async function resolveChangelogFromSHA({
if (latestTag?.tag) {
return await getCommitHash(latestTag.tag);
}
// Finally, if automatic from ref is enabled, use the first commit as a fallback
// Finally, if automatic from ref is enabled, use the first commit as a fallback.
// When a projectRoot is provided, scope the fallback to the project's first commit
// to avoid scanning the entire repo history for projects added after the repo was created.
if (useAutomaticFromRef) {
return await getFirstGitCommit();
return projectRoot
? await getFirstProjectCommit(projectRoot)
: await getFirstGitCommit();
}

return null;
Expand Down
38 changes: 38 additions & 0 deletions packages/nx/src/command-line/release/utils/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -732,6 +732,44 @@ export async function getFirstGitCommit() {
}
}

/**
* Returns the parent of the first commit that touched the given project root,
* so that `from..HEAD` ranges include the project's creation commit.
* Falls back to getFirstGitCommit() if the project history cannot be determined.
*/
export async function getFirstProjectCommit(
projectRoot: string
): Promise<string> {
try {
const result = (
await execCommand('git', [
'rev-list',
'--reverse',
'HEAD',
'--first-parent',
'--',
`${projectRoot}/package.json`,
])
).trim();
const firstCommit = result.split('\n')[0];

if (firstCommit) {
// Return the parent so the creation commit is included in from..to ranges
try {
return (
await execCommand('git', ['rev-parse', `${firstCommit}~1`])
).trim();
} catch {
// No parent (project was added in the repo's very first commit)
return firstCommit;
}
}
} catch {
// fall through to fallback
}
return getFirstGitCommit();
}

async function getGitRoot() {
try {
return (await execCommand('git', ['rev-parse', '--show-toplevel'])).trim();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import type {
} from '../../../config/project-graph';
import { NxReleaseConfig } from '../config/config';
import { ReleaseGroupWithName } from '../config/filter-release-groups';
import { getFirstGitCommit, getLatestGitTagForPattern } from '../utils/git';
import {
getFirstGitCommit,
getFirstProjectCommit,
getLatestGitTagForPattern,
} from '../utils/git';
import { ReleaseGraph } from '../utils/release-graph';
import { resolveSemverSpecifierFromConventionalCommits } from '../utils/resolve-semver-specifier';
import { SemverSpecifier, SemverSpecifierType } from '../utils/semver';
Expand Down Expand Up @@ -36,11 +40,12 @@ export async function deriveSpecifierFromConventionalCommits(
: releaseGroup.projects;

// latestMatchingGitTag will be undefined if the current version was resolved from the disk fallback.
// In this case, we want to use the first commit as the ref to be consistent with the changelog command.
// In this case, use the first commit that touched this project rather than the repo's first commit,
// to avoid scanning the entire git history for projects that were added after the repo was created.
const previousVersionRef = latestMatchingGitTag
? latestMatchingGitTag.tag
: fallbackCurrentVersionResolver === 'disk'
? await getFirstGitCommit()
? await getFirstProjectCommit(projectGraphNode.data.root)
: undefined;

if (!previousVersionRef) {
Expand Down