Skip to content

Commit f42e19a

Browse files
authored
Merge pull request #13 from aymanbagabas/git-daemon
feat: add git daemon server and cli command
2 parents b67e7b2 + 843c634 commit f42e19a

File tree

4 files changed

+514
-1
lines changed

4 files changed

+514
-1
lines changed

cmd/gogit/daemon.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package main
2+
3+
import (
4+
"log"
5+
"net"
6+
"path/filepath"
7+
"strconv"
8+
9+
gitserver "github.com/go-git/cli/server/git"
10+
"github.com/go-git/go-billy/v6"
11+
"github.com/go-git/go-billy/v6/osfs"
12+
gitbackend "github.com/go-git/go-git/v6/backend/git"
13+
"github.com/go-git/go-git/v6/plumbing/transport"
14+
"github.com/go-git/go-git/v6/storage"
15+
"github.com/spf13/cobra"
16+
)
17+
18+
var (
19+
daemonExportAll bool
20+
daemonPort int
21+
daemonListen string
22+
)
23+
24+
func init() {
25+
daemonCmd.Flags().BoolVarP(&daemonExportAll, "export-all", "", false, "Export all repositories")
26+
daemonCmd.Flags().IntVarP(&daemonPort, "port", "", 9418, "Port to run the Git daemon on")
27+
daemonCmd.Flags().StringVarP(&daemonListen, "listen", "", "", "Address to listen on (default: all interfaces)")
28+
29+
rootCmd.AddCommand(daemonCmd)
30+
}
31+
32+
var daemonCmd = &cobra.Command{
33+
Use: "daemon [<options>] [<directory>...]",
34+
Short: "Start a Git daemon server",
35+
RunE: func(cmd *cobra.Command, args []string) error {
36+
var dirs []string
37+
if len(args) == 0 {
38+
dirs = append(dirs, ".")
39+
}
40+
41+
loader := NewDirsLoader(dirs, false, daemonExportAll)
42+
addr := net.JoinHostPort(daemonListen, strconv.Itoa(daemonPort))
43+
be := gitbackend.NewBackend(loader)
44+
srv := &gitserver.Server{
45+
Addr: addr,
46+
Handler: gitserver.LoggingMiddleware(log.Default(), be),
47+
ErrorLog: log.Default(),
48+
}
49+
50+
log.Printf("Starting Git daemon on %q", addr)
51+
return srv.ListenAndServe()
52+
},
53+
}
54+
55+
type dirsLoader struct {
56+
loaders []transport.Loader
57+
fss []billy.Filesystem
58+
exportAll bool
59+
}
60+
61+
var _ transport.Loader = (*dirsLoader)(nil)
62+
63+
// NewDirsLoader creates a new dirsLoader with the given directories.
64+
func NewDirsLoader(dirs []string, strict, exportAll bool) *dirsLoader {
65+
var loaders []transport.Loader
66+
var fss []billy.Filesystem
67+
for _, dir := range dirs {
68+
abs, err := filepath.Abs(dir)
69+
if err != nil {
70+
continue
71+
}
72+
fs := osfs.New(abs, osfs.WithBoundOS())
73+
fss = append(fss, fs)
74+
loaders = append(loaders, transport.NewFilesystemLoader(fs, strict))
75+
}
76+
return &dirsLoader{loaders: loaders, fss: fss, exportAll: exportAll}
77+
}
78+
79+
// Load implements transport.Loader.
80+
func (d *dirsLoader) Load(ep *transport.Endpoint) (storage.Storer, error) {
81+
for i, loader := range d.loaders {
82+
storer, err := loader.Load(ep)
83+
if err == nil {
84+
if !d.exportAll {
85+
// We need to check if git-daemon-export-ok
86+
// file exists and if it does not, we skip this
87+
// repository.
88+
dfs := d.fss[i]
89+
okFile := filepath.Join(ep.Path, "git-daemon-export-ok")
90+
stat, err := dfs.Lstat(okFile)
91+
if err != nil || (stat != nil && !stat.Mode().IsRegular()) {
92+
// If the file does not exist or is a directory,
93+
// we skip this repository.
94+
continue
95+
}
96+
97+
}
98+
return storer, nil
99+
}
100+
}
101+
return nil, transport.ErrRepositoryNotFound
102+
}

cmd/gogit/main.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package main
22

33
import (
4+
"errors"
45
"fmt"
56
"os"
67
"strconv"
@@ -43,7 +44,12 @@ func init() {
4344

4445
func main() {
4546
if err := rootCmd.Execute(); err != nil {
46-
fmt.Fprintln(os.Stderr, err)
47+
var rerr *transport.RemoteError
48+
if errors.As(err, &rerr) {
49+
fmt.Fprintln(os.Stderr, rerr)
50+
} else {
51+
fmt.Fprintln(os.Stderr, err)
52+
}
4753
os.Exit(1)
4854
}
4955
}

server/git/logging.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package git
2+
3+
import (
4+
"context"
5+
"io"
6+
"time"
7+
8+
"github.com/go-git/go-git/v6/plumbing/protocol/packp"
9+
)
10+
11+
type Logger interface {
12+
Printf(format string, v ...interface{})
13+
}
14+
15+
func LoggingMiddleware(logger Logger, next Handler) HandlerFunc {
16+
return func(ctx context.Context, c io.ReadWriteCloser, r *packp.GitProtoRequest) {
17+
now := time.Now()
18+
next.ServeTCP(ctx, c, r)
19+
elapsedTime := time.Since(now)
20+
if logger != nil {
21+
logger.Printf("%s %s %s %v %v", r.Host, r.RequestCommand, r.Pathname, r.ExtraParams, elapsedTime)
22+
}
23+
}
24+
}

0 commit comments

Comments
 (0)