1
0
Fork 0
mirror of https://github.com/imjasonh/infinite-git synced 2026-07-09 17:39:03 +00:00
infinite-git/internal/packfile/packfile.go
Jason Hall 30ca57dae3
Implement infinite Git HTTP server
This server generates a new commit every time someone pulls from it.
Features:
- Pure Go implementation of Git object format and protocols
- No dependency on git CLI commands
- Thread-safe commit generation
- Read-only Git HTTP smart protocol support
- Comprehensive test suite

Each pull creates a new commit with a unique file containing a timestamp
and counter, making the repository grow infinitely with each pull operation.

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-23 21:19:02 -04:00

171 lines
3.6 KiB
Go

package packfile
import (
"bytes"
"compress/zlib"
"crypto/sha1"
"encoding/binary"
"fmt"
"hash"
"io"
)
const (
// Object types in packfile
OBJ_COMMIT = 1
OBJ_TREE = 2
OBJ_BLOB = 3
OBJ_TAG = 4
)
// Writer writes a packfile.
type Writer struct {
buf bytes.Buffer
objects int
hash hash.Hash
}
// NewWriter creates a new packfile writer.
func NewWriter() *Writer {
w := &Writer{
hash: sha1.New(),
}
// Write pack header
w.buf.WriteString("PACK")
binary.Write(&w.buf, binary.BigEndian, uint32(2)) // version
binary.Write(&w.buf, binary.BigEndian, uint32(0)) // placeholder for object count
return w
}
// AddObject adds an object to the packfile.
func (w *Writer) AddObject(objType int, data []byte) error {
w.objects++
// Encode object header
// Format: 1-bit continuation, 3-bit type, 4-bit size (then 7-bit size chunks)
size := len(data)
header := (objType << 4) | (size & 0xf)
size >>= 4
for size > 0 {
header |= 0x80 // Set continuation bit
w.buf.WriteByte(byte(header))
header = size & 0x7f
size >>= 7
}
w.buf.WriteByte(byte(header))
// Compress and write object data
var compressedBuf bytes.Buffer
zw := zlib.NewWriter(&compressedBuf)
if _, err := zw.Write(data); err != nil {
return fmt.Errorf("compressing object: %w", err)
}
if err := zw.Close(); err != nil {
return fmt.Errorf("closing compressor: %w", err)
}
w.buf.Write(compressedBuf.Bytes())
return nil
}
// Finalize completes the packfile and returns the data.
func (w *Writer) Finalize() []byte {
data := w.buf.Bytes()
// Update object count in header
binary.BigEndian.PutUint32(data[8:12], uint32(w.objects))
// Calculate and append checksum
w.hash.Write(data)
checksum := w.hash.Sum(nil)
result := append(data, checksum...)
return result
}
// Reader reads objects from a packfile.
type Reader struct {
data []byte
offset int
}
// NewReader creates a new packfile reader.
func NewReader(data []byte) (*Reader, error) {
if len(data) < 12 {
return nil, fmt.Errorf("packfile too small")
}
if string(data[:4]) != "PACK" {
return nil, fmt.Errorf("invalid packfile signature")
}
version := binary.BigEndian.Uint32(data[4:8])
if version != 2 {
return nil, fmt.Errorf("unsupported packfile version: %d", version)
}
return &Reader{
data: data,
offset: 12, // Skip header
}, nil
}
// readVarint reads a variable-length integer.
func (r *Reader) readVarint() (int, int, error) {
if r.offset >= len(r.data) {
return 0, 0, io.EOF
}
b := r.data[r.offset]
r.offset++
objType := (int(b) >> 4) & 0x7
size := int(b) & 0xf
shift := 4
for b&0x80 != 0 {
if r.offset >= len(r.data) {
return 0, 0, io.EOF
}
b = r.data[r.offset]
r.offset++
size |= (int(b) & 0x7f) << shift
shift += 7
}
return objType, size, nil
}
// ReadObject reads the next object from the packfile.
func (r *Reader) ReadObject() (objType int, data []byte, err error) {
// Read object header
objType, size, err := r.readVarint()
if err != nil {
return 0, nil, err
}
// Read compressed data
zr, err := zlib.NewReader(bytes.NewReader(r.data[r.offset:]))
if err != nil {
return 0, nil, fmt.Errorf("creating decompressor: %w", err)
}
defer zr.Close()
data = make([]byte, size)
if _, err := io.ReadFull(zr, data); err != nil {
return 0, nil, fmt.Errorf("decompressing object: %w", err)
}
// Update offset past compressed data
// This is tricky - we need to figure out how much compressed data we read
// For now, we'll use a simple approach and skip this optimization
// In a real implementation, we'd track the compressed size properly
// For now, just consume the rest
var buf bytes.Buffer
io.Copy(&buf, zr)
return objType, data, nil
}