This document covers low-level Git plumbing operations in go-git, including packfiles, index manipulation, and object encoding.
The plumbing packages provide low-level access to Git internals:
type ObjectType int8
const (
InvalidObject ObjectType = 0
CommitObject ObjectType = 1
TreeObject ObjectType = 2
BlobObject ObjectType = 3
TagObject ObjectType = 4
OFSDeltaObject ObjectType = 6
REFDeltaObject ObjectType = 7
AnyObject ObjectType = -127
)
func ParseObjectType(value string) (ObjectType, error)
func (t ObjectType) String() string
func (t ObjectType) Bytes() []byte
func (t ObjectType) Valid() bool
func (t ObjectType) IsDelta() booltype EncodedObject interface {
Hash() Hash
Type() ObjectType
SetType(ObjectType)
Size() int64
SetSize(int64)
Reader() (io.ReadCloser, error)
Writer() (io.WriteCloser, error)
}import "github.com/go-git/go-git/v5/plumbing/format/packfile"
type Scanner struct {
// contains filtered or unexported fields
}
func NewScanner(r io.Reader) *Scanner
func (s *Scanner) Header() (*Header, error)
func (s *Scanner) NextObjectHeader() (*ObjectHeader, error)
func (s *Scanner) NextObject(w io.Writer) (int64, plumbing.Hash, error)
func (s *Scanner) Close() errorExample:
package main
import (
"fmt"
"os"
"github.com/go-git/go-git/v5/plumbing/format/packfile"
)
func main() {
f, err := os.Open("/tmp/repo/.git/objects/pack/pack-*.pack")
if err != nil {
panic(err)
}
defer f.Close()
scanner := packfile.NewScanner(f)
// Read header
header, err := scanner.Header()
if err != nil {
panic(err)
}
fmt.Printf("Version: %d, Objects: %d\n", header.Version, header.Objects)
// Scan objects
for {
objHeader, err := scanner.NextObjectHeader()
if err != nil {
break
}
fmt.Printf("Type: %s, Size: %d\n", objHeader.Type, objHeader.Length)
}
}import "github.com/go-git/go-git/v5/plumbing/format/packfile"
type Parser struct {
// contains filtered or unexported fields
}
func NewParser(scanner Scanner, storage storer.EncodedObjectStorer) *Parser
func (p *Parser) Parse() (plumbing.Hash, error)import "github.com/go-git/go-git/v5/plumbing/format/packfile"
type Encoder struct {
// contains filtered or unexported fields
}
func NewEncoder(w io.Writer, storage storer.EncodedObjectStorer, useRefDeltas bool) *Encoder
func (e *Encoder) Encode(refs []*plumbing.Reference, cfg *config.Config) (plumbing.Hash, error)import "github.com/go-git/go-git/v5/plumbing/format/index"
type Index struct {
Version uint32
Entries []*Entry
Cache *Tree
ResolveUndo *ResolveUndo
EndOfIndexEntry *EndOfIndexEntry
}
func (i *Index) Add(path string, mode filemode.FileMode, hash plumbing.Hash) error
func (i *Index) Remove(path string) error
func (i *Index) Entry(path string) (*Entry, bool)
func (i *Index) Entries() []*Entry
func (i *Index) Decode(r io.Reader) error
func (i *Index) Encode(w io.Writer) errortype Entry struct {
CreatedAt time.Time
ModifiedAt time.Time
Dev uint32
Inode uint32
Mode filemode.FileMode
UID uint32
GID uint32
Size uint32
Hash plumbing.Hash
Stage Stage
Name string
IntentToAdd bool
SkipWorktree bool
}
type Stage uint16
const (
Merged Stage = 0
AncestorMode Stage = 1
OurMode Stage = 2
TheirMode Stage = 3
)Example:
package main
import (
"fmt"
"time"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/filemode"
"github.com/go-git/go-git/v5/plumbing/format/index"
)
func main() {
idx := &index.Index{
Version: 2,
}
// Add entry
err := idx.Add("README.md", filemode.Regular, plumbing.NewHash("abc123..."))
if err != nil {
panic(err)
}
// Get entry
entry, found := idx.Entry("README.md")
if found {
fmt.Println("File:", entry.Name)
fmt.Println("Mode:", entry.Mode)
fmt.Println("Hash:", entry.Hash)
}
}import "github.com/go-git/go-git/v5/plumbing/filemode"
type FileMode uint32
const (
Empty FileMode = 0
Dir FileMode = 0040000
Regular FileMode = 0100644
Deprecated FileMode = 0100664
Executable FileMode = 0100755
Symlink FileMode = 0120000
Submodule FileMode = 0160000
)
func New(mode string) (FileMode, error)
func NewFromOSFileMode(mode os.FileMode) (FileMode, error)
func (m FileMode) Bytes() []byte
func (m FileMode) IsMalformed() bool
func (m FileMode) String() string
func (m FileMode) IsRegular() bool
func (m FileMode) IsFile() bool
func (m FileMode) ToOSFileMode() (os.FileMode, error)import "github.com/go-git/go-git/v5/plumbing/protocol/packp"
type AdvRefs struct {
Prefix [][]byte
Head *plumbing.Hash
Peeled map[string]plumbing.Hash
Refs map[string]plumbing.Hash
Shallows []plumbing.Hash
Capabilities *capability.List
}
func (a *AdvRefs) AllReferences() map[string]plumbing.Hash
func (a *AdvRefs) IsEmpty() bool
func (a *AdvRefs) Decode(r io.Reader) error
func (a *AdvRefs) Encode(w io.Writer) errortype UploadPackRequest struct {
Wants []plumbing.Hash
Haves []plumbing.Hash
Shallows []plumbing.Hash
Depth Depth
Capabilities *capability.List
}
func (r *UploadPackRequest) Decode(rd io.Reader) error
func (r *UploadPackRequest) Encode(w io.Writer) errortype ReferenceUpdateRequest struct {
Commands []*Command
Capabilities *capability.List
Shallow *plumbing.Hash
Packfile io.ReadCloser
}
type Command struct {
Name plumbing.ReferenceName
Old plumbing.Hash
New plumbing.Hash
}
func (r *ReferenceUpdateRequest) Decode(rd io.Reader) error
func (r *ReferenceUpdateRequest) Encode(w io.Writer) errorimport "github.com/go-git/go-git/v5/plumbing/protocol/packp/capability"
type List struct {
// contains filtered or unexported fields
}
func (l *List) Add(cap Capability) *List
func (l *List) Set(cap Capability, values ...string) *List
func (l *List) Get(cap Capability) *Capability
func (l *List) Supports(cap Capability) bool
func (l *List) Delete(cap Capability)
func (l *List) String() string
func (l *List) Decode(data []byte) error
func (l *List) Encode(w io.Writer) error
type Capability string
const (
MultiACK Capability = "multi_ack"
MultiACKDetailed Capability = "multi_ack_detailed"
NoDone Capability = "no-done"
ThinPack Capability = "thin-pack"
SideBand Capability = "side-band"
SideBand64k Capability = "side-band-64k"
OFSDelta Capability = "ofs-delta"
Shallow Capability = "shallow"
DeepenSince Capability = "deepen-since"
DeepenNot Capability = "deepen-not"
DeepenRelative Capability = "deepen-relative"
NoProgress Capability = "no-progress"
IncludeTag Capability = "include-tag"
ReportStatus Capability = "report-status"
ReportStatusV2 Capability = "report-status-v2"
DeleteRefs Capability = "delete-refs"
Quiet Capability = "quiet"
Atomic Capability = "atomic"
PushOptions Capability = "push-options"
Agent Capability = "agent"
SymRef Capability = "symref"
Filter Capability = "filter"
)import "github.com/go-git/go-git/v5/plumbing"
type Hasher interface {
io.Writer
Sum() Hash
}
func NewHasher(t ObjectType, size int64) Hasher
func ComputeHash(t ObjectType, content []byte) HashExample:
package main
import (
"fmt"
"github.com/go-git/go-git/v5/plumbing"
)
func main() {
// Compute hash for blob content
content := []byte("Hello, World!")
hash := plumbing.ComputeHash(plumbing.BlobObject, content)
fmt.Println("Blob hash:", hash.String())
// Using hasher
hasher := plumbing.NewHasher(plumbing.BlobObject, int64(len(content)))
hasher.Write(content)
hash2 := hasher.Sum()
fmt.Println("Hasher result:", hash2.String())
}import "github.com/go-git/go-git/v5/utils/binary"
func Read(r io.Reader, data interface{}) error
func Write(w io.Writer, data interface{}) error
func ReadUint8(r io.Reader) (uint8, error)
func ReadUint16(r io.Reader) (uint16, error)
func ReadUint32(r io.Reader) (uint32, error)
func ReadUint64(r io.Reader) (uint64, error)
func ReadVariableWidthInt(r io.Reader) (int64, error)
func WriteUint8(w io.Writer, value uint8) error
func WriteUint16(w io.Writer, value uint16) error
func WriteUint32(w io.Writer, value uint32) error
func WriteUint64(w io.Writer, value uint64) error
func WriteVariableWidthInt(w io.Writer, value int64) errorimport "github.com/go-git/go-git/v5/utils/diff"
func Do(a, b string) (string, error)
func DoWithColor(e UnifiedEncoder, from, to *File, color color.Color) error
type UnifiedEncoder struct {
// contains filtered or unexported fields
}
func (e *UnifiedEncoder) Encode(w io.Writer) errorimport "github.com/go-git/go-git/v5/utils/merkletrie"
type Change struct {
From noder.Noder
To noder.Noder
}
type Action int
const (
Insert Action = iota
Delete
Modify
)
func (c *Change) Action() Action
func (c *Change) Files() (from, to noder.Path)
func (c *Change) String() string
func DiffTree(from, to noder.Noder) ([]*Change, error)The plumbing APIs are powerful but complex. Use porcelain APIs for common operations.
When working with packfiles and object encoding, ensure proper error handling for corrupted data.
Always close resources:
package main
import (
"os"
"github.com/go-git/go-git/v5/plumbing/format/packfile"
)
func main() {
f, err := os.Open("pack.pack")
if err != nil {
panic(err)
}
defer f.Close()
scanner := packfile.NewScanner(f)
defer scanner.Close()
// Use scanner...
}package main
import (
"fmt"
"os"
"github.com/go-git/go-git/v5/plumbing/format/packfile"
)
func main() {
f, _ := os.Open("/tmp/repo/.git/objects/pack/pack-*.pack")
defer f.Close()
scanner := packfile.NewScanner(f)
defer scanner.Close()
header, _ := scanner.Header()
fmt.Printf("Objects: %d\n", header.Objects)
}package main
import (
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/filemode"
"github.com/go-git/go-git/v5/plumbing/format/index"
)
func main() {
idx := &index.Index{Version: 2}
// Add file
idx.Add("main.go", filemode.Regular, plumbing.NewHash("abc123..."))
// Remove file
idx.Remove("old.go")
// Check entries
for _, entry := range idx.Entries() {
println(entry.Name)
}
}