From 2b9b210c0b6ac69853ffb01f0dbac9109ab419c5 Mon Sep 17 00:00:00 2001 From: Daniel Stockman Date: Sun, 29 Dec 2019 13:49:00 -0800 Subject: [PATCH] fix(version): Support git clients that do not support `git push --atomic` Support for `--atomic` was added in 2015, but apparently 5 years isn't enough time for CI providers to upgrade their shit. --- commands/version/__tests__/git-push.test.js | 66 ++++++++++++++++++++- commands/version/lib/git-push.js | 18 +++++- 2 files changed, 82 insertions(+), 2 deletions(-) diff --git a/commands/version/__tests__/git-push.test.js b/commands/version/__tests__/git-push.test.js index 6f0793184d..e49323ff27 100644 --- a/commands/version/__tests__/git-push.test.js +++ b/commands/version/__tests__/git-push.test.js @@ -1,9 +1,22 @@ "use strict"; const execa = require("execa"); +const childProcess = require("@lerna/child-process"); const cloneFixture = require("@lerna-test/clone-fixture")(__dirname); const gitPush = require("../lib/git-push"); +async function listRemoteTags(cwd) { + return execa.stdout("git", ["ls-remote", "--tags", "--refs", "--quiet"], { cwd }); +} + +beforeEach(() => { + jest.spyOn(childProcess, "exec"); +}); + +afterEach(() => { + jest.restoreAllMocks(); +}); + test("gitPush", async () => { const { cwd } = await cloneFixture("root-manifest-only"); @@ -14,8 +27,59 @@ test("gitPush", async () => { await gitPush("origin", "master", { cwd }); - const list = await execa.stdout("git", ["ls-remote", "--tags", "--refs", "--quiet"], { cwd }); + expect(childProcess.exec).toHaveBeenLastCalledWith( + "git", + ["push", "--follow-tags", "--no-verify", "--atomic", "origin", "master"], + { cwd } + ); + + const list = await listRemoteTags(cwd); expect(list).toMatch("v1.2.3"); expect(list).toMatch("foo@2.3.1"); expect(list).toMatch("bar@3.2.1"); }); + +test("remote that does not support --atomic", async () => { + const { cwd } = await cloneFixture("root-manifest-only"); + + await execa("git", ["commit", "--allow-empty", "-m", "change"], { cwd }); + await execa("git", ["tag", "v4.5.6", "-m", "v4.5.6"], { cwd }); + + // the first time the command is executed, simulate remote error + childProcess.exec.mockImplementationOnce(async () => { + throw new Error( + [ + "Command failed: git push --follow-tags --atomic --no-verify origin master", + "fatal: the receiving end does not support --atomic push", + ].join("\n") + ); + }); + + // this call should _not_ throw + await gitPush("origin", "master", { cwd }); + + expect(childProcess.exec).toHaveBeenCalledTimes(2); + expect(childProcess.exec).toHaveBeenLastCalledWith( + "git", + ["push", "--follow-tags", "--no-verify", "origin", "master"], + { cwd } + ); + + const list = await listRemoteTags(cwd); + expect(list).toMatch("v4.5.6"); +}); + +test("unexpected git error", async () => { + const { cwd } = await cloneFixture("root-manifest-only"); + + childProcess.exec.mockImplementationOnce(async () => { + throw new Error( + [ + "Command failed: git push --follow-tags --atomic --no-verify origin master", + "fatal: some unexpected error", + ].join("\n") + ); + }); + + await expect(gitPush("origin", "master", { cwd })).rejects.toThrowError(/some unexpected error/); +}); diff --git a/commands/version/lib/git-push.js b/commands/version/lib/git-push.js index 43be5e7a13..09314548bc 100644 --- a/commands/version/lib/git-push.js +++ b/commands/version/lib/git-push.js @@ -8,5 +8,21 @@ module.exports = gitPush; function gitPush(remote, branch, opts) { log.silly("gitPush", remote, branch); - return childProcess.exec("git", ["push", "--follow-tags", "--atomic", "--no-verify", remote, branch], opts); + return childProcess + .exec("git", ["push", "--follow-tags", "--no-verify", "--atomic", remote, branch], opts) + .catch(error => { + // @see https://github.com/sindresorhus/execa/blob/v1.0.0/index.js#L159-L179 + // the error message _should_ be on stderr, but I don't trust Windows to do anything right + if (/fatal:(.*)--atomic/.test(error.message)) { + // --atomic is only supported in git >=2.4.0, which some crusty CI environments deem unnecessary to upgrade. + // so let's try again without attempting to pass an option that is almost 5 years old as of this writing... + log.warn("gitPush", "failed to pass --atomic (unsupported by remote), attempting non-atomic push"); + log.verbose("", error.message); + + return childProcess.exec("git", ["push", "--follow-tags", "--no-verify", remote, branch], opts); + } + + // ensure unexpected errors still break chain + throw error; + }); }