Add git-http-backend integration test · Entire

Add git-http-backend integration test

c05370d→main·

Soph·3mo ago·3 files·+239 added/-8

Sessions

07ebffc93812View transcript

[?
Can you take a look at the go code (wasm) in /Users/soph/Work/entire/devenv/entire-io-worktree1 based a bit on that I wonder if something like this can be build:Codex·GPT-5.4·1 step](/content/gh/entireio/git-sync/session/019d6d29-8cf7-7fe3-adc9-8c3e4d9d5603#timeline-07ebffc93812/index.html)

Changes

3

201 unmodified lines

202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220

201 unmodified lines

- The main transfer and negotiation win is on the source side. That is where `ls-refs` and `fetch` reduce unnecessary work.

In other words, this project uses protocol v2 where it changes the fetch/list behavior in a useful way, and keeps the current push path where switching protocols would mostly add complexity without a comparable payoff.

## Testing

The default test suite uses in-process smart HTTP servers and does not require a local listener:

```bash
env GOCACHE=/tmp/go-build go test ./...
```

There is also an optional end-to-end write test against the system `git-http-backend`:

```bash
env GOCACHE=/tmp/go-build GITSYNC_E2E_GIT_HTTP_BACKEND=1 go test ./internal/syncer -run TestRun_GitHTTPBackendSync -v
```

That path exercises real smart HTTP fetch and push with a local bare source repo and a local bare target repo.

MREADME.md+16

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151

package syncer

import (
    "context"
    "net/http"
    "net/http/cgi"
    "net/http/httptest"
    "os"
    "os/exec"
    "path/filepath"
    "strings"
    "testing"

"github.com/go-git/go-git/v5/plumbing"
)

const gitHTTPBackendEnv = "GITSYNC_E2E_GIT_HTTP_BACKEND"

func TestRun_GitHTTPBackendSync(t *testing.T) {
    if os.Getenv(gitHTTPBackendEnv) == "" {
        t.Skip("set GITSYNC_E2E_GIT_HTTP_BACKEND=1 to run git-http-backend integration test")
    }

if _, err := exec.LookPath("git"); err != nil {
        t.Skipf("git not available: %v", err)
    }

root := t.TempDir()
    sourceBare := filepath.Join(root, "source.git")
    targetBare := filepath.Join(root, "target.git")
    worktree := filepath.Join(root, "work")

runGit(t, root, "init", "--bare", sourceBare)
    runGit(t, root, "init", "--bare", targetBare)
    runGit(t, targetBare, "config", "http.receivepack", "true")
    runGit(t, root, "init", "-b", testBranch, worktree)
    runGit(t, worktree, "config", "user.name", "git-sync test")
    runGit(t, worktree, "config", "user.email", "git-sync@example.com")

writeFile(t, filepath.Join(worktree, "README.md"), "one\n")
    runGit(t, worktree, "add", "README.md")
    runGit(t, worktree, "commit", "-m", "initial")
    runGit(t, worktree, "remote", "add", "origin", sourceBare)
    runGit(t, worktree, "push", "origin", "HEAD:refs/heads/"+testBranch)

server := newGitHTTPBackendServer(t, root)
    defer server.Close()

sourceURL := server.RepoURL("source.git")
    targetURL := server.RepoURL("target.git")

result, err := Run(context.Background(), Config{
        Source: Endpoint{URL: sourceURL},
        Target: Endpoint{URL: targetURL},
    })
    if err != nil {
            t.Fatalf("initial sync failed: %v", err)
    }
    if result.Pushed != 1 || result.Blocked != 0 {
            t.Fatalf("unexpected initial result: %+v", result)
    }

assertGitRefEqual(t, sourceBare, targetBare, plumbing.NewBranchReferenceName(testBranch))

writeFile(t, filepath.Join(worktree, "README.md"), "one\ntwo\n")
    runGit(t, worktree, "add", "README.md")
    runGit(t, worktree, "commit", "-m", "second")
    runGit(t, worktree, "push", "origin", "HEAD:refs/heads/"+testBranch)

result, err = Run(context.Background(), Config{
        Source: Endpoint{URL: sourceURL},
        Target: Endpoint{URL: targetURL},
    })
    if err != nil {
            t.Fatalf("incremental sync failed: %v", err)
    }
    if result.Pushed != 1 || result.Blocked != 0 {
            t.Fatalf("unexpected incremental result: %+v", result)
    }

assertGitRefEqual(t, sourceBare, targetBare, plumbing.NewBranchReferenceName(testBranch))
}

type gitHTTPBackendServer struct {
    server *httptest.Server
    root   string
}

func newGitHTTPBackendServer(t *testing.T, root string) *gitHTTPBackendServer {
    t.Helper()

gitPath, err := exec.LookPath("git")
    if err != nil {
            t.Fatalf("find git: %v", err)
    }

handler := &cgi.Handler{
        Path: gitPath,
        Args: []string{"http-backend"},
        Env: []string{
            "GIT_PROJECT_ROOT=" + root,
            "GIT_HTTP_EXPORT_ALL=1",
        },
    }

return &gitHTTPBackendServer{
        server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            handler.ServeHTTP(w, r)
        })),
        root: root,
    }
}

func (s *gitHTTPBackendServer) Close() {
    s.server.Close()
}

func (s *gitHTTPBackendServer) RepoURL(name string) string {
    return s.server.URL + "/" + strings.TrimPrefix(name, "/")
}

func runGit(t *testing.T, dir string, args ...string) string {
    t.Helper()

cmd := exec.Command("git", args...)
    cmd.Dir = dir
    cmd.Env = append(os.Environ(),
        "GIT_TERMINAL_PROMPT=0",
    )
    output, err := cmd.CombinedOutput()
    if err != nil {
            t.Fatalf("git %s failed: %v\n%s", strings.Join(args, " "), err, output)
    }
    return string(output)
}

func writeFile(t *testing.T, path, contents string) {
    t.Helper()
    if err := os.WriteFile(path, []byte(contents), 0o644); err != nil {
            t.Fatalf("write %s: %v", path, err)
    }
}

func assertGitRefEqual(t *testing.T, sourceRepoPath, targetRepoPath string, ref plumbing.ReferenceName) {
    t.Helper()
    sourceHash := strings.TrimSpace(runGit(t, sourceRepoPath, "rev-parse", ref.String()))
    targetHash := strings.TrimSpace(runGit(t, targetRepoPath, "rev-parse", ref.String()))
    if sourceHash != targetHash {
            t.Fatalf("ref mismatch for %s: source=%s target=%s", ref, sourceHash, targetHash)
    }
}