vole
Clone
Read only, no credentials:
https://rodent.software/git/vole.git
Write over SSH with a registered key:
git clone ssh://git@0.0.0.0:2222/vole.git
A free and open-source forge that lives in your Git repo.
Drop the legacy run-record import
59badc638dde0655b11d01eebfe3a457735d2501 · Keenan Nemetz · 10d ago · parent b312e166
Drop the legacy run-record import There is no released instance to stay compatible with, so the file format under runs/, the branch importer, and the pre-receive allowance for that directory are gone. The Record value is now only the finish wire format and the shape stored in run_attempts. metadata.ValidateIssues rejects runs/ like any other unknown directory, and vole admin index rebuild no longer reads run history from Git. An existing instance migrates once with the previous build, then rewrites the vole branch to remove the old records. Nothing in this build reads them.
server/integration_test.go +12−69
| @@ -27,7 +27,6 @@ import ( | |||
| 27 | 27 | ||
| 28 | 28 | "github.com/rodent-software/vole/ci" | |
| 29 | 29 | "github.com/rodent-software/vole/ci/runs" | |
| 30 | "github.com/rodent-software/vole/core/id" | ||
| 31 | 30 | "github.com/rodent-software/vole/forge/access" | |
| 32 | 31 | "github.com/rodent-software/vole/forge/config" | |
| 33 | 32 | "github.com/rodent-software/vole/forge/identity" | |
| @@ -526,68 +525,6 @@ func TestRebuildIndexAfterDelete(t *testing.T) { | |||
| 526 | 525 | } | |
| 527 | 526 | } | |
| 528 | 527 | ||
| 529 | // TestIndexRebuildImportsLegacyRunRecords is the upgrade path: records written | ||
| 530 | // before CI history moved into the database are still on the branch, and a rebuild | ||
| 531 | // imports them into runs.db instead of rebuilding the queue from Git forever. | ||
| 532 | func TestIndexRebuildImportsLegacyRunRecords(t *testing.T) { | ||
| 533 | t.Parallel() | ||
| 534 | f := newFixture(t) | ||
| 535 | f.publish(f.repo) | ||
| 536 | |||
| 537 | now := time.Now().UTC() | ||
| 538 | runID, err := id.New(now) | ||
| 539 | if err != nil { | ||
| 540 | t.Fatal(err) | ||
| 541 | } | ||
| 542 | m, err := f.srv.Repos.Open(f.repo) | ||
| 543 | if err != nil { | ||
| 544 | t.Fatal(err) | ||
| 545 | } | ||
| 546 | rec := runs.Record{ | ||
| 547 | RunID: runID, Attempt: 1, Repo: f.repo, Ref: "refs/heads/main", | ||
| 548 | SHA: strings.Repeat("ab", 20), Workflow: ".vole/workflows/ci.yaml", | ||
| 549 | WorkflowSHA: strings.Repeat("cd", 20), Job: "build", StepsSHA: "steps-digest", | ||
| 550 | Exit: 0, Finished: now, Signer: "keenan", | ||
| 551 | Steps: []runs.StepResult{{ | ||
| 552 | Name: "build", Started: now.Add(-time.Second), Finished: now, | ||
| 553 | }}, | ||
| 554 | } | ||
| 555 | if _, err := runs.WriteRecord(m.Git(), metadata.Ref, rec, "keenan", nil); err != nil { | ||
| 556 | t.Fatalf("writing the legacy record: %v", err) | ||
| 557 | } | ||
| 558 | |||
| 559 | // A fresh database, as after restoring the repositories but not runs.db. | ||
| 560 | f.srv.SSH.Close() | ||
| 561 | f.http.Close() | ||
| 562 | f.srv.Close() | ||
| 563 | for _, p := range []string{f.cfg.RunsDB(), f.cfg.RunsDB() + "-wal", f.cfg.RunsDB() + "-shm"} { | ||
| 564 | if err := os.Remove(p); err != nil && !os.IsNotExist(err) { | ||
| 565 | t.Fatal(err) | ||
| 566 | } | ||
| 567 | } | ||
| 568 | cmd := exec.Command(binPath, "index", "rebuild", "--config", f.cfgFile(t)) | ||
| 569 | if out, err := cmd.CombinedOutput(); err != nil { | ||
| 570 | t.Fatalf("rebuild: %v\n%s", err, out) | ||
| 571 | } | ||
| 572 | |||
| 573 | store, err := runs.Open(f.cfg.RunsDB()) | ||
| 574 | if err != nil { | ||
| 575 | t.Fatal(err) | ||
| 576 | } | ||
| 577 | defer store.Close() | ||
| 578 | got, err := store.Get(runID) | ||
| 579 | if err != nil || got == nil { | ||
| 580 | t.Fatalf("the legacy run was not imported: %v", err) | ||
| 581 | } | ||
| 582 | if got.Status != runs.StatusSuccess || got.Attempts != 1 { | ||
| 583 | t.Fatalf("imported run = %+v", got) | ||
| 584 | } | ||
| 585 | stored, err := store.Attempt(runID, 1) | ||
| 586 | if err != nil || stored == nil || stored.Signer != "keenan" || len(stored.Steps) != 1 { | ||
| 587 | t.Fatalf("imported attempt = %+v, %v", stored, err) | ||
| 588 | } | ||
| 589 | } | ||
| 590 | |||
| 591 | 528 | // TestRosterIsOneDatabaseFile proves the backup story end to end: the roster is | |
| 592 | 529 | // one SQLite file, a consistent copy of it is complete, and no system repository | |
| 593 | 530 | // or key directory exists beside it. | |
| @@ -3086,12 +3023,18 @@ jobs: | |||
| 3086 | 3023 | if err != nil { | |
| 3087 | 3024 | t.Fatal(err) | |
| 3088 | 3025 | } | |
| 3089 | onBranch, err := runs.ReadRecord(m.Git(), metadata.Ref, done.ID) | ||
| 3090 | if err != nil { | ||
| 3091 | t.Fatalf("reading the branch: %v", err) | ||
| 3092 | } | ||
| 3093 | if onBranch != nil { | ||
| 3094 | t.Fatalf("the run record is still on %s: %+v", metadata.Ref, onBranch) | ||
| 3026 | if tip, err := m.Git().RevParseRaw(metadata.Ref); err != nil { | ||
| 3027 | t.Fatal(err) | ||
| 3028 | } else if tip != "" { | ||
| 3029 | entries, err := m.Git().LsTreeR(tip) | ||
| 3030 | if err != nil { | ||
| 3031 | t.Fatal(err) | ||
| 3032 | } | ||
| 3033 | for _, e := range entries { | ||
| 3034 | if strings.HasPrefix(e.Name, "runs/") { | ||
| 3035 | t.Fatalf("the run record is still on %s: %s", metadata.Ref, e.Name) | ||
| 3036 | } | ||
| 3037 | } | ||
| 3095 | 3038 | } | |
| 3096 | 3039 | ||
| 3097 | 3040 | // The page reads the attempt, not a tree walk. | |