Skip to content
This repository has been archived by the owner on Nov 4, 2024. It is now read-only.

Commit

Permalink
fix: ensures changelog files are up to date before adding entries (#610)
Browse files Browse the repository at this point in the history
  • Loading branch information
noahnu authored Mar 9, 2023
1 parent 6bd64d6 commit c5a3460
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 18 deletions.
7 changes: 7 additions & 0 deletions __mocks__/@monodeploy/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,13 @@ export const gitGlob = async (
return globs // TODO: not entirely accurate
}

export const gitCheckout = async (
{ files }: { files: string[] },
{ cwd, context }: { cwd: string; context?: YarnContext },
): Promise<void> => {
//
}

module.exports = {
__esModule: true,
_commitFiles_,
Expand Down
105 changes: 105 additions & 0 deletions e2e-tests/tests/issues/issue-601.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { RegistryMode } from '@monodeploy/types'

import setupProject from 'helpers/setupProject'

describe('Issue #601', () => {
it(
'handles merge conflicts',
setupProject({
repository: [
{
'pkg-1': {},
},
],
config: {
access: 'public',
changelogFilename: '<packageDir>/CHANGELOG.md',
dryRun: false,
autoCommit: true,
autoCommitMessage: 'chore: release',
conventionalChangelogConfig: require.resolve(
'@tophat/conventional-changelog-config',
),
git: {
push: true,
remote: 'origin',
tag: true,
},
jobs: 1,
persistVersions: false,
registryMode: RegistryMode.NPM,
topological: true,
topologicalDev: true,
maxConcurrentReads: 1,
maxConcurrentWrites: 1,
},
testCase: async ({ run, readFile, exec, writeFile }) => {
// First semantic commit
await writeFile('packages/pkg-1/README.md', 'Modification.')
await exec('git add . && git commit -n -m "feat: change 1" && git push')

const { error } = await run()
if (error) console.error(error)
expect(error).toBeUndefined()

// We should have a 'pkg-1/CHANGELOG.md' at this point.
expect(await readFile('packages/pkg-1/CHANGELOG.md')).toMatch(/change 1/)

await exec('git checkout -b change_2')
await writeFile('packages/pkg-1/README.md', 'Modification 2.')
await exec('git add . && git commit -n -m "feat: change 2"')

await exec('git checkout -b change_3') // change_3 is based on change_2
await writeFile('packages/pkg-1/README.md', 'Modification 3.')
await exec('git add . && git commit -n -m "feat: change 3"')

// Back on change 2, we'll publish
await exec('git checkout main')
await exec('git merge change_2 --no-verify --no-edit')

const { error: error2 } = await run()
if (error2) console.error(error2)
expect(error2).toBeUndefined()

// At this point we expected change 2 followed by change 1 in the changelog
let remoteChangelog = (
await exec('git cat-file blob origin/main:packages/pkg-1/CHANGELOG.md')
).stdout
expect(remoteChangelog).toMatch(/change 2.*change 1/s)

// Switch back to change_3 which is now "out of sync" with main
await exec('git checkout main')
await exec('git reset --hard change_3')

// If we attempt to publish, we'll have a conflict with the CHANGELOG.md file
// since our change_3 will only have change 1 and change 3 and will be missing change 2.
// We'll validate this:
remoteChangelog = (
await exec('git cat-file blob origin/main:packages/pkg-1/CHANGELOG.md')
).stdout
expect(remoteChangelog).toEqual(expect.stringContaining('change 1'))
expect(remoteChangelog).toEqual(expect.stringContaining('change 2'))
expect(remoteChangelog).not.toEqual(expect.stringContaining('change 3'))

// Now we publish. It's up to monodeploy to deal with or prevent conflicts.
const { error: error3 } = await run()
if (error3) console.error(error3)
expect(error3).toBeUndefined()

// If we get this far, monodeploy didn't fail due to the conflicts. We'll verify the changelog
// with ordering:
remoteChangelog = (
await exec('git cat-file blob origin/main:packages/pkg-1/CHANGELOG.md')
).stdout
expect(remoteChangelog).toMatch(/change 3.*change 2.*change 1/s)
expect((await (await exec('git describe --abbrev=0')).stdout).trim()).toBe(
'pkg-1@0.3.0',
)

// NOTE: the hard reset we do disassociates the git tag with the HEAD of main.
// This causes the change_3 monodeploy run to include changes 2 and 3. This is
// just a quirk of the test scenario.
},
}),
)
})
17 changes: 17 additions & 0 deletions packages/changelog/src/prependChangelogFile.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { promises as fs } from 'fs'
import path from 'path'

import { gitCheckout } from '@monodeploy/git'
import logging from '@monodeploy/logging'
import {
type ChangesetSchema,
Expand Down Expand Up @@ -75,14 +76,30 @@ const prependChangelogFile = async ({
context,
changeset,
workspaces,
forceRefreshChangelogs = false,
}: {
config: MonodeployConfiguration
context: YarnContext
changeset: ChangesetSchema
workspaces: Set<Workspace>
forceRefreshChangelogs?: boolean
}): Promise<void> => {
if (!config.changelogFilename) return

// Make sure the changelogs are up to date with the remote
if (!config.dryRun && forceRefreshChangelogs) {
const changelogGlob = config.changelogFilename.replace('<packageDir>', '**')
if (changelogGlob) {
try {
await gitCheckout({ files: [changelogGlob] }, { cwd: config.cwd, context })
} catch {
logging.debug('Force refreshing changelogs failed. Ignoring.', {
report: context.report,
})
}
}
}

if (config.changelogFilename.includes(TOKEN_PACKAGE_DIR)) {
const prependForWorkspace = async (workspace: Workspace): Promise<void> => {
const filename = npath.fromPortablePath(
Expand Down
17 changes: 17 additions & 0 deletions packages/git/src/gitCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,20 @@ const git = async (
return await exec(command, { cwd, env: { GIT_TERMINAL_PROMPT: '0', ...process.env } })
}

export const gitCheckout = async (
{ files }: { files: string[] },
{ cwd, context }: { cwd: string; context?: YarnContext },
): Promise<void> => {
const { stdout: branch } = await git('rev-parse --abbrev-ref --symbolic-full-name @\\{u\\}', {
cwd,
context,
})
await git(`checkout ${branch.trim()} -- ${files.map((f) => `"${f}"`).join(' ')}`, {
cwd,
context,
})
}

export const gitResolveSha = async (
ref: string,
{ cwd, context }: { cwd: string; context?: YarnContext },
Expand Down Expand Up @@ -84,15 +98,18 @@ export const gitPull = async ({
remote,
context,
autostash = false,
strategyOption,
}: {
cwd: string
remote: string
context?: YarnContext
autostash?: boolean
strategyOption?: 'theirs'
}): Promise<void> => {
assertProduction()
const args = ['--rebase', '--no-verify']
if (autostash) args.push('--autostash')
if (strategyOption) args.push(`--strategy-option=${strategyOption}`)
await git(`pull ${args.join(' ')} ${remote}`, {
cwd,
context,
Expand Down
23 changes: 14 additions & 9 deletions packages/node/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,15 +214,6 @@ const monodeploy = async (
resolve()
})

await report.startTimerPromise('Updating Changelog', { skipIfEmpty: false }, async () => {
await prependChangelogFile({
config,
context,
changeset,
workspaces,
})
})

try {
// Update package.jsons (the main destructive action which requires the backup)
await report.startTimerPromise(
Expand Down Expand Up @@ -270,6 +261,20 @@ const monodeploy = async (
let publishCommitSha: string | undefined
const restoredGitTags = getGitTagsFromChangeset(changeset)

await report.startTimerPromise(
'Updating Changelog',
{ skipIfEmpty: false },
async () => {
await prependChangelogFile({
config,
context,
changeset,
workspaces,
forceRefreshChangelogs: config.autoCommit && config.git.push,
})
},
)

await report.startTimerPromise(
'Committing Changes',
{ skipIfEmpty: true },
Expand Down
19 changes: 10 additions & 9 deletions packages/publish/src/commitPublishChanges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,21 +47,22 @@ export const createPublishCommit = async ({
}
}

if (config.git.tag && gitTags?.size) {
// Tag commit
await createReleaseGitTags({
config,
context,
gitTags,
})
}

if (config.git.push && config.autoCommit) {
await gitPull({
cwd: config.cwd,
remote: config.git.remote,
context,
autostash: true,
strategyOption: 'theirs',
})
}

if (config.git.tag && gitTags?.size) {
// Tag commit
await createReleaseGitTags({
config,
context,
gitTags,
})
}

Expand Down

0 comments on commit c5a3460

Please sign in to comment.