Skip to content
Merged
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
11 changes: 2 additions & 9 deletions src/commands/prepare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import {
handleGlobalError,
reportError,
} from '../utils/errors';
import { getGitClient, getDefaultBranch, getLatestTag } from '../utils/git';
import { getGitClient, getDefaultBranch, getLatestTag, isRepoDirty } from '../utils/git';
import {
getChangelogWithBumpType,
calculateNextVersion,
Expand Down Expand Up @@ -335,14 +335,7 @@ function checkGitStatus(repoStatus: StatusResult, rev: string) {

logger.debug('Repository status:', formatJson(repoStatus));

if (
repoStatus.conflicted.length ||
repoStatus.created.length ||
repoStatus.deleted.length ||
repoStatus.modified.length ||
repoStatus.renamed.length ||
repoStatus.staged.length
) {
if (isRepoDirty(repoStatus)) {
reportError(
'Your repository is in a dirty state. ' +
'Please stash or commit the pending changes.',
Expand Down
23 changes: 22 additions & 1 deletion src/commands/publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import { isValidVersion } from '../utils/version';
import { BaseStatusProvider } from '../status_providers/base';
import { BaseArtifactProvider } from '../artifact_providers/base';
import { SimpleGit } from 'simple-git';
import { getGitClient, getDefaultBranch } from '../utils/git';
import { getGitClient, getDefaultBranch, isRepoDirty } from '../utils/git';

/** Default path to post-release script, relative to project root */
const DEFAULT_POST_RELEASE_SCRIPT_PATH = join('scripts', 'post-release.sh');
Expand Down Expand Up @@ -118,6 +118,11 @@ export const builder: CommandBuilder = (yargs: Argv) => {
description: 'Do not check for build status',
type: 'boolean',
})
.option('no-git-checks', {
default: false,
description: 'Ignore local git changes and unsynchronized remotes',
type: 'boolean',
})
.check(checkVersion)
.demandOption('new-version', 'Please specify the version to publish');
};
Expand All @@ -142,6 +147,8 @@ export interface PublishOptions {
noStatusCheck: boolean;
/** Do not remove release branch after publishing */
keepBranch: boolean;
/** Do not perform basic git checks */
noGitChecks: boolean;
}

export interface PublishState {
Expand Down Expand Up @@ -453,6 +460,20 @@ export async function publishMain(argv: PublishOptions): Promise<any> {

const git = await getGitClient();

// Check for dirty repository state before any git operations
if (argv.noGitChecks) {
logger.info('Not checking the status of the local repository');
} else {
const repoStatus = await git.status();
if (isRepoDirty(repoStatus)) {
reportError(
'Your repository is in a dirty state. ' +
'Please stash or commit the pending changes.',
logger
);
}
}

const rev = argv.rev;
let checkoutTarget;
let branchName;
Expand Down
64 changes: 63 additions & 1 deletion src/utils/__tests__/git.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { vi, type Mock, type MockInstance, type Mocked, type MockedFunction } from 'vitest';
import { getLatestTag } from '../git';
import { getLatestTag, isRepoDirty } from '../git';
import * as loggerModule from '../../logger';
import type { StatusResult } from 'simple-git';

describe('getLatestTag', () => {
it('returns latest tag in the repo by calling `git describe`', async () => {
Expand All @@ -26,3 +27,64 @@ describe('getLatestTag', () => {
expect(latestTag).toBe('');
});
});

describe('isRepoDirty', () => {
const createCleanStatus = (): StatusResult => ({
not_added: [],
conflicted: [],
created: [],
deleted: [],
ignored: [],
modified: [],
renamed: [],
staged: [],
files: [],
ahead: 0,
behind: 0,
current: 'main',
tracking: 'origin/main',
detached: false,
isClean: () => true,
});

it('returns false for clean repository', () => {
const status = createCleanStatus();
expect(isRepoDirty(status)).toBe(false);
});

it('returns true when there are modified files', () => {
const status = createCleanStatus();
status.modified = ['file.txt'];
expect(isRepoDirty(status)).toBe(true);
});

it('returns true when there are created files', () => {
const status = createCleanStatus();
status.created = ['newfile.txt'];
expect(isRepoDirty(status)).toBe(true);
});

it('returns true when there are deleted files', () => {
const status = createCleanStatus();
status.deleted = ['removed.txt'];
expect(isRepoDirty(status)).toBe(true);
});

it('returns true when there are staged files', () => {
const status = createCleanStatus();
status.staged = ['staged.txt'];
expect(isRepoDirty(status)).toBe(true);
});

it('returns true when there are renamed files', () => {
const status = createCleanStatus();
status.renamed = [{ from: 'old.txt', to: 'new.txt' }];
expect(isRepoDirty(status)).toBe(true);
});

it('returns true when there are conflicted files', () => {
const status = createCleanStatus();
status.conflicted = ['conflict.txt'];
expect(isRepoDirty(status)).toBe(true);
});
});
19 changes: 18 additions & 1 deletion src/utils/git.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import simpleGit, { type SimpleGit, type LogOptions, type Options } from 'simple-git';
import simpleGit, { type SimpleGit, type LogOptions, type Options, type StatusResult } from 'simple-git';

import { getConfigFileDir } from '../config';
import { ConfigurationError } from './errors';
Expand Down Expand Up @@ -111,3 +111,20 @@ export async function getGitClient(): Promise<SimpleGit> {
}
return git;
}

/**
* Checks if the git repository has uncommitted changes
*
* @param repoStatus Result of git.status()
* @returns true if the repository has uncommitted changes
*/
export function isRepoDirty(repoStatus: StatusResult): boolean {
return !!(
repoStatus.conflicted.length ||
repoStatus.created.length ||
repoStatus.deleted.length ||
repoStatus.modified.length ||
repoStatus.renamed.length ||
repoStatus.staged.length
);
}
Loading