174 lines
4.8 KiB
Go
174 lines
4.8 KiB
Go
package models
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"time"
|
|
)
|
|
|
|
// OplogEntry represents a single entry in the action log
|
|
type OplogEntry struct {
|
|
// ID is a monotonically increasing entry ID
|
|
ID uint64
|
|
|
|
// Timestamp when the operation was performed
|
|
Timestamp time.Time
|
|
|
|
// Operation type (e.g., "save", "switch", "new", "sync")
|
|
Operation string
|
|
|
|
// Description of the operation
|
|
Description string
|
|
|
|
// StateBefore captures the state before the operation
|
|
StateBefore *RepositoryState
|
|
|
|
// StateAfter captures the state after the operation
|
|
StateAfter *RepositoryState
|
|
|
|
// Metadata contains operation-specific data
|
|
Metadata map[string]string
|
|
}
|
|
|
|
// RepositoryState captures the state of the repository at a point in time
|
|
type RepositoryState struct {
|
|
// Refs maps reference names to their SHA-1 hashes
|
|
Refs map[string]string
|
|
|
|
// CurrentWorkstream is the active workstream name
|
|
CurrentWorkstream string
|
|
|
|
// WorkingTreeHash is the hash of the current working tree snapshot
|
|
WorkingTreeHash string
|
|
|
|
// IndexHash is the hash of the staging area
|
|
IndexHash string
|
|
}
|
|
|
|
// Serialize converts an OplogEntry to binary format
|
|
func (e *OplogEntry) Serialize() ([]byte, error) {
|
|
buf := new(bytes.Buffer)
|
|
|
|
// Write entry ID (8 bytes)
|
|
if err := binary.Write(buf, binary.LittleEndian, e.ID); err != nil {
|
|
return nil, fmt.Errorf("failed to write ID: %w", err)
|
|
}
|
|
|
|
// Write timestamp (8 bytes, Unix nano)
|
|
timestamp := e.Timestamp.UnixNano()
|
|
if err := binary.Write(buf, binary.LittleEndian, timestamp); err != nil {
|
|
return nil, fmt.Errorf("failed to write timestamp: %w", err)
|
|
}
|
|
|
|
// Serialize the rest as JSON for flexibility
|
|
payload := struct {
|
|
Operation string `json:"operation"`
|
|
Description string `json:"description"`
|
|
StateBefore *RepositoryState `json:"state_before"`
|
|
StateAfter *RepositoryState `json:"state_after"`
|
|
Metadata map[string]string `json:"metadata"`
|
|
}{
|
|
Operation: e.Operation,
|
|
Description: e.Description,
|
|
StateBefore: e.StateBefore,
|
|
StateAfter: e.StateAfter,
|
|
Metadata: e.Metadata,
|
|
}
|
|
|
|
jsonData, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal JSON: %w", err)
|
|
}
|
|
|
|
// Write JSON length (4 bytes)
|
|
jsonLen := uint32(len(jsonData))
|
|
if err := binary.Write(buf, binary.LittleEndian, jsonLen); err != nil {
|
|
return nil, fmt.Errorf("failed to write JSON length: %w", err)
|
|
}
|
|
|
|
// Write JSON data
|
|
if _, err := buf.Write(jsonData); err != nil {
|
|
return nil, fmt.Errorf("failed to write JSON data: %w", err)
|
|
}
|
|
|
|
return buf.Bytes(), nil
|
|
}
|
|
|
|
// Deserialize converts binary data back to an OplogEntry
|
|
func DeserializeOplogEntry(data []byte) (*OplogEntry, error) {
|
|
buf := bytes.NewReader(data)
|
|
|
|
entry := &OplogEntry{}
|
|
|
|
// Read entry ID (8 bytes)
|
|
if err := binary.Read(buf, binary.LittleEndian, &entry.ID); err != nil {
|
|
return nil, fmt.Errorf("failed to read ID: %w", err)
|
|
}
|
|
|
|
// Read timestamp (8 bytes)
|
|
var timestamp int64
|
|
if err := binary.Read(buf, binary.LittleEndian, ×tamp); err != nil {
|
|
return nil, fmt.Errorf("failed to read timestamp: %w", err)
|
|
}
|
|
entry.Timestamp = time.Unix(0, timestamp)
|
|
|
|
// Read JSON length (4 bytes)
|
|
var jsonLen uint32
|
|
if err := binary.Read(buf, binary.LittleEndian, &jsonLen); err != nil {
|
|
return nil, fmt.Errorf("failed to read JSON length: %w", err)
|
|
}
|
|
|
|
// Read JSON data
|
|
jsonData := make([]byte, jsonLen)
|
|
if _, err := io.ReadFull(buf, jsonData); err != nil {
|
|
return nil, fmt.Errorf("failed to read JSON data: %w", err)
|
|
}
|
|
|
|
// Unmarshal JSON
|
|
payload := struct {
|
|
Operation string `json:"operation"`
|
|
Description string `json:"description"`
|
|
StateBefore *RepositoryState `json:"state_before"`
|
|
StateAfter *RepositoryState `json:"state_after"`
|
|
Metadata map[string]string `json:"metadata"`
|
|
}{}
|
|
|
|
if err := json.Unmarshal(jsonData, &payload); err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal JSON: %w", err)
|
|
}
|
|
|
|
entry.Operation = payload.Operation
|
|
entry.Description = payload.Description
|
|
entry.StateBefore = payload.StateBefore
|
|
entry.StateAfter = payload.StateAfter
|
|
entry.Metadata = payload.Metadata
|
|
|
|
return entry, nil
|
|
}
|
|
|
|
// NewOplogEntry creates a new oplog entry
|
|
func NewOplogEntry(id uint64, operation, description string, before, after *RepositoryState) *OplogEntry {
|
|
return &OplogEntry{
|
|
ID: id,
|
|
Timestamp: time.Now(),
|
|
Operation: operation,
|
|
Description: description,
|
|
StateBefore: before,
|
|
StateAfter: after,
|
|
Metadata: make(map[string]string),
|
|
}
|
|
}
|
|
|
|
// NewRepositoryState creates a new repository state snapshot
|
|
func NewRepositoryState(refs map[string]string, currentWorkstream, workingTreeHash, indexHash string) *RepositoryState {
|
|
return &RepositoryState{
|
|
Refs: refs,
|
|
CurrentWorkstream: currentWorkstream,
|
|
WorkingTreeHash: workingTreeHash,
|
|
IndexHash: indexHash,
|
|
}
|
|
}
|