mirror of https://git.jolheiser.com/ugit.git
parent
65f464aaca
commit
78f30f901e
|
@ -6,6 +6,12 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-git/go-git/v5/plumbing/protocol/packp"
|
||||||
|
"go.jolheiser.com/ugit/internal/git"
|
||||||
|
|
||||||
"go.jolheiser.com/ugit/internal/http"
|
"go.jolheiser.com/ugit/internal/http"
|
||||||
"go.jolheiser.com/ugit/internal/ssh"
|
"go.jolheiser.com/ugit/internal/ssh"
|
||||||
|
@ -16,6 +22,11 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
if len(os.Args) > 1 && os.Args[1] == "pre-receive-hook" {
|
||||||
|
preReceive()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
args, err := parseArgs(os.Args[1:])
|
args, err := parseArgs(os.Args[1:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, flag.ErrHelp) {
|
if errors.Is(err, flag.ErrHelp) {
|
||||||
|
@ -23,6 +34,10 @@ func main() {
|
||||||
}
|
}
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
args.RepoDir, err = filepath.Abs(args.RepoDir)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
if args.Debug {
|
if args.Debug {
|
||||||
trace.SetTarget(trace.Packet)
|
trace.SetTarget(trace.Packet)
|
||||||
|
@ -32,7 +47,7 @@ func main() {
|
||||||
ssh.DefaultLogger = ssh.NoopLogger
|
ssh.DefaultLogger = ssh.NoopLogger
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.MkdirAll(args.RepoDir, os.ModePerm); err != nil {
|
if err := requiredFS(args.RepoDir); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,3 +98,62 @@ func main() {
|
||||||
signal.Notify(ch, os.Kill, os.Interrupt)
|
signal.Notify(ch, os.Kill, os.Interrupt)
|
||||||
<-ch
|
<-ch
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func requiredFS(repoDir string) error {
|
||||||
|
if err := os.MkdirAll(repoDir, os.ModePerm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !git.RequiresHook {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
bin, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fp := filepath.Join(repoDir, "hooks")
|
||||||
|
if err := os.MkdirAll(fp, os.ModePerm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fp = filepath.Join(fp, "pre-receive")
|
||||||
|
|
||||||
|
fi, err := os.Create(fp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fi.WriteString("#!/usr/bin/env bash\n")
|
||||||
|
fi.WriteString(fmt.Sprintf("%s pre-receive-hook\n", bin))
|
||||||
|
fi.Close()
|
||||||
|
|
||||||
|
return os.Chmod(fp, 0o755)
|
||||||
|
}
|
||||||
|
|
||||||
|
func preReceive() {
|
||||||
|
repoDir, ok := os.LookupEnv("UGIT_REPODIR")
|
||||||
|
if !ok {
|
||||||
|
panic("UGIT_REPODIR is not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := make([]*packp.Option, 0)
|
||||||
|
if pushCount, err := strconv.Atoi(os.Getenv("GIT_PUSH_OPTION_COUNT")); err == nil {
|
||||||
|
for idx := 0; idx < pushCount; idx++ {
|
||||||
|
opt := os.Getenv(fmt.Sprintf("GIT_PUSH_OPTION_%d", idx))
|
||||||
|
kv := strings.SplitN(opt, "=", 2)
|
||||||
|
if len(kv) == 2 {
|
||||||
|
opts = append(opts, &packp.Option{
|
||||||
|
Key: kv[0],
|
||||||
|
Value: kv[1],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repo, err := git.NewRepo(filepath.Dir(repoDir), filepath.Base(repoDir))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if err := git.HandlePushOptions(repo, opts); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,23 +1,15 @@
|
||||||
package git
|
package git
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/go-git/go-billy/v5/osfs"
|
|
||||||
"github.com/go-git/go-git/v5"
|
"github.com/go-git/go-git/v5"
|
||||||
"github.com/go-git/go-git/v5/plumbing/format/pktline"
|
|
||||||
"github.com/go-git/go-git/v5/plumbing/protocol/packp"
|
"github.com/go-git/go-git/v5/plumbing/protocol/packp"
|
||||||
"github.com/go-git/go-git/v5/plumbing/protocol/packp/capability"
|
|
||||||
"github.com/go-git/go-git/v5/plumbing/serverinfo"
|
"github.com/go-git/go-git/v5/plumbing/serverinfo"
|
||||||
"github.com/go-git/go-git/v5/plumbing/transport"
|
|
||||||
"github.com/go-git/go-git/v5/plumbing/transport/server"
|
|
||||||
"github.com/go-git/go-git/v5/storage/filesystem"
|
"github.com/go-git/go-git/v5/storage/filesystem"
|
||||||
"github.com/go-git/go-git/v5/utils/ioutil"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ReadWriteContexter is the interface required to operate on git protocols
|
// ReadWriteContexter is the interface required to operate on git protocols
|
||||||
|
@ -26,179 +18,25 @@ type ReadWriteContexter interface {
|
||||||
Context() context.Context
|
Context() context.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
// Protocol handles the endpoint and server of the git protocols
|
type Protocoler interface {
|
||||||
type Protocol struct {
|
HTTPInfoRefs(ReadWriteContexter) error
|
||||||
endpoint *transport.Endpoint
|
HTTPUploadPack(ReadWriteContexter) error
|
||||||
server transport.Transport
|
SSHUploadPack(ReadWriteContexter) error
|
||||||
|
SSHReceivePack(ReadWriteContexter, *Repo) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewProtocol constructs a Protocol for a given repo
|
// UpdateServerInfo handles updating server info for the git repo
|
||||||
func NewProtocol(repoPath string) (Protocol, error) {
|
func UpdateServerInfo(repo string) error {
|
||||||
endpoint, err := transport.NewEndpoint("/")
|
r, err := git.PlainOpen(repo)
|
||||||
if err != nil {
|
|
||||||
return Protocol{}, err
|
|
||||||
}
|
|
||||||
fs := osfs.New(repoPath)
|
|
||||||
loader := server.NewFilesystemLoader(fs)
|
|
||||||
gitServer := server.NewServer(loader)
|
|
||||||
return Protocol{
|
|
||||||
endpoint: endpoint,
|
|
||||||
server: gitServer,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTPInfoRefs handles the inforef part of the HTTP protocol
|
|
||||||
func (p Protocol) HTTPInfoRefs(rwc ReadWriteContexter) error {
|
|
||||||
session, err := p.server.NewUploadPackSession(p.endpoint, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer ioutil.CheckClose(rwc, &err)
|
fs := r.Storer.(*filesystem.Storage).Filesystem()
|
||||||
return p.infoRefs(rwc, session, "# service=git-upload-pack")
|
return serverinfo.UpdateServerInfo(r.Storer, fs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Protocol) infoRefs(rwc ReadWriteContexter, session transport.UploadPackSession, prefix string) error {
|
// HandlePushOptions handles all relevant push options for a [Repo] and saves the new [RepoMeta]
|
||||||
ar, err := session.AdvertisedReferencesContext(rwc.Context())
|
func HandlePushOptions(repo *Repo, opts []*packp.Option) error {
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if prefix != "" {
|
|
||||||
ar.Prefix = [][]byte{
|
|
||||||
[]byte(prefix),
|
|
||||||
pktline.Flush,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := ar.Encode(rwc); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTPUploadPack handles the upload-pack process for HTTP
|
|
||||||
func (p Protocol) HTTPUploadPack(rwc ReadWriteContexter) error {
|
|
||||||
return p.uploadPack(rwc, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SSHUploadPack handles the upload-pack process for SSH
|
|
||||||
func (p Protocol) SSHUploadPack(rwc ReadWriteContexter) error {
|
|
||||||
return p.uploadPack(rwc, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p Protocol) uploadPack(rwc ReadWriteContexter, ssh bool) error {
|
|
||||||
session, err := p.server.NewUploadPackSession(p.endpoint, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer ioutil.CheckClose(rwc, &err)
|
|
||||||
|
|
||||||
if ssh {
|
|
||||||
if err := p.infoRefs(rwc, session, ""); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
req := packp.NewUploadPackRequest()
|
|
||||||
if err := req.Decode(rwc); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var resp *packp.UploadPackResponse
|
|
||||||
resp, err = session.UploadPack(rwc.Context(), req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := resp.Encode(rwc); err != nil {
|
|
||||||
return fmt.Errorf("could not encode upload pack: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SSHReceivePack handles the receive-pack process for SSH
|
|
||||||
func (p Protocol) SSHReceivePack(rwc ReadWriteContexter, repo *Repo) error {
|
|
||||||
buf := bufio.NewReader(rwc)
|
|
||||||
|
|
||||||
session, err := p.server.NewReceivePackSession(p.endpoint, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ar, err := session.AdvertisedReferencesContext(rwc.Context())
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("internal error in advertised references: %w", err)
|
|
||||||
}
|
|
||||||
_ = ar.Capabilities.Set(capability.PushOptions)
|
|
||||||
_ = ar.Capabilities.Set("no-thin")
|
|
||||||
|
|
||||||
if err := ar.Encode(rwc); err != nil {
|
|
||||||
return fmt.Errorf("error in advertised references encoding: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
req := packp.NewReferenceUpdateRequest()
|
|
||||||
_ = req.Capabilities.Set(capability.ReportStatus)
|
|
||||||
if err := req.Decode(buf); err != nil {
|
|
||||||
// FIXME this is a hack, but go-git doesn't accept a 0000 if there are no refs to update
|
|
||||||
if !strings.EqualFold(err.Error(), "capabilities delimiter not found") {
|
|
||||||
return fmt.Errorf("error decoding: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME also a hack, if the next bytes are PACK then we have a packfile, otherwise assume it's push options
|
|
||||||
peek, err := buf.Peek(4)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if string(peek) != "PACK" {
|
|
||||||
s := pktline.NewScanner(buf)
|
|
||||||
for s.Scan() {
|
|
||||||
val := string(s.Bytes())
|
|
||||||
if val == "" {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if s.Err() != nil {
|
|
||||||
return s.Err()
|
|
||||||
}
|
|
||||||
parts := strings.SplitN(val, "=", 2)
|
|
||||||
req.Options = append(req.Options, &packp.Option{
|
|
||||||
Key: parts[0],
|
|
||||||
Value: parts[1],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := handlePushOptions(repo, req.Options); err != nil {
|
|
||||||
return fmt.Errorf("could not handle push options: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME if there are only delete commands, there is no packfile and ReceivePack will block forever
|
|
||||||
noPack := true
|
|
||||||
for _, c := range req.Commands {
|
|
||||||
if c.Action() != packp.Delete {
|
|
||||||
noPack = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if noPack {
|
|
||||||
req.Packfile = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
rs, err := session.ReceivePack(rwc.Context(), req)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error in receive pack: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := rs.Encode(rwc); err != nil {
|
|
||||||
return fmt.Errorf("could not encode receive pack: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func handlePushOptions(repo *Repo, opts []*packp.Option) error {
|
|
||||||
var changed bool
|
var changed bool
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
switch strings.ToLower(opt.Key) {
|
switch strings.ToLower(opt.Key) {
|
||||||
|
@ -219,13 +57,3 @@ func handlePushOptions(repo *Repo, opts []*packp.Option) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateServerInfo handles updating server info for the git repo
|
|
||||||
func UpdateServerInfo(repo string) error {
|
|
||||||
r, err := git.PlainOpen(repo)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fs := r.Storer.(*filesystem.Storage).Filesystem()
|
|
||||||
return serverinfo.UpdateServerInfo(r.Storer, fs)
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
//go:build !gogit
|
||||||
|
|
||||||
|
package git
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/go-git/go-git/v5/plumbing/format/pktline"
|
||||||
|
)
|
||||||
|
|
||||||
|
var RequiresHook = true
|
||||||
|
|
||||||
|
type CmdProtocol string
|
||||||
|
|
||||||
|
func NewProtocol(repoPath string) (Protocoler, error) {
|
||||||
|
return CmdProtocol(repoPath), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c CmdProtocol) HTTPInfoRefs(ctx ReadWriteContexter) error {
|
||||||
|
pkt := pktline.NewEncoder(ctx)
|
||||||
|
if err := pkt.EncodeString("# service=git-upload-pack"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := pkt.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return gitService(ctx, "receive-pack", string(c), "--stateless-rpc", "--advertise-refs")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c CmdProtocol) HTTPUploadPack(ctx ReadWriteContexter) error {
|
||||||
|
return gitService(ctx, "upload-pack", string(c), "--stateless-rpc")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c CmdProtocol) SSHUploadPack(ctx ReadWriteContexter) error {
|
||||||
|
return gitService(ctx, "upload-pack", string(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c CmdProtocol) SSHReceivePack(ctx ReadWriteContexter, _ *Repo) error {
|
||||||
|
return gitService(ctx, "receive-pack", string(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
func gitService(ctx ReadWriteContexter, command, repoDir string, args ...string) error {
|
||||||
|
cmd := exec.CommandContext(ctx.Context(), "git")
|
||||||
|
cmd.Args = append(cmd.Args, []string{
|
||||||
|
"-c", "uploadpack.allowFilter=true",
|
||||||
|
"-c", "receive.advertisePushOptions=true",
|
||||||
|
"-c", fmt.Sprintf("core.hooksPath=%s", filepath.Join(filepath.Dir(repoDir), "hooks")),
|
||||||
|
command,
|
||||||
|
}...)
|
||||||
|
if len(args) > 0 {
|
||||||
|
cmd.Args = append(cmd.Args, args...)
|
||||||
|
}
|
||||||
|
cmd.Args = append(cmd.Args, repoDir)
|
||||||
|
cmd.Env = append(os.Environ(), fmt.Sprintf("UGIT_REPODIR=%s", repoDir))
|
||||||
|
cmd.Stdin = ctx
|
||||||
|
cmd.Stdout = ctx
|
||||||
|
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
|
@ -0,0 +1,191 @@
|
||||||
|
//go:build gogit
|
||||||
|
|
||||||
|
package git
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-git/go-billy/v5/osfs"
|
||||||
|
"github.com/go-git/go-git/v5/plumbing/format/pktline"
|
||||||
|
"github.com/go-git/go-git/v5/plumbing/protocol/packp"
|
||||||
|
"github.com/go-git/go-git/v5/plumbing/protocol/packp/capability"
|
||||||
|
"github.com/go-git/go-git/v5/plumbing/transport"
|
||||||
|
"github.com/go-git/go-git/v5/plumbing/transport/server"
|
||||||
|
"github.com/go-git/go-git/v5/utils/ioutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
var RequiresHook = false
|
||||||
|
|
||||||
|
// Protocol handles the endpoint and server of the git protocols
|
||||||
|
type Protocol struct {
|
||||||
|
endpoint *transport.Endpoint
|
||||||
|
server transport.Transport
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewProtocol constructs a Protocol for a given repo
|
||||||
|
func NewProtocol(repoPath string) (Protocoler, error) {
|
||||||
|
endpoint, err := transport.NewEndpoint("/")
|
||||||
|
if err != nil {
|
||||||
|
return Protocol{}, err
|
||||||
|
}
|
||||||
|
fs := osfs.New(repoPath)
|
||||||
|
loader := server.NewFilesystemLoader(fs)
|
||||||
|
gitServer := server.NewServer(loader)
|
||||||
|
return Protocol{
|
||||||
|
endpoint: endpoint,
|
||||||
|
server: gitServer,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPInfoRefs handles the inforef part of the HTTP protocol
|
||||||
|
func (p Protocol) HTTPInfoRefs(rwc ReadWriteContexter) error {
|
||||||
|
session, err := p.server.NewUploadPackSession(p.endpoint, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer ioutil.CheckClose(rwc, &err)
|
||||||
|
return p.infoRefs(rwc, session, "# service=git-upload-pack")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Protocol) infoRefs(rwc ReadWriteContexter, session transport.UploadPackSession, prefix string) error {
|
||||||
|
ar, err := session.AdvertisedReferencesContext(rwc.Context())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if prefix != "" {
|
||||||
|
ar.Prefix = [][]byte{
|
||||||
|
[]byte(prefix),
|
||||||
|
pktline.Flush,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ar.Encode(rwc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPUploadPack handles the upload-pack process for HTTP
|
||||||
|
func (p Protocol) HTTPUploadPack(rwc ReadWriteContexter) error {
|
||||||
|
return p.uploadPack(rwc, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSHUploadPack handles the upload-pack process for SSH
|
||||||
|
func (p Protocol) SSHUploadPack(rwc ReadWriteContexter) error {
|
||||||
|
return p.uploadPack(rwc, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Protocol) uploadPack(rwc ReadWriteContexter, ssh bool) error {
|
||||||
|
session, err := p.server.NewUploadPackSession(p.endpoint, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer ioutil.CheckClose(rwc, &err)
|
||||||
|
|
||||||
|
if ssh {
|
||||||
|
if err := p.infoRefs(rwc, session, ""); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
req := packp.NewUploadPackRequest()
|
||||||
|
if err := req.Decode(rwc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp *packp.UploadPackResponse
|
||||||
|
resp, err = session.UploadPack(rwc.Context(), req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := resp.Encode(rwc); err != nil {
|
||||||
|
return fmt.Errorf("could not encode upload pack: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSHReceivePack handles the receive-pack process for SSH
|
||||||
|
func (p Protocol) SSHReceivePack(rwc ReadWriteContexter, repo *Repo) error {
|
||||||
|
buf := bufio.NewReader(rwc)
|
||||||
|
|
||||||
|
session, err := p.server.NewReceivePackSession(p.endpoint, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ar, err := session.AdvertisedReferencesContext(rwc.Context())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("internal error in advertised references: %w", err)
|
||||||
|
}
|
||||||
|
_ = ar.Capabilities.Set(capability.PushOptions)
|
||||||
|
_ = ar.Capabilities.Set("no-thin")
|
||||||
|
|
||||||
|
if err := ar.Encode(rwc); err != nil {
|
||||||
|
return fmt.Errorf("error in advertised references encoding: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req := packp.NewReferenceUpdateRequest()
|
||||||
|
_ = req.Capabilities.Set(capability.ReportStatus)
|
||||||
|
if err := req.Decode(buf); err != nil {
|
||||||
|
// FIXME this is a hack, but go-git doesn't accept a 0000 if there are no refs to update
|
||||||
|
if !strings.EqualFold(err.Error(), "capabilities delimiter not found") {
|
||||||
|
return fmt.Errorf("error decoding: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME also a hack, if the next bytes are PACK then we have a packfile, otherwise assume it's push options
|
||||||
|
peek, err := buf.Peek(4)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if string(peek) != "PACK" {
|
||||||
|
s := pktline.NewScanner(buf)
|
||||||
|
for s.Scan() {
|
||||||
|
val := string(s.Bytes())
|
||||||
|
if val == "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if s.Err() != nil {
|
||||||
|
return s.Err()
|
||||||
|
}
|
||||||
|
parts := strings.SplitN(val, "=", 2)
|
||||||
|
req.Options = append(req.Options, &packp.Option{
|
||||||
|
Key: parts[0],
|
||||||
|
Value: parts[1],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := HandlePushOptions(repo, req.Options); err != nil {
|
||||||
|
return fmt.Errorf("could not handle push options: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME if there are only delete commands, there is no packfile and ReceivePack will block forever
|
||||||
|
noPack := true
|
||||||
|
for _, c := range req.Commands {
|
||||||
|
if c.Action() != packp.Delete {
|
||||||
|
noPack = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if noPack {
|
||||||
|
req.Packfile = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rs, err := session.ReceivePack(rwc.Context(), req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error in receive pack: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rs.Encode(rwc); err != nil {
|
||||||
|
return fmt.Errorf("could not encode receive pack: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -6,11 +6,12 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go.jolheiser.com/ugit/internal/html/markup"
|
|
||||||
"go/format"
|
"go/format"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
||||||
|
"go.jolheiser.com/ugit/internal/html/markup"
|
||||||
|
|
||||||
"github.com/alecthomas/chroma/v2/styles"
|
"github.com/alecthomas/chroma/v2/styles"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -3,12 +3,13 @@ package markup
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"golang.org/x/net/html"
|
|
||||||
"io"
|
"io"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/net/html"
|
||||||
|
|
||||||
"go.jolheiser.com/ugit/internal/git"
|
"go.jolheiser.com/ugit/internal/git"
|
||||||
|
|
||||||
chromahtml "github.com/alecthomas/chroma/v2/formatters/html"
|
chromahtml "github.com/alecthomas/chroma/v2/formatters/html"
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.jolheiser.com/ugit/internal/git"
|
"go.jolheiser.com/ugit/internal/git"
|
||||||
|
@ -19,6 +20,9 @@ func (rh repoHandler) index(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
|
||||||
repos := make([]*git.Repo, 0, len(repoPaths))
|
repos := make([]*git.Repo, 0, len(repoPaths))
|
||||||
for _, repoName := range repoPaths {
|
for _, repoName := range repoPaths {
|
||||||
|
if !strings.HasSuffix(repoName.Name(), ".git") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
repo, err := git.NewRepo(rh.s.RepoDir, repoName.Name())
|
repo, err := git.NewRepo(rh.s.RepoDir, repoName.Name())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httperr.Error(err)
|
return httperr.Error(err)
|
||||||
|
|
|
@ -3,11 +3,12 @@ package http
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"go.jolheiser.com/ugit/internal/html/markup"
|
|
||||||
"mime"
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"go.jolheiser.com/ugit/internal/html/markup"
|
||||||
|
|
||||||
"go.jolheiser.com/ugit/internal/git"
|
"go.jolheiser.com/ugit/internal/git"
|
||||||
"go.jolheiser.com/ugit/internal/html"
|
"go.jolheiser.com/ugit/internal/html"
|
||||||
"go.jolheiser.com/ugit/internal/http/httperr"
|
"go.jolheiser.com/ugit/internal/http/httperr"
|
||||||
|
|
Loading…
Reference in New Issue