From c0b8052a7802d392353f33206b7032646757a00d Mon Sep 17 00:00:00 2001 From: hp-himani <101387465+hp-himani@users.noreply.github.com.> Date: Thu, 1 Oct 2026 16:44:05 +0530 Subject: [PATCH] fix(publish): explain why the clean repo check fails --- lib/commands/publish.js | 25 ++++++++++++++++++-- test/command-publish.test.js | 42 +++++++++++++++++++++++++++++++++- test/proxy/simple-git-proxy.js | 4 ++-- 3 files changed, 66 insertions(+), 5 deletions(-) diff --git a/lib/commands/publish.js b/lib/commands/publish.js index ca6723d..96ec763 100644 --- a/lib/commands/publish.js +++ b/lib/commands/publish.js @@ -1,6 +1,6 @@ 'use strict' -const assert = require('node:assert').strict +const { isDeepStrictEqual } = require('node:util') const pino = require('pino') const { validate } = require('../validation') const draft = require('./draft') @@ -86,7 +86,28 @@ module.exports = async function (args) { delete compare.current // the current branch could be named in a different way CLEAN_REPO.tracking = `${args.remote}/${args.branch}` // your local branch must track where you will push logger.debug('Complete repo status %s', inlineNotEmpty(status)) - assert.deepStrictEqual(buildGitStatus(compare), CLEAN_REPO, 'The git repo must be clean (committed and pushed) before releasing!') + + if (!compare.tracking) { + throw new Error(`Your local branch doesn't track any remote branch, but releasify expects "${CLEAN_REPO.tracking}". +Push it and set its upstream (for example: git push -u ${args.remote} ${args.branch}), or switch to the branch you want to release.`) + } + + if (compare.tracking !== CLEAN_REPO.tracking) { + throw new Error(`Your local branch tracks "${compare.tracking}", but releasify expects "${CLEAN_REPO.tracking}". + Either switch to the branch you want to release, or tell releasify which one to use: + - for this run only: add --branch (and --remote if needed) + - as your new default: run "releasify config --arg branch"`) + } + + const gitStatus = buildGitStatus(compare) + const notClean = Object.keys(CLEAN_REPO) + .filter(key => !isDeepStrictEqual(gitStatus[key], CLEAN_REPO[key])) + .map(key => `- ${key}: ${JSON.stringify(gitStatus[key])}`) + if (notClean.length > 0) { + throw new Error(`The git repo must be clean (committed and pushed) before releasing! +${notClean.join('\n')} +Check "git status", then commit, push or pull before trying again.`) + } logger.debug('Building draft release..') const releasing = await draft(args) diff --git a/test/command-publish.test.js b/test/command-publish.test.js index 8dbb6c1..b0c3516 100644 --- a/test/command-publish.test.js +++ b/test/command-publish.test.js @@ -63,7 +63,7 @@ test('mandatory options', (t) => { }) test('try to publish a repo not sync', async (t) => { - t.plan(1) + t.plan(2) const cmd = h.buildProxyCommand('../lib/commands/publish', { git: { status: { dirty: true } } }) @@ -81,6 +81,46 @@ test('try to publish a repo not sync', async (t) => { 'The git repo must be clean (committed and pushed) before releasing!' ) ) + t.assert.match(error.message, /- modified: \["dirty-status\.js"\]/) + } +}) + +test('try to publish a repo tracked not to main branch', async (t) => { + t.plan(3) + const cmd = h.buildProxyCommand('../lib/commands/publish', { + git: { status: { tracking: 'origin/develop' } } + }) + const opts = buildOptions() + opts.semver = 'patch' + opts.ghToken = '0000000000000000000000000000000000000000' + delete opts.tag + + try { + await cmd(opts) + t.assert.fail('should not succeed') + } catch (error) { + t.assert.match(error.message, /origin\/main/) + t.assert.match(error.message, /origin\/develop/) + t.assert.match(error.message, /--branch/) + } +}) + +test('try to publish a branch that does not track any remote branch', async (t) => { + t.plan(2) + const cmd = h.buildProxyCommand('../lib/commands/publish', { + git: { status: { tracking: null } } + }) + const opts = buildOptions() + opts.semver = 'patch' + opts.ghToken = '0000000000000000000000000000000000000000' + delete opts.tag + + try { + await cmd(opts) + t.assert.fail('should not succeed') + } catch (error) { + t.assert.match(error.message, /doesn't track any remote branch/) + t.assert.match(error.message, /git push -u origin main/) } }) diff --git a/test/proxy/simple-git-proxy.js b/test/proxy/simple-git-proxy.js index d5e9ef9..c37dd57 100644 --- a/test/proxy/simple-git-proxy.js +++ b/test/proxy/simple-git-proxy.js @@ -14,7 +14,7 @@ function shouldThrows (cmd = {}, params) { * { * tag: {inputChecker: func, throwError: true, history: number of tag commit list} * log: {inputChecker: func, throwError: true, messages: [array of messages to use]} - * status: {throwError: true, dirty: true, tracking: 'origin/main'} + * status: {throwError: true, dirty: true, tracking: 'origin/main' (null = no upstream branch)} * add: {inputChecker: func, throwError: true} * commit: {inputChecker: func, throwError: true, branch: 'main'} * push: {inputChecker: func, throwError: true} @@ -71,7 +71,7 @@ module.exports = function factory (opts = {}) { modified.push('dirty-status.js') } - const tracking = (opts.status && opts.status.tracking) ? opts.status.tracking : 'origin/main' + const tracking = (opts.status && opts.status.tracking !== undefined) ? opts.status.tracking : 'origin/main' cb(null, { not_added: [],