Skip to content
This repository was archived by the owner on May 13, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 27 additions & 7 deletions main.go → cmd/infinite-git/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ 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"
Expand All @@ -18,21 +20,39 @@ var env = envconfig.MustProcess(context.Background(), &struct {
RepoPath string `env:"REPO_PATH,default=./infinite-repo"`
}{})

func main() {
// clog/gcp/init automatically sets up the logger
// 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"),
}
}

// Initialize repository
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)
gitRepo, err := repo.New(env.RepoPath)
content := &gitContent{}
gitRepo, err := repo.New(env.RepoPath, content.InitialFiles())
if err != nil {
slog.Error("failed to initialize repository", "error", err)
os.Exit(1)
}

// Create server
srv := server.New(gitRepo)
srv := server.New(gitRepo, content)

// Set up HTTP server
httpServer := &http.Server{
Addr: ":" + env.Port,
Handler: srv.Handler(),
Expand Down
54 changes: 14 additions & 40 deletions main_test.go → cmd/infinite-git/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,22 @@ import (
"github.com/imjasonh/infinite-git/internal/server"
)

func TestCloneAndPull(t *testing.T) {
// Create temporary directories
serverRepoDir := t.TempDir()
clientRepoDir := t.TempDir()

// Initialize server repository
serverRepo, err := repo.New(serverRepoDir)
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)

// Start test server
srv := server.New(serverRepo, content)
ts := httptest.NewServer(srv.Handler())
defer ts.Close()
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{
Expand Down Expand Up @@ -115,20 +115,7 @@ func TestCloneAndPull(t *testing.T) {
}

func TestConcurrentPulls(t *testing.T) {
// Create temporary directories
serverRepoDir := t.TempDir()

// Initialize server repository
serverRepo, err := repo.New(serverRepoDir)
if err != nil {
t.Fatalf("failed to create server repo: %v", err)
}

srv := server.New(serverRepo)

// Start test server
ts := httptest.NewServer(srv.Handler())
defer ts.Close()
ts := newTestServer(t)

// Clone initial repository
baseClientDir := t.TempDir()
Expand Down Expand Up @@ -199,22 +186,9 @@ func TestConcurrentPulls(t *testing.T) {
}

func TestPushRejection(t *testing.T) {
// Create temporary directories
serverRepoDir := t.TempDir()
ts := newTestServer(t)
clientRepoDir := t.TempDir()

// Initialize server repository
serverRepo, err := repo.New(serverRepoDir)
if err != nil {
t.Fatalf("failed to create server repo: %v", err)
}

srv := server.New(serverRepo)

// Start test server
ts := httptest.NewServer(srv.Handler())
defer ts.Close()

// Clone the repository
gitRepo, err := git.PlainClone(clientRepoDir, false, &git.CloneOptions{
URL: ts.URL,
Expand Down
122 changes: 122 additions & 0 deletions cmd/infinite-go/main.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
Loading
Loading