shithub: furgit

Download patch

ref: 33fda1b8e4da0ad9d4208a8b8249c8d7b305f4ae
parent: f329f9e6ddc0f47a29df41082f4f12b9466bdac7
author: Runxi Yu <me@runxiyu.org>
date: Sat Mar 7 20:56:58 EST 2026

cmd/receivepack9418: Init

--- /dev/null
+++ b/cmd/receivepack9418/conn.go
@@ -1,0 +1,65 @@
+package main
+
+import (
+	"bufio"
+	"context"
+	"fmt"
+	"log"
+	"net"
+	"strings"
+
+	"codeberg.org/lindenii/furgit/receivepack"
+)
+
+func (srv *server) handleConn(conn net.Conn) {
+	defer func() { _ = conn.Close() }()
+
+	reader := bufio.NewReader(conn)
+	writer := bufio.NewWriter(conn)
+
+	req, err := readGitProtoRequest(reader)
+	if err != nil {
+		writeErrPkt(writer, fmt.Sprintf("invalid initial request: %v", err))
+		_ = writer.Flush()
+		log.Printf("receivepack9418: %s: invalid initial request: %v", conn.RemoteAddr(), err)
+		return
+	}
+
+	if req.Command != "git-receive-pack" {
+		writeErrPkt(writer, fmt.Sprintf("unsupported command %q", req.Command))
+		_ = writer.Flush()
+		log.Printf("receivepack9418: %s: unsupported command %q", conn.RemoteAddr(), req.Command)
+		return
+	}
+
+	gitProtocol := strings.Join(req.ExtraParameters, ":")
+
+	opts := receivepack.Options{
+		GitProtocol:     gitProtocol,
+		Algorithm:       srv.repo.Algorithm(),
+		Refs:            srv.repo.Refs(),
+		ExistingObjects: srv.repo.Objects(),
+		ObjectsRoot:     srv.objectsRoot,
+	}
+
+	err = receivepack.ReceivePack(context.Background(), writer, reader, opts)
+	if err != nil {
+		_ = writer.Flush()
+		log.Printf(
+			"receivepack9418: %s: receive-pack failed (path=%q host=%q extras=%v): %v",
+			conn.RemoteAddr(),
+			req.Pathname,
+			req.Host,
+			req.ExtraParameters,
+			err,
+		)
+
+		return
+	}
+
+	err = writer.Flush()
+	if err != nil {
+		log.Printf("receivepack9418: %s: flush failed: %v", conn.RemoteAddr(), err)
+		return
+	}
+}
--- /dev/null
+++ b/cmd/receivepack9418/errpkt.go
@@ -1,0 +1,18 @@
+package main
+
+import (
+	"io"
+
+	"codeberg.org/lindenii/furgit/format/pktline"
+)
+
+func writeErrPkt(w io.Writer, message string) {
+	payload := []byte("ERR " + message + "\n")
+
+	frame, err := pktline.AppendData(nil, payload)
+	if err != nil {
+		return
+	}
+
+	_, _ = w.Write(frame)
+}
--- /dev/null
+++ b/cmd/receivepack9418/gitproto.go
@@ -1,0 +1,23 @@
+package main
+
+import (
+	"fmt"
+	"io"
+
+	"codeberg.org/lindenii/furgit/format/pktline"
+)
+
+func readGitProtoRequest(r io.Reader) (gitProtoRequest, error) {
+	dec := pktline.NewDecoder(r, pktline.ReadOptions{})
+
+	frame, err := dec.ReadFrame()
+	if err != nil {
+		return gitProtoRequest{}, err
+	}
+
+	if frame.Type != pktline.PacketData {
+		return gitProtoRequest{}, fmt.Errorf("expected initial pkt-line data, got %v", frame.Type)
+	}
+
+	return parseGitProtoRequestPayload(frame.Payload)
+}
--- /dev/null
+++ b/cmd/receivepack9418/main.go
@@ -1,0 +1,23 @@
+// Command receivepack9418 serves one fixed repository over git:// receive-pack on TCP 9418.
+package main
+
+import (
+	"flag"
+	"log"
+)
+
+func main() {
+	listenAddr := flag.String("listen", ":9418", "listen address")
+	repoPath := flag.String("repo", "", "path to git dir (.git or bare repo root)")
+
+	flag.Parse()
+
+	if *repoPath == "" {
+		log.Fatal("must provide -repo <path-to-git-dir>")
+	}
+
+	err := run(*listenAddr, *repoPath)
+	if err != nil {
+		log.Fatalf("run: %v", err)
+	}
+}
--- /dev/null
+++ b/cmd/receivepack9418/request.go
@@ -1,0 +1,60 @@
+package main
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+	"strings"
+)
+
+type gitProtoRequest struct {
+	Command         string
+	Pathname        string
+	Host            string
+	ExtraParameters []string
+}
+
+func parseGitProtoRequestPayload(payload []byte) (gitProtoRequest, error) {
+	parts := bytes.Split(payload, []byte{0})
+	if len(parts) == 0 || len(parts[0]) == 0 {
+		return gitProtoRequest{}, errors.New("missing command/path segment")
+	}
+
+	commandPath := string(parts[0])
+	command, pathname, ok := strings.Cut(commandPath, " ")
+	if !ok || command == "" || pathname == "" {
+		return gitProtoRequest{}, fmt.Errorf("malformed command/path segment %q", commandPath)
+	}
+
+	req := gitProtoRequest{
+		Command:  command,
+		Pathname: pathname,
+	}
+
+	i := 1
+	if i < len(parts) && strings.HasPrefix(string(parts[i]), "host=") {
+		req.Host = strings.TrimPrefix(string(parts[i]), "host=")
+		i++
+	}
+
+	// No tail left.
+	if i >= len(parts) {
+		return req, nil
+	}
+
+	// If there is tail, grammar requires one empty field before extras.
+	if len(parts[i]) != 0 {
+		return gitProtoRequest{}, fmt.Errorf("unexpected token %q after host/path", string(parts[i]))
+	}
+
+	i++
+	for ; i < len(parts); i++ {
+		if len(parts[i]) == 0 {
+			continue
+		}
+
+		req.ExtraParameters = append(req.ExtraParameters, string(parts[i]))
+	}
+
+	return req, nil
+}
--- /dev/null
+++ b/cmd/receivepack9418/run.go
@@ -1,0 +1,64 @@
+package main
+
+import (
+	"errors"
+	"fmt"
+	"log"
+	"net"
+	"os"
+
+	"codeberg.org/lindenii/furgit/repository"
+)
+
+func run(listenAddr, repoPath string) error {
+	repoRoot, err := os.OpenRoot(repoPath)
+	if err != nil {
+		return fmt.Errorf("open repo root: %w", err)
+	}
+	defer func() { _ = repoRoot.Close() }()
+
+	repo, err := repository.Open(repoRoot)
+	if err != nil {
+		return fmt.Errorf("open repository: %w", err)
+	}
+	defer func() { _ = repo.Close() }()
+
+	objectsRoot, err := repoRoot.OpenRoot("objects")
+	if err != nil {
+		return fmt.Errorf("open objects root: %w", err)
+	}
+	defer func() { _ = objectsRoot.Close() }()
+
+	srv := &server{
+		repo:        repo,
+		objectsRoot: objectsRoot,
+	}
+
+	ln, err := net.Listen("tcp", listenAddr)
+	if err != nil {
+		return fmt.Errorf("listen %q: %w", listenAddr, err)
+	}
+	defer func() { _ = ln.Close() }()
+
+	log.Printf("receivepack9418: listening on %s", listenAddr)
+	log.Printf("receivepack9418: repository=%s algorithm=%s", repoPath, repo.Algorithm())
+
+	for {
+		conn, err := ln.Accept()
+		if err != nil {
+			if errors.Is(err, net.ErrClosed) {
+				return nil
+			}
+
+			var nerr net.Error
+			if errors.As(err, &nerr) && nerr.Temporary() {
+				log.Printf("receivepack9418: temporary accept error: %v", err)
+				continue
+			}
+
+			return fmt.Errorf("accept: %w", err)
+		}
+
+		go srv.handleConn(conn)
+	}
+}
--- /dev/null
+++ b/cmd/receivepack9418/server.go
@@ -1,0 +1,12 @@
+package main
+
+import (
+	"os"
+
+	"codeberg.org/lindenii/furgit/repository"
+)
+
+type server struct {
+	repo        *repository.Repository
+	objectsRoot *os.Root
+}
--