@@ -4,6 +4,7 @@ import {
|
||||
mkdtempSync,
|
||||
mkdirSync,
|
||||
readFileSync,
|
||||
readdirSync,
|
||||
writeFileSync,
|
||||
rmSync,
|
||||
} from "node:fs";
|
||||
@@ -134,6 +135,17 @@ test("PlanOnly is offline and leaves versions/index/refs unchanged", (t) => {
|
||||
assert.equal(plan.details.targetVersion, "1.2.1");
|
||||
assert.equal(f.git("status", "--porcelain"), before);
|
||||
assert.equal(f.git("rev-parse", "HEAD"), head);
|
||||
const replacement = f.release("-Version", "1.2.0", "-Replace", "-PlanOnly");
|
||||
assert.equal(replacement.status, 0, replacement.stderr);
|
||||
const replacementPlan = JSON.parse(replacement.stdout);
|
||||
assert.equal(replacementPlan.changed, false);
|
||||
assert.equal(replacementPlan.details.replace, true);
|
||||
assert.equal(
|
||||
replacementPlan.details.git.replaceOnlyVersionTagWithLease,
|
||||
true,
|
||||
);
|
||||
assert.equal(f.git("status", "--porcelain"), before);
|
||||
assert.equal(f.git("rev-parse", "HEAD"), head);
|
||||
});
|
||||
|
||||
test("release commits exact dirty source, versions both locks, tags and atomically pushes", (t) => {
|
||||
@@ -172,6 +184,169 @@ test("failed build creates no commit/tag/push and preserves existing staging", (
|
||||
assert.equal(f.git("tag", "--list"), "");
|
||||
});
|
||||
|
||||
for (const remoteOnly of [false, true]) {
|
||||
test(`replacement rebuilds the same version and preserves the old folder (remote-only tag: ${remoteOnly})`, (t) => {
|
||||
const f = fixture(t);
|
||||
assert.equal(f.release("-Version", "1.2.1").status, 0);
|
||||
const oldTag = f.git("rev-parse", "refs/tags/v1.2.1");
|
||||
const oldCommit = f.git("rev-parse", "HEAD");
|
||||
const oldManifest = f.manifest();
|
||||
if (remoteOnly) f.git("tag", "-d", "v1.2.1");
|
||||
f.write("feature.txt", "updated before publishing");
|
||||
|
||||
const result = f.release("-Version", "1.2.1", "-Replace");
|
||||
assert.equal(result.status, 0, result.stdout + result.stderr);
|
||||
const newTag = f.git("rev-parse", "refs/tags/v1.2.1");
|
||||
assert.notEqual(newTag, oldTag);
|
||||
assert.equal(
|
||||
f.git("rev-parse", "v1.2.1^{commit}"),
|
||||
f.git("rev-parse", "HEAD"),
|
||||
);
|
||||
assert.equal(
|
||||
f.git("ls-remote", "origin", "refs/tags/v1.2.1").split(/\s/)[0],
|
||||
newTag,
|
||||
);
|
||||
assert.equal(f.git("rev-parse", "HEAD~1"), oldCommit);
|
||||
assert.equal(f.manifest().gitRelease.previousRemoteTag, oldTag);
|
||||
assert.equal(f.manifest().gitRelease.status, "pushed");
|
||||
const backups = readdirSync(join(f.repo, "releases")).filter((name) =>
|
||||
name.startsWith("proxywarden-v1.2.1-replaced-"),
|
||||
);
|
||||
assert.equal(backups.length, 1);
|
||||
assert.deepEqual(
|
||||
JSON.parse(
|
||||
readFileSync(
|
||||
join(f.repo, "releases", backups[0], "release-manifest.json"),
|
||||
"utf8",
|
||||
),
|
||||
),
|
||||
oldManifest,
|
||||
);
|
||||
assert.equal(
|
||||
readFileSync(
|
||||
join(
|
||||
f.repo,
|
||||
"releases",
|
||||
backups[0],
|
||||
"artifacts/nsis/ProxyWarden_1.2.1_x64-setup.exe",
|
||||
),
|
||||
"utf8",
|
||||
),
|
||||
"test artifact",
|
||||
);
|
||||
assert.equal(f.git("status", "--porcelain"), "");
|
||||
});
|
||||
}
|
||||
|
||||
test("failed replacement build preserves the previous release and refs", (t) => {
|
||||
const f = fixture(
|
||||
t,
|
||||
"if ($Replace) { throw 'Synthetic replacement build failure' }",
|
||||
);
|
||||
assert.equal(f.release("-Version", "1.2.1").status, 0);
|
||||
const oldManifest = f.manifest();
|
||||
const oldRefs = f.git("show-ref");
|
||||
f.write("feature.txt", "work in progress");
|
||||
const result = f.release("-Version", "1.2.1", "-Replace");
|
||||
assert.notEqual(result.status, 0);
|
||||
assert.match(result.stderr, /Synthetic replacement build failure/);
|
||||
assert.equal(f.git("show-ref"), oldRefs);
|
||||
assert.deepEqual(f.manifest(), oldManifest);
|
||||
assert.deepEqual(readdirSync(join(f.repo, "releases")), [
|
||||
"proxywarden-v1.2.1",
|
||||
]);
|
||||
});
|
||||
|
||||
test("a failed replacement push resumes using the original tag lease", (t) => {
|
||||
const f = fixture(t);
|
||||
assert.equal(f.release("-Version", "1.2.1").status, 0);
|
||||
const oldRefs = f.git("ls-remote", "origin");
|
||||
f.write("feature.txt", "replacement");
|
||||
const hook = join(f.remote, "hooks/pre-receive");
|
||||
writeFileSync(hook, "#!/bin/sh\nexit 1\n");
|
||||
const result = f.release("-Version", "1.2.1", "-Replace");
|
||||
assert.notEqual(result.status, 0);
|
||||
assert.equal(f.manifest().gitRelease.status, "pending-push");
|
||||
assert.equal(f.git("ls-remote", "origin"), oldRefs);
|
||||
const replacementTag = f.git("rev-parse", "refs/tags/v1.2.1");
|
||||
rmSync(hook);
|
||||
const resumed = f.release("-Version", "1.2.1", "-Resume");
|
||||
assert.equal(resumed.status, 0, resumed.stdout + resumed.stderr);
|
||||
assert.equal(f.manifest().gitRelease.status, "pushed");
|
||||
assert.equal(
|
||||
f.git("ls-remote", "origin", "refs/tags/v1.2.1").split(/\s/)[0],
|
||||
replacementTag,
|
||||
);
|
||||
const repeat = f.release("-Version", "1.2.1", "-Resume");
|
||||
assert.equal(repeat.status, 0, repeat.stdout + repeat.stderr);
|
||||
});
|
||||
|
||||
test("replacement never forces the branch when it advances during the build", (t) => {
|
||||
const f = fixture(
|
||||
t,
|
||||
`if ($Replace) {
|
||||
$otherCommit = 'Concurrent remote commit' | & git commit-tree 'HEAD^{tree}' -p HEAD
|
||||
Invoke-Git @('push', 'origin', "${"$"}{otherCommit}:refs/heads/master") | Out-Null
|
||||
}`,
|
||||
);
|
||||
assert.equal(f.release("-Version", "1.2.1").status, 0);
|
||||
const oldTag = f.git("ls-remote", "origin", "refs/tags/v1.2.1");
|
||||
f.write("feature.txt", "replacement");
|
||||
const result = f.release("-Version", "1.2.1", "-Replace");
|
||||
assert.notEqual(result.status, 0);
|
||||
assert.match(result.stderr, /non-fast-forward|fetch first/);
|
||||
const remoteBranch = f
|
||||
.git("ls-remote", "origin", "refs/heads/master")
|
||||
.split(/\s/)[0];
|
||||
assert.notEqual(remoteBranch, f.git("rev-parse", "HEAD"));
|
||||
assert.equal(
|
||||
f.git("show", "-s", "--format=%s", remoteBranch),
|
||||
"Concurrent remote commit",
|
||||
);
|
||||
assert.equal(f.git("ls-remote", "origin", "refs/tags/v1.2.1"), oldTag);
|
||||
});
|
||||
|
||||
test("a concurrent remote tag change is preserved, including on Resume", (t) => {
|
||||
const f = fixture(
|
||||
t,
|
||||
`if ($Replace) {
|
||||
$otherCommit = Invoke-Git @('rev-parse', 'HEAD~1')
|
||||
Invoke-Git @('--git-dir', (Join-Path $RepoRoot '../origin.git'), 'update-ref', 'refs/tags/v1.2.1', $otherCommit) | Out-Null
|
||||
}`,
|
||||
);
|
||||
assert.equal(f.release("-Version", "1.2.1").status, 0);
|
||||
const oldBranch = f.git("ls-remote", "origin", "refs/heads/master");
|
||||
const concurrentTag = f.git("rev-parse", "HEAD~1");
|
||||
f.write("feature.txt", "replacement");
|
||||
const result = f.release("-Version", "1.2.1", "-Replace");
|
||||
assert.notEqual(result.status, 0);
|
||||
assert.match(result.stderr, /stale info/);
|
||||
const resumed = f.release("-Version", "1.2.1", "-Resume");
|
||||
assert.notEqual(resumed.status, 0);
|
||||
assert.match(resumed.stderr, /stale info/);
|
||||
assert.equal(
|
||||
f.git("ls-remote", "origin", "refs/tags/v1.2.1").split(/\s/)[0],
|
||||
concurrentTag,
|
||||
);
|
||||
assert.equal(f.git("ls-remote", "origin", "refs/heads/master"), oldBranch);
|
||||
});
|
||||
|
||||
test("a concurrent local tag change is not overwritten by replacement", (t) => {
|
||||
const f = fixture(
|
||||
t,
|
||||
"if ($Replace) { Invoke-Git @('tag', '-f', 'v1.2.1', 'HEAD~1') | Out-Null }",
|
||||
);
|
||||
assert.equal(f.release("-Version", "1.2.1").status, 0);
|
||||
const oldRemote = f.git("ls-remote", "origin");
|
||||
const concurrentTag = f.git("rev-parse", "HEAD~1");
|
||||
f.write("feature.txt", "replacement");
|
||||
const result = f.release("-Version", "1.2.1", "-Replace");
|
||||
assert.notEqual(result.status, 0);
|
||||
assert.match(result.stderr, /Local version tag changed/);
|
||||
assert.equal(f.git("rev-parse", "refs/tags/v1.2.1"), concurrentTag);
|
||||
assert.equal(f.git("ls-remote", "origin"), oldRemote);
|
||||
});
|
||||
|
||||
test("source edit during build refuses to tag an artifact from another tree", (t) => {
|
||||
const f = fixture(
|
||||
t,
|
||||
|
||||
Reference in New Issue
Block a user