1
0
Fork 0
mirror of https://github.com/imjasonh/infinite-git synced 2026-07-13 11:19:14 +00:00

Add cmd/infinite-go: serve Go package with PullTime via git

Refactor infinite-git to support pluggable content generation:
- Extract ContentProvider interface (InitialFiles, GenerateFiles, CommitMessage)
- Make repo.New accept initial files map instead of hardcoding content
- Move infinite-git to cmd/infinite-git with gitContent provider
- Add cmd/infinite-go that serves a Go module where each pull generates
  a new version with PullTime set to the pull timestamp
- Include ?go-get=1 discovery middleware for Go module resolution

https://claude.ai/code/session_01HpvELk7HnbJoXEUjqHP7SU
This commit is contained in:
Claude 2026-04-03 08:22:45 +00:00
parent 16db43a3af
commit 96dd2d9604
No known key found for this signature in database
8 changed files with 325 additions and 106 deletions

69
cmd/infinite-git/main.go Normal file
View file

@ -0,0 +1,69 @@
package main
import (
"context"
"fmt"
"log/slog"
"net/http"
"os"
"time"
_ "github.com/chainguard-dev/clog/gcp/init"
"github.com/imjasonh/infinite-git/internal/generator"
"github.com/imjasonh/infinite-git/internal/repo"
"github.com/imjasonh/infinite-git/internal/server"
"github.com/sethvargo/go-envconfig"
)
var env = envconfig.MustProcess(context.Background(), &struct {
Port string `env:"PORT,default=8080"`
RepoPath string `env:"REPO_PATH,default=./infinite-repo"`
}{})
// gitContent provides the default infinite-git file content.
type gitContent struct{}
func (g *gitContent) InitialFiles() map[string][]byte {
return map[string][]byte{
"README.md": []byte("# Infinite Git Repository\n\nThis repository generates a new commit every time you pull.\n"),
"hello.txt": []byte("Pull #0\nTimestamp: Initial commit\n"),
}
}
func (g *gitContent) GenerateFiles(count int64, now time.Time) map[string][]byte {
return map[string][]byte{
"hello.txt": []byte(fmt.Sprintf("Pull #%d\nTimestamp: %s\n", count, now.Format("2006-01-02 15:04:05.999999999"))),
}
}
func (g *gitContent) CommitMessage(count int64, now time.Time) string {
return fmt.Sprintf("Pull #%d at %s", count, now.Format("2006-01-02 15:04:05"))
}
var _ generator.ContentProvider = (*gitContent)(nil)
func main() {
slog.Info("initializing repository", "env", env)
content := &gitContent{}
gitRepo, err := repo.New(env.RepoPath, content.InitialFiles())
if err != nil {
slog.Error("failed to initialize repository", "error", err)
os.Exit(1)
}
srv := server.New(gitRepo, content)
httpServer := &http.Server{
Addr: ":" + env.Port,
Handler: srv.Handler(),
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 120 * time.Second,
}
slog.Info("starting HTTP server", "port", env.Port)
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
slog.Error("HTTP server error", "error", err)
os.Exit(1)
}
}

View file

@ -0,0 +1,258 @@
package main
import (
"fmt"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"sync"
"testing"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/go-git/go-git/v5/plumbing/transport/http"
"github.com/imjasonh/infinite-git/internal/repo"
"github.com/imjasonh/infinite-git/internal/server"
)
func newTestServer(t *testing.T) *httptest.Server {
t.Helper()
content := &gitContent{}
serverRepo, err := repo.New(t.TempDir(), content.InitialFiles())
if err != nil {
t.Fatalf("failed to create server repo: %v", err)
}
srv := server.New(serverRepo, content)
ts := httptest.NewServer(srv.Handler())
t.Cleanup(ts.Close)
return ts
}
func TestCloneAndPull(t *testing.T) {
ts := newTestServer(t)
clientRepoDir := t.TempDir()
// Clone the repository
gitRepo, err := git.PlainClone(clientRepoDir, false, &git.CloneOptions{
URL: ts.URL,
})
if err != nil {
t.Fatalf("failed to clone: %v", err)
}
// Get initial commit
ref, err := gitRepo.Head()
if err != nil {
t.Fatalf("failed to get HEAD: %v", err)
}
initialCommit := ref.Hash()
t.Logf("Initial commit: %s", initialCommit)
// Count initial commits
initialCount := countCommits(t, gitRepo)
t.Logf("Initial commit count: %d", initialCount)
// Perform multiple pulls
commits := []plumbing.Hash{initialCommit}
for i := 0; i < 3; i++ {
// Pull from origin
w, err := gitRepo.Worktree()
if err != nil {
t.Fatalf("failed to get worktree: %v", err)
}
err = w.Pull(&git.PullOptions{
RemoteName: "origin",
})
if err != nil && err != git.NoErrAlreadyUpToDate {
t.Fatalf("pull %d failed: %v", i+1, err)
}
// Get new HEAD
ref, err := gitRepo.Head()
if err != nil {
t.Fatalf("failed to get HEAD after pull %d: %v", i+1, err)
}
newCommit := ref.Hash()
// Verify we got a new commit
if newCommit == commits[len(commits)-1] {
t.Errorf("pull %d did not generate a new commit", i+1)
}
commits = append(commits, newCommit)
t.Logf("Pull %d got commit: %s", i+1, newCommit)
// Verify commit count increased
newCount := countCommits(t, gitRepo)
if newCount != initialCount+i+1 {
t.Errorf("expected %d commits after pull %d, got %d", initialCount+i+1, i+1, newCount)
}
// Verify hello.txt exists and has been updated
helloFile := filepath.Join(clientRepoDir, "hello.txt")
content, err := os.ReadFile(helloFile)
if err != nil {
t.Errorf("failed to read hello.txt: %v", err)
} else {
// Clone increments counter to 1, so first pull is #2, second is #3, etc.
expectedContent := fmt.Sprintf("Pull #%d\n", i+2)
if !strings.Contains(string(content), expectedContent) {
t.Errorf("hello.txt does not contain expected content %q, got: %s", expectedContent, content)
}
}
}
// Verify all commits are unique
uniqueCommits := make(map[plumbing.Hash]bool)
for _, commit := range commits {
if uniqueCommits[commit] {
t.Errorf("found duplicate commit: %s", commit)
}
uniqueCommits[commit] = true
}
}
func TestConcurrentPulls(t *testing.T) {
ts := newTestServer(t)
// Clone initial repository
baseClientDir := t.TempDir()
baseRepo, err := git.PlainClone(baseClientDir, false, &git.CloneOptions{
URL: ts.URL,
})
if err != nil {
t.Fatalf("failed to clone: %v", err)
}
initialCount := countCommits(t, baseRepo)
t.Logf("Initial commit count: %d", initialCount)
// Perform concurrent pulls
const numPulls = 5
var wg sync.WaitGroup
commits := make([]plumbing.Hash, numPulls)
errors := make([]error, numPulls)
for i := 0; i < numPulls; i++ {
wg.Add(1)
go func(idx int) {
defer wg.Done()
// Clone a fresh repo for this goroutine
clientDir := filepath.Join(t.TempDir(), fmt.Sprintf("client_%d", idx))
gitRepo, err := git.PlainClone(clientDir, false, &git.CloneOptions{
URL: ts.URL,
})
if err != nil {
errors[idx] = fmt.Errorf("clone failed: %w", err)
return
}
// Get the commit we received
ref, err := gitRepo.Head()
if err != nil {
errors[idx] = fmt.Errorf("get HEAD failed: %w", err)
return
}
commits[idx] = ref.Hash()
t.Logf("Concurrent pull %d got commit: %s", idx, commits[idx])
}(i)
}
wg.Wait()
// Check for errors
for i, err := range errors {
if err != nil {
t.Errorf("goroutine %d failed: %v", i, err)
}
}
// Verify all commits are unique
uniqueCommits := make(map[plumbing.Hash]bool)
for i, commit := range commits {
if uniqueCommits[commit] {
t.Errorf("concurrent pull %d got duplicate commit: %s", i, commit)
}
uniqueCommits[commit] = true
}
// All pulls should have generated unique commits
if len(uniqueCommits) != numPulls {
t.Errorf("expected %d unique commits, got %d", numPulls, len(uniqueCommits))
}
}
func TestPushRejection(t *testing.T) {
ts := newTestServer(t)
clientRepoDir := t.TempDir()
// Clone the repository
gitRepo, err := git.PlainClone(clientRepoDir, false, &git.CloneOptions{
URL: ts.URL,
})
if err != nil {
t.Fatalf("failed to clone: %v", err)
}
// Create a new file and commit
testFile := filepath.Join(clientRepoDir, "test-push.txt")
if err := os.WriteFile(testFile, []byte("test push content"), 0644); err != nil {
t.Fatalf("failed to write test file: %v", err)
}
w, err := gitRepo.Worktree()
if err != nil {
t.Fatalf("failed to get worktree: %v", err)
}
if _, err := w.Add("test-push.txt"); err != nil {
t.Fatalf("failed to add file: %v", err)
}
if _, err := w.Commit("Test push commit", &git.CommitOptions{
Author: &object.Signature{
Name: "Test User",
Email: "test@example.com",
},
}); err != nil {
t.Fatalf("failed to commit: %v", err)
}
// Try to push - should fail
err = gitRepo.Push(&git.PushOptions{
RemoteName: "origin",
Auth: &http.BasicAuth{
Username: "test",
Password: "test",
},
})
if err == nil {
t.Fatal("push should have been rejected but succeeded")
}
// Verify the error indicates push was forbidden
t.Logf("Push rejected with error: %v", err)
}
// Helper function to count commits
func countCommits(t *testing.T, repo *git.Repository) int {
iter, err := repo.Log(&git.LogOptions{})
if err != nil {
t.Fatalf("failed to get log: %v", err)
}
defer iter.Close()
count := 0
err = iter.ForEach(func(c *object.Commit) error {
count++
return nil
})
if err != nil {
t.Fatalf("failed to iterate commits: %v", err)
}
return count
}

122
cmd/infinite-go/main.go Normal file
View file

@ -0,0 +1,122 @@
package main
import (
"context"
"fmt"
"log/slog"
"net/http"
"os"
"path"
"strings"
"time"
_ "github.com/chainguard-dev/clog/gcp/init"
"github.com/imjasonh/infinite-git/internal/generator"
"github.com/imjasonh/infinite-git/internal/repo"
"github.com/imjasonh/infinite-git/internal/server"
"github.com/sethvargo/go-envconfig"
)
var env = envconfig.MustProcess(context.Background(), &struct {
Port string `env:"PORT,default=8080"`
RepoPath string `env:"REPO_PATH,default=./infinite-go-repo"`
ModulePath string `env:"MODULE_PATH,default=example.com/infinite-go"`
}{})
// goContent generates a Go package with PullTime set to the pull timestamp.
type goContent struct {
modulePath string
pkgName string
}
func newGoContent(modulePath string) *goContent {
// Derive package name from last path element, removing hyphens.
pkg := path.Base(modulePath)
pkg = strings.ReplaceAll(pkg, "-", "")
pkg = strings.ReplaceAll(pkg, ".", "")
return &goContent{
modulePath: modulePath,
pkgName: pkg,
}
}
func (g *goContent) InitialFiles() map[string][]byte {
return g.GenerateFiles(0, time.Now())
}
func (g *goContent) GenerateFiles(count int64, now time.Time) map[string][]byte {
now = now.UTC()
goMod := fmt.Sprintf("module %s\n\ngo 1.24\n", g.modulePath)
goFile := fmt.Sprintf(`package %s
import "time"
// PullTime is the time this module version was generated.
var PullTime = time.Date(%d, time.%s, %d, %d, %d, %d, %d, time.UTC)
`,
g.pkgName,
now.Year(), now.Month(), now.Day(),
now.Hour(), now.Minute(), now.Second(), now.Nanosecond(),
)
return map[string][]byte{
"go.mod": []byte(goMod),
"pulltime.go": []byte(goFile),
}
}
func (g *goContent) CommitMessage(count int64, now time.Time) string {
return fmt.Sprintf("Pull #%d at %s", count, now.Format("2006-01-02 15:04:05"))
}
var _ generator.ContentProvider = (*goContent)(nil)
// goGetMiddleware intercepts ?go-get=1 requests for Go module discovery.
func goGetMiddleware(modulePath string, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Get("go-get") == "1" {
scheme := "https"
if r.TLS == nil {
scheme = "http"
}
repoURL := fmt.Sprintf("%s://%s", scheme, r.Host)
w.Header().Set("Content-Type", "text/html")
fmt.Fprintf(w, `<!DOCTYPE html>
<html><head>
<meta name="go-import" content="%s git %s">
</head></html>
`, modulePath, repoURL)
return
}
next.ServeHTTP(w, r)
})
}
func main() {
slog.Info("initializing repository", "env", env)
content := newGoContent(env.ModulePath)
gitRepo, err := repo.New(env.RepoPath, content.InitialFiles())
if err != nil {
slog.Error("failed to initialize repository", "error", err)
os.Exit(1)
}
srv := server.New(gitRepo, content)
handler := goGetMiddleware(env.ModulePath, srv.Handler())
httpServer := &http.Server{
Addr: ":" + env.Port,
Handler: handler,
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 120 * time.Second,
}
slog.Info("starting HTTP server", "port", env.Port, "module", env.ModulePath)
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
slog.Error("HTTP server error", "error", err)
os.Exit(1)
}
}

View file

@ -0,0 +1,104 @@
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"github.com/go-git/go-git/v5"
"github.com/imjasonh/infinite-git/internal/repo"
"github.com/imjasonh/infinite-git/internal/server"
)
func TestGoCloneAndPull(t *testing.T) {
modulePath := "example.com/infinite-go"
content := newGoContent(modulePath)
serverRepo, err := repo.New(t.TempDir(), content.InitialFiles())
if err != nil {
t.Fatalf("failed to create server repo: %v", err)
}
srv := server.New(serverRepo, content)
ts := httptest.NewServer(goGetMiddleware(modulePath, srv.Handler()))
defer ts.Close()
// Clone the repository
clientDir := t.TempDir()
gitRepo, err := git.PlainClone(clientDir, false, &git.CloneOptions{
URL: ts.URL,
})
if err != nil {
t.Fatalf("failed to clone: %v", err)
}
// Verify go.mod exists with correct module path
goMod, err := os.ReadFile(filepath.Join(clientDir, "go.mod"))
if err != nil {
t.Fatalf("failed to read go.mod: %v", err)
}
if !strings.Contains(string(goMod), fmt.Sprintf("module %s", modulePath)) {
t.Errorf("go.mod does not contain expected module path, got: %s", goMod)
}
// Verify pulltime.go exists with PullTime var
goFile, err := os.ReadFile(filepath.Join(clientDir, "pulltime.go"))
if err != nil {
t.Fatalf("failed to read pulltime.go: %v", err)
}
goFileStr := string(goFile)
if !strings.Contains(goFileStr, "package infinitego") {
t.Errorf("pulltime.go does not contain expected package name, got: %s", goFile)
}
if !strings.Contains(goFileStr, "var PullTime = time.Date(") {
t.Errorf("pulltime.go does not contain PullTime var, got: %s", goFile)
}
// Pull again and verify PullTime changed
firstGoFile := goFileStr
w, err := gitRepo.Worktree()
if err != nil {
t.Fatalf("failed to get worktree: %v", err)
}
err = w.Pull(&git.PullOptions{RemoteName: "origin"})
if err != nil && err != git.NoErrAlreadyUpToDate {
t.Fatalf("pull failed: %v", err)
}
goFile, err = os.ReadFile(filepath.Join(clientDir, "pulltime.go"))
if err != nil {
t.Fatalf("failed to read pulltime.go after pull: %v", err)
}
if string(goFile) == firstGoFile {
t.Error("pulltime.go did not change after pull")
}
}
func TestGoGetDiscovery(t *testing.T) {
modulePath := "example.com/infinite-go"
content := newGoContent(modulePath)
serverRepo, err := repo.New(t.TempDir(), content.InitialFiles())
if err != nil {
t.Fatalf("failed to create server repo: %v", err)
}
srv := server.New(serverRepo, content)
ts := httptest.NewServer(goGetMiddleware(modulePath, srv.Handler()))
defer ts.Close()
// Test ?go-get=1 returns go-import meta tag
resp, err := http.Get(ts.URL + "/?go-get=1")
if err != nil {
t.Fatalf("failed to fetch go-get: %v", err)
}
defer resp.Body.Close()
buf := make([]byte, 4096)
n, _ := resp.Body.Read(buf)
body := string(buf[:n])
expected := fmt.Sprintf(`<meta name="go-import" content="%s git`, modulePath)
if !strings.Contains(body, expected) {
t.Errorf("go-get response does not contain expected meta tag.\nexpected to contain: %s\ngot: %s", expected, body)
}
}