Publish and reconcile complete module tags

This commit is contained in:
2026-10-10 02:18:02 +00:00
parent 3e516d0ac1
commit fa34cbaff3
3 changed files with 164 additions and 9 deletions

View File

@@ -94,6 +94,109 @@ func TestPublishPushesOnlyTheCapturedAnnotatedTag(t *testing.T) {
}
}
func TestPublishUsesSeparateFullTagsForModulesAtOneCommit(t *testing.T) {
repo := newPublishFixture(t)
firstDir, secondDir := "integrations/first", "integrations/second"
addNestedReleaseModule(t, repo.work, firstDir, "example.com/first", "v1.2.3")
addNestedReleaseModule(t, repo.work, secondDir, "example.com/second", "v1.2.3")
gitRun(t, repo.work, "add", firstDir, secondDir)
gitRun(t, repo.work, "commit", "-m", "add two release modules")
gitRun(t, repo.work, "push", "origin")
commit := gitText(t, repo.work, "rev-parse", "HEAD")
for _, moduleDir := range []string{firstDir, secondDir} {
result, err := releasekit.Publish(context.Background(), releasekit.ReleaseRequest{
RepositoryDir: repo.work,
ModuleDir: moduleDir,
Product: "Sample App",
Version: "v1.2.3",
})
if err != nil {
t.Fatalf("Publish(%q): %v", moduleDir, err)
}
wantTag := moduleDir + "/v1.2.3"
if result.CandidateCommit != commit || result.Tag != wantTag || result.Version != "v1.2.3" {
t.Fatalf("publication for %q = %#v", moduleDir, result)
}
if got := gitText(t, repo.bare, "rev-parse", "refs/tags/"+wantTag); got != result.TagObject {
t.Fatalf("remote tag %s object = %s, want %s", wantTag, got, result.TagObject)
}
}
refs := gitText(t, repo.bare, "for-each-ref", "--format=%(refname)", "refs/tags")
if strings.Contains(refs, "refs/tags/v1.2.3\n") || !strings.Contains(refs, "refs/tags/"+firstDir+"/v1.2.3") || !strings.Contains(refs, "refs/tags/"+secondDir+"/v1.2.3") {
t.Fatalf("published tag refs = %q", refs)
}
firstTag := firstDir + "/v1.2.3"
gitRun(t, repo.work, "tag", "-d", firstTag)
conflict, err := releasekit.Publish(context.Background(), releasekit.ReleaseRequest{
RepositoryDir: repo.work,
ModuleDir: firstDir,
Product: "Sample App",
Version: "v1.2.3",
})
if !errors.Is(err, git.ErrRemoteTagConflict) || conflict.State != releasekit.PublicationConflict || conflict.Tag != firstTag {
t.Fatalf("reuse of remote tag result=%#v error=%v", conflict, err)
}
}
func TestPublishNestedRejectedPushReportsFullTag(t *testing.T) {
repo := newPublishFixture(t)
moduleDir := "integrations/ckconfig"
addNestedReleaseModule(t, repo.work, moduleDir, "example.com/ckconfig", "v1.2.3")
gitRun(t, repo.work, "add", moduleDir)
gitRun(t, repo.work, "commit", "-m", "add nested module")
gitRun(t, repo.work, "push", "origin")
hook := filepath.Join(repo.bare, "hooks", "pre-receive")
if err := os.WriteFile(hook, []byte("#!/bin/sh\ncat >/dev/null\nexit 1\n"), 0o755); err != nil {
t.Fatal(err)
}
wantTag := moduleDir + "/v1.2.3"
result, err := releasekit.Publish(context.Background(), releasekit.ReleaseRequest{
RepositoryDir: repo.work,
ModuleDir: moduleDir,
Product: "Sample App",
Version: "v1.2.3",
})
if err == nil || result.State != releasekit.PublicationNotPublished || result.Tag != wantTag {
t.Fatalf("nested rejected push result=%#v error=%v", result, err)
}
if !strings.Contains(err.Error(), wantTag) || !strings.Contains(err.Error(), repo.bare) || !strings.Contains(err.Error(), "authoritative tag is absent") {
t.Fatalf("nested rejected-push error = %v", err)
}
if _, err := exec.Command("git", "--git-dir", repo.bare, "show-ref", "--verify", "--quiet", "refs/tags/"+wantTag).CombinedOutput(); err == nil {
t.Fatal("rejected nested tag unexpectedly appeared on the remote")
}
}
func TestPublishNestedTagKeepsUnknownReconciliationEvidence(t *testing.T) {
repo := newPublishFixture(t)
moduleDir := "integrations/ckconfig"
addNestedReleaseModule(t, repo.work, moduleDir, "example.com/ckconfig", "v1.2.3")
gitRun(t, repo.work, "add", moduleDir)
gitRun(t, repo.work, "commit", "-m", "add nested module")
gitRun(t, repo.work, "push", "origin")
hook := filepath.Join(repo.bare, "hooks", "pre-receive")
if err := os.WriteFile(hook, []byte("#!/bin/sh\ncat >/dev/null\nexit 1\n"), 0o755); err != nil {
t.Fatal(err)
}
wantTag := moduleDir + "/v1.2.3"
wrapper := installTagQueryFailureWrapper(t, wantTag)
t.Setenv("PATH", wrapper+string(os.PathListSeparator)+os.Getenv("PATH"))
result, err := releasekit.Publish(context.Background(), releasekit.ReleaseRequest{
RepositoryDir: repo.work,
ModuleDir: moduleDir,
Product: "Sample App",
Version: "v1.2.3",
})
if err == nil || result.State != releasekit.PublicationUnknown || result.Tag != wantTag || result.TagObject == "" {
t.Fatalf("nested unknown result=%#v error=%v", result, err)
}
if !strings.Contains(err.Error(), wantTag) || !strings.Contains(err.Error(), repo.bare) || !strings.Contains(err.Error(), "reconciliation unavailable") || !strings.Contains(err.Error(), "failed to push some refs") {
t.Fatalf("nested reconciliation error = %v", err)
}
}
func TestPublishRejectsUnexpectedPushURLBeforeValidation(t *testing.T) {
repo := newPublishFixture(t)
suiteRuns := filepath.Join(t.TempDir(), "suite-runs")
@@ -699,6 +802,51 @@ func installGitWrapper(t *testing.T, repo publishFixture, mode string) gitWrappe
return installGitWrapperWithMutationMode(t, repo, mode, "")
}
func addNestedReleaseModule(t *testing.T, repository, moduleDir, modulePath, version string) {
t.Helper()
writeFile(t, repository, moduleDir+"/go.mod", "module "+modulePath+"\n\ngo 1.25.0\n")
writeFile(t, repository, moduleDir+"/docs/releases/"+version+".md", "# Sample App "+version+"\n\nNested release.\n")
}
func installTagQueryFailureWrapper(t *testing.T, tag string) string {
t.Helper()
realGit, err := exec.LookPath("git")
if err != nil {
t.Fatal(err)
}
bin := filepath.Join(t.TempDir(), "bin")
if err := os.MkdirAll(bin, 0o755); err != nil {
t.Fatal(err)
}
marker := filepath.Join(t.TempDir(), "push-attempted")
contents := fmt.Sprintf(`#!/bin/sh
REAL_GIT=%s
MARKER=%s
TAG_REF=%s
ARGS=" $* "
IS_PUSH=0
IS_LS_REMOTE=0
for ARG in "$@"; do
[ "$ARG" = "push" ] && IS_PUSH=1
[ "$ARG" = "ls-remote" ] && IS_LS_REMOTE=1
done
if [ "$IS_LS_REMOTE" = "1" ] && [ -f "$MARKER" ]; then
case "$ARGS" in *"$TAG_REF"*) echo "reconciliation unavailable" >&2; exit 77;; esac
fi
if [ "$IS_PUSH" = "1" ]; then
"$REAL_GIT" "$@"
STATUS=$?
: > "$MARKER"
exit "$STATUS"
fi
exec "$REAL_GIT" "$@"
`, shellQuote(realGit), shellQuote(marker), shellQuote("refs/tags/"+tag))
if err := os.WriteFile(filepath.Join(bin, "git"), []byte(contents), 0o755); err != nil {
t.Fatal(err)
}
return bin
}
func installGitWrapperWithMutation(t *testing.T, repo publishFixture, object string) gitWrapper {
t.Helper()
return installGitWrapperWithMutationMode(t, repo, "mutate-tag", object)