Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/vole.git

Write over SSH with a registered key:

git clone ssh://git@rodent.software:2222/vole.git

A free and open-source forge that lives in your Git repo.

Store CI run records in the database, not the repository

b312e1660ac3d570fc35350a22b3514c56ab466f · Keenan Nemetz · 11d ago · parent db5e4382

Store CI run records in the database, not the repository

A finished attempt was a set of files on the vole branch: a record, step
ranges, and artifact pointers, pushed as a commit. At 959 files, CI records
were 849 of them, and every human metadata write had to carry them.

The runner now sends the record with its finish message over the SSH channel
that holds the lease. The server checks the account, the job, and the log and
artifact pointers, then writes the attempt, steps, artifacts, attestation, and
run result in one transaction in data/runs.db. A tagged build still pushes its
release pointers to the branch after the record is stored.

Pre-receive refuses a new runs/ file. vole admin index rebuild imports records
still on an old branch; records written since are never deleted by a rebuild.
The run page reads the stored attempt, and runs.db is now a source that must be
backed up.

This stops new CI history from entering Git. Existing commits keep the records
they already have; removing those would require rewriting the protected vole
branch, which this change does not do.

server/tag_release_test.go +12−15

@@ -87,23 +87,20 @@ func TestTagPushBuildsARelease(t *testing.T) {
87 87 if code != 200 || body != "release binary\n" {
88 88 t.Fatalf("artifact download = %d %q", code, body)
89 89 }
90 // The pointer commit names the run, so a clone says which build shipped the bytes.
91 repoDir := filepath.Join(f.cfg.ReposDir(), f.repo+".git")
92 subject, _, _ := runGit(t, repoDir, "log", "-1", "--format=%s", "refs/heads/vole")
93 if !strings.Contains(subject, done.ID) {
94 t.Fatalf("metadata commit %q does not name run %s", subject, done.ID)
90 // The record is in the database. The branch carries the release pointer, and it
91 // no longer carries a run record: a release can still name the build that made it,
92 // but the CI history does not travel with a clone.
93 rec, err := f.srv.Runs.Attempt(done.ID, done.Attempts)
94 if err != nil || rec == nil {
95 t.Fatalf("the run has no stored attempt: %v", err)
95 96 }
96 // One commit carries both: the record and the release pointer are the same push, so
97 // a release cannot name bytes whose build has no record, and a run costs one push
98 // rather than one per output.
97 repoDir := filepath.Join(f.cfg.ReposDir(), f.repo+".git")
99 98 tip, _, _ := runGit(t, repoDir, "ls-tree", "-r", "--name-only", "refs/heads/vole")
100 for _, want := range []string{
101 "runs/" + done.ID + "/1/record.md",
102 "releases/v1.0.0/tool-linux",
103 } {
104 if !strings.Contains(tip, want) {
105 t.Fatalf("the run's commit does not carry %s; it has:\n%s", want, tip)
106 }
99 if !strings.Contains(tip, "releases/v1.0.0/tool-linux") {
100 t.Fatalf("the release commit does not carry the pointer; it has:\n%s", tip)
101 }
102 if strings.Contains(tip, "runs/") {
103 t.Fatalf("the release commit still carries a run record; it has:\n%s", tip)
107 104 }
108 105 // And the run page shows the tag it built for.
109 106 if _, runPage := f.get("/r/" + f.repo + "/runs/" + done.ID); !strings.Contains(runPage, "tag v1.0.0") {