-
Notifications
You must be signed in to change notification settings - Fork 5
/
server.go
174 lines (136 loc) · 4.62 KB
/
server.go
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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
package gittp
import (
"bytes"
"errors"
"fmt"
"io"
"log"
"net/http"
"os"
"path/filepath"
)
// PreReceiveHook is a func called on pre receive. This is right before a git push is processed. Returning false from this handler will cancel the push to the remote, and returning true will allow the process to continue
type PreReceiveHook func(*HookContext) error
// PostReceiveHook is a func called after git-receive-pack is ran. This is a good place to fire notifications.
type PostReceiveHook func(*HookContext, []byte)
// PreCreateHook is a func called before a missing repository is created. Returning false from this handler will prevent a new repository from being created.
type PreCreateHook func(string) bool
// ServerConfig is a configuration object for NewGitServer
type ServerConfig struct {
// Path is the file path where pushed repositories are stored
Path string
// Enables debug logging
Debug bool
// PostReceive is a post receive hook that is ran after refs have been successfully processed. Useful for running automated builds, sending notifications etc.
PostReceive PostReceiveHook
// PreReceive is a pre receive hook that is ran before the repo is updated. Useful for enforcing branch naming (master only pushing).
PreReceive PreReceiveHook
// PreCreate is a hook called when a push causes a new repository to be created. This hook is ran before the repo is created.
PreCreate PreCreateHook
}
// NewGitServer initializes a new http.Handler that can serve to a git client over HTTP. An error is returned if the specified repositories path does not exist.
func NewGitServer(config ServerConfig) (http.Handler, error) {
config.Path, _ = filepath.Abs(config.Path)
if _, err := os.Stat(config.Path); os.IsNotExist(err) {
if err := os.MkdirAll(config.Path, os.ModeDir|os.ModePerm); err != nil {
return nil, errors.New("Could not create repository path")
}
}
if config.PreCreate == nil {
config.PreCreate = CreateRepo
}
if config.PreReceive == nil {
config.PreReceive = NoopPreReceive
}
return &gitHTTPServer{
config,
}, nil
}
type gitHTTPServer struct{ ServerConfig }
func (g *gitHTTPServer) ServeHTTP(res http.ResponseWriter, req *http.Request) {
if g.Debug {
log.Println(req.Method, req.URL)
}
header := res.Header()
header.Set("Cache-Control", "no-cache, max-age=0, must-revalidate")
header.Set("Expires", "Fri, 01 Jan 1980 00:00:00 GMT")
header.Set("Pragma", "no-cache")
header.Set("Server", "gittp")
header.Set("X-Frame-Options", "DENY")
ctx, err := newHandlerContext(res, req, g.Path)
if err != nil {
if g.Debug {
log.Println("could not create handler context", err)
}
res.WriteHeader(http.StatusInternalServerError)
return
}
header.Set("Content-Type", contentType(ctx.ServiceType, ctx.Advertisement))
if ctx.ShouldRunHooks {
ok, hookContinuation := g.runHooks(ctx)
if !ok {
return
}
ctx.Output = &bytes.Buffer{}
defer func() {
io.Copy(res, ctx.Output.(io.Reader))
}()
defer hookContinuation()
}
if err := g.createRepoIfMissing(ctx); err != nil {
res.WriteHeader(http.StatusNotFound)
return
}
if ctx.IsGetRefs {
svc := pktline(fmt.Sprintf("# service=%s\n", ctx.ServiceType))
ctx.Output.Write(svc)
ctx.Output.Write(pktline(""))
}
err = runCmd(ctx.ServiceType, ctx.FullRepoPath, ctx.Input, ctx.Output, ctx.Advertisement)
if err != nil {
if g.Debug {
log.Println("an error occurred running", ctx.ServiceType, err)
}
}
}
func (g *gitHTTPServer) runHooks(ctx handlerContext) (bool, func()) {
hookCtx := newHookContext(ctx)
flush := func() {}
if err := g.PreReceive(hookCtx); err != nil {
// statusHeader := pktline("unpack ok\n")
// reportStatus := pktline(fmt.Sprintf("ng %s %v\n", hookCtx.Branch, err))
// payload := fmt.Sprintf("%s%s", statusHeader, reportStatus)
// status := writePacket(fmt.Sprintf("\x01%s0000", payload))
// ctx.Output.Write(status)
return false, flush
}
if g.PostReceive != nil {
return true, func() {
defer flush()
// so we can get real time progress writes
archive, _ := gitArchive(ctx.FullRepoPath, hookCtx.Commit)
g.PostReceive(hookCtx, archive)
}
}
return true, flush
}
func (g *gitHTTPServer) createRepoIfMissing(ctx handlerContext) error {
if ctx.RepoExists {
return nil
}
shouldRunCreate := !ctx.RepoExists && ctx.Advertisement
if shouldRunCreate && g.PreCreate(ctx.RepoName) {
if err := initRepository(ctx.FullRepoPath); err != nil {
log.Println("Could not initialize repository", err)
return err
} else if g.Debug {
log.Println("creating repository")
}
} else {
if g.Debug {
log.Print("pushing is disallowed")
}
return errors.New("Cannot create repository")
}
return nil
}