Preserve replacement selectors during recovery
This commit is contained in:
@@ -15,6 +15,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/mod/modfile"
|
||||
|
||||
"go.maximumdirect.net/releasekit/cmd/releasekit/internal/cli"
|
||||
"go.maximumdirect.net/releasekit/internal/gotool"
|
||||
"go.maximumdirect.net/releasekit/internal/publish"
|
||||
@@ -159,16 +161,25 @@ func TestColdGoToolRecoverySurvivesLauncherExit(t *testing.T) {
|
||||
if err := os.Symlink(source, selectedSource); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !registered {
|
||||
relative, err := filepath.Rel(repo, selectedSource)
|
||||
const toolModule = "go.maximumdirect.net/releasekit"
|
||||
const toolVersion = "v0.0.0"
|
||||
module := "module example.com/cli-candidate\n\ngo 1.26.0\n\nrequire " + toolModule + " " + toolVersion + "\n"
|
||||
var wildcardSource string
|
||||
if registered {
|
||||
module += "\nreplace " + toolModule + " => " + strconv.Quote(selectedSource) + "\n"
|
||||
module += "\ntool " + toolModule + "/cmd/releasekit\n"
|
||||
} else {
|
||||
wildcardSource = t.TempDir()
|
||||
writeRepositoryFile(t, wildcardSource, "go.mod", "module "+toolModule+"\n\ngo 1.26.0\n")
|
||||
wildcard, err := filepath.Rel(repo, wildcardSource)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
selectedSource = relative
|
||||
}
|
||||
module := "module example.com/cli-candidate\n\ngo 1.26.0\n\nrequire go.maximumdirect.net/releasekit v0.0.0\n\nreplace go.maximumdirect.net/releasekit => " + strconv.Quote(selectedSource) + "\n"
|
||||
if registered {
|
||||
module += "\ntool go.maximumdirect.net/releasekit/cmd/releasekit\n"
|
||||
specific, err := filepath.Rel(repo, selectedSource)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
module += "\nreplace (\n" + toolModule + " => " + strconv.Quote(wildcard) + "\n" + toolModule + " " + toolVersion + " => " + strconv.Quote(specific) + "\n)\n"
|
||||
}
|
||||
writeRepositoryFile(t, repo, "go.mod", module)
|
||||
sums, err := os.ReadFile(filepath.Join(source, "go.sum"))
|
||||
@@ -251,8 +262,34 @@ func TestColdGoToolRecoverySurvivesLauncherExit(t *testing.T) {
|
||||
if !strings.Contains(command, "go ") || strings.Contains(command, "/exe/releasekit") {
|
||||
t.Fatalf("unstable handoff: %s", command)
|
||||
}
|
||||
if !registered && !strings.Contains(command, "publication-recovery") {
|
||||
t.Fatalf("API-only handoff lacks durable metadata: %s", command)
|
||||
if !registered {
|
||||
store := filepath.Join(repo, ".git", "releasekit", "publication-recovery")
|
||||
entries, err := os.ReadDir(store)
|
||||
if err != nil || len(entries) != 1 {
|
||||
t.Fatalf("API-only recovery modules = %v, %v", entries, err)
|
||||
}
|
||||
dir := filepath.Join(store, entries[0].Name())
|
||||
if !strings.Contains(command, shellQuote(dir)) {
|
||||
t.Fatalf("API-only handoff lacks durable metadata: %s", command)
|
||||
}
|
||||
path := filepath.Join(dir, "go.mod")
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
prepared, err := modfile.Parse(path, data, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
paths := map[string]string{}
|
||||
for _, replacement := range prepared.Replace {
|
||||
if replacement.Old.Path == toolModule {
|
||||
paths[replacement.Old.Version] = replacement.New.Path
|
||||
}
|
||||
}
|
||||
if len(paths) != 2 || paths[""] != wildcardSource || paths[toolVersion] != selectedSource {
|
||||
t.Fatalf("durable recovery lost replacement selection: %v", paths)
|
||||
}
|
||||
}
|
||||
// Execute from another directory after the original driver and the
|
||||
// API-only launcher's temporary files are gone.
|
||||
|
||||
@@ -12,8 +12,10 @@ For CLI publication recovery, the package selects a registered module tool or
|
||||
prepares a durable private module root for an API-only consumer. Go's module
|
||||
graph query includes transitive dependencies; preparation retains module
|
||||
requirements, versioned replacements, and checksums. Local replacements resolve
|
||||
against their original module directory before relocation. The original main
|
||||
module remains a local replacement, including when releasing ReleaseKit itself.
|
||||
against their original module directory before relocation. Each relocation
|
||||
updates the directive's destination and retains its path and version selector.
|
||||
The original main module remains a local replacement, including when releasing
|
||||
ReleaseKit itself.
|
||||
The private root provides tool resolution independently of consumer vendoring.
|
||||
API-only preparation discovers the host target for dependency inspection. The
|
||||
returned invocation carries its directory and tool arguments; the CLI's later
|
||||
|
||||
@@ -172,6 +172,8 @@ and a new module-selection defect in the smoke recipe.
|
||||
|
||||
### Relocate recovery replacements in one pass without merging selectors
|
||||
|
||||
**Completed.** Recovery rewrites relative destinations in one pass. The CLI regression verifies both selectors, the selected tool, the rejected-push status, and later reconciliation.
|
||||
|
||||
**Evidence:** The API-only path in
|
||||
[`RecoveryTool`](../../internal/gotool/recovery.go) walks the parsed
|
||||
`go.mod` replacements. It calls `modfile.AddReplace` for each relative
|
||||
|
||||
@@ -100,9 +100,11 @@ func RecoveryTool(ctx context.Context, moduleDir, recordDir string) (*RecoveryIn
|
||||
// replacements relative to the directory that originally owned go.mod.
|
||||
for _, replacement := range module.Replace {
|
||||
if replacement.New.Version == "" && !filepath.IsAbs(replacement.New.Path) {
|
||||
if err := module.AddReplace(replacement.Old.Path, replacement.Old.Version, filepath.Join(moduleDir, replacement.New.Path), ""); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// A local replacement ends in its path token. Edit that token on
|
||||
// the parsed directive so a wildcard does not absorb versioned ones.
|
||||
replacement.New.Path = filepath.Join(moduleDir, replacement.New.Path)
|
||||
tokens := replacement.Syntax.Token
|
||||
tokens[len(tokens)-1] = modfile.AutoQuote(replacement.New.Path)
|
||||
}
|
||||
}
|
||||
// The original main module's source remains local if a dependency refers
|
||||
|
||||
Reference in New Issue
Block a user