diff options
| -rw-r--r-- | hash.go | 6 | ||||
| -rw-r--r-- | headers.go | 1 | ||||
| -rw-r--r-- | ident.go | 5 | ||||
| -rw-r--r-- | obj.go | 32 | ||||
| -rw-r--r-- | obj_blob.go | 9 | ||||
| -rw-r--r-- | obj_commit.go | 7 | ||||
| -rw-r--r-- | obj_tag.go | 9 | ||||
| -rw-r--r-- | obj_tree.go | 44 | ||||
| -rw-r--r-- | pack_pack.go | 1 | ||||
| -rw-r--r-- | refs.go | 3 | ||||
| -rw-r--r-- | repo.go | 13 |
11 files changed, 97 insertions, 33 deletions
@@ -8,7 +8,7 @@ import ( const maxHashSize = 32 -// Hash represents a Git object identifier. +// Hash represents a Git object ID. type Hash struct { data [maxHashSize]byte size int @@ -35,12 +35,12 @@ var hashFuncs = map[int]hashFunc{ }, } -// String returns the ID as hex using its internal size. +// String returns a hexadecimal string representation of the hash. func (hash Hash) String() string { return hex.EncodeToString(hash.data[:hash.size]) } -// Bytes returns a mutable copy of the underlying bytes using its internal size. +// Bytes returns a mutable copy of the hash bytes. func (hash Hash) Bytes() []byte { return append([]byte(nil), hash.data[:hash.size]...) } @@ -1,5 +1,6 @@ package furgit +// ExtraHeader represents an extra header in a Git object. type ExtraHeader struct { Key string Value []byte @@ -10,8 +10,7 @@ import ( "time" ) -// Ident models an author/committer identity together with its timestamp -// and timezone offset, mirroring the fields that appear in Git objects. +// Ident represents a Git identity (author/committer/tagger). type Ident struct { Name []byte Email []byte @@ -116,7 +115,7 @@ func (ident Ident) Serialize() ([]byte, error) { return []byte(b.String()), nil } -// When returns the timestamp as time.Time using the embedded offset. +// When returns the ident's time.Time with the correct timezone. func (ident Ident) When() time.Time { loc := time.FixedZone("git", int(ident.OffsetMinutes)*60) return time.Unix(ident.WhenUnix, 0).In(loc) @@ -11,13 +11,21 @@ import ( type ObjectType uint8 const ( - ObjectTypeInvalid ObjectType = 0 - ObjectTypeCommit ObjectType = 1 - ObjectTypeTree ObjectType = 2 - ObjectTypeBlob ObjectType = 3 - ObjectTypeTag ObjectType = 4 - ObjectTypeFuture ObjectType = 5 + // An invalid object. + ObjectTypeInvalid ObjectType = 0 + // A commit object. + ObjectTypeCommit ObjectType = 1 + // A tree object. + ObjectTypeTree ObjectType = 2 + // A blob object. + ObjectTypeBlob ObjectType = 3 + // An annotated tag object. + ObjectTypeTag ObjectType = 4 + // An object type reserved for future use. + ObjectTypeFuture ObjectType = 5 + // A packfile offset delta object. This is not typically exposed. ObjectTypeOfsDelta ObjectType = 6 + // A packfile reference delta object. This is not typically exposed. ObjectTypeRefDelta ObjectType = 7 ) @@ -28,12 +36,13 @@ const ( objectTypeNameTag = "tag" ) -// Object describes any Git object variant. +// Object represents a Git object. type Object interface { ObjectType() ObjectType } -// StoredObject describes a Git object with a known hash. +// StoredObject describes a Git object with a known hash, such as +// one read from storage. type StoredObject interface { Object Hash() Hash @@ -82,7 +91,7 @@ func parseObjectBody(ty ObjectType, id Hash, body []byte, repo *Repository) (Sto } } -// ReadObject resolves an ID by consulting loose then packed storage. +// ReadObject resolves an ID. func (repo *Repository) ReadObject(id Hash) (Object, error) { obj, err := repo.looseRead(id) if err == nil { @@ -98,7 +107,10 @@ func (repo *Repository) ReadObject(id Hash) (Object, error) { return obj, err } -// ReadObjectTypeSize reports the object type and size without inflating the body. +// ReadObjectTypeSize reports the object type and size. +// +// Typicall, this is more efficient than reading the full object, +// as it avoids decompressing the entire object body. func (repo *Repository) ReadObjectTypeSize(id Hash) (ObjectType, int64, error) { ty, size, err := repo.looseTypeSize(id) if err == nil { diff --git a/obj_blob.go b/obj_blob.go index 6a987604..05fd5311 100644 --- a/obj_blob.go +++ b/obj_blob.go @@ -1,6 +1,6 @@ package furgit -// Blob represents the contents of a Git blob. +// Blob represents a Git blob object. type Blob struct { Data []byte } @@ -16,7 +16,9 @@ func (sBlob *StoredBlob) Hash() Hash { return sBlob.hash } -// ObjectType allows Blob to satisfy the Object interface. +// ObjectType returns the object type of the blob. +// +// It always returns ObjectTypeBlob. func (blob *Blob) ObjectType() ObjectType { _ = blob return ObjectTypeBlob @@ -32,7 +34,8 @@ func parseBlob(id Hash, body []byte) (*StoredBlob, error) { }, nil } -// Serialize renders the full "blob size\\0body" representation. +// Serialize renders the blob into its raw byte representation, +// including the header (i.e., "type size\0"). func (blob *Blob) Serialize() ([]byte, error) { header, err := headerForType(ObjectTypeBlob, blob.Data) if err != nil { diff --git a/obj_commit.go b/obj_commit.go index c3a4e5db..9953f690 100644 --- a/obj_commit.go +++ b/obj_commit.go @@ -27,7 +27,9 @@ func (sCommit *StoredCommit) Hash() Hash { return sCommit.hash } -// ObjectType allows Commit to satisfy the Object interface. +// ObjectType returns the object type of the commit. +// +// It always returns ObjectTypeCommit. func (commit *Commit) ObjectType() ObjectType { _ = commit return ObjectTypeCommit @@ -128,7 +130,8 @@ func commitBody(c *Commit) ([]byte, error) { return buf.Bytes(), nil } -// Serialize renders a Commit into canonical Git format. +// Serialize renders the commit into its raw byte representation, +// including the header (i.e., "type size\0"). func (commit *Commit) Serialize() ([]byte, error) { body, err := commitBody(commit) if err != nil { @@ -6,7 +6,7 @@ import ( "fmt" ) -// Tag represents an annotated Git tag object. +// Tag represents a Git annotated tag object. type Tag struct { Target Hash TargetType ObjectType @@ -26,7 +26,9 @@ func (sTag *StoredTag) Hash() Hash { return sTag.hash } -// ObjectType allows Tag to satisfy the Object interface. +// ObjectType returns the object type of the tag. +// +// It always returns ObjectTypeTag. func (tag *Tag) ObjectType() ObjectType { _ = tag return ObjectTypeTag @@ -142,7 +144,8 @@ func tagBody(t *Tag) ([]byte, error) { return buf.Bytes(), nil } -// Serialize renders a Tag into canonical Git format. +// Serialize renders the tag into its raw byte representation, +// including the header (i.e., "type size\0"). func (tag *Tag) Serialize() ([]byte, error) { body, err := tagBody(tag) if err != nil { diff --git a/obj_tree.go b/obj_tree.go index eb926832..7b04b231 100644 --- a/obj_tree.go +++ b/obj_tree.go @@ -30,7 +30,9 @@ type TreeEntry struct { ID Hash } -// ObjectType allows Tree to satisfy the Object interface. +// ObjectType returns the object type of the tree. +// +// It always returns ObjectTypeTree. func (tree *Tree) ObjectType() ObjectType { _ = tree return ObjectTypeTree @@ -108,7 +110,8 @@ func treeBody(t *Tree) []byte { return body } -// Serialize renders a Tree into canonical Git format. +// Serialize renders the tree into its raw byte representation, +// including the header (i.e., "type size\0"). func (tree *Tree) Serialize() ([]byte, error) { body := treeBody(tree) header, err := headerForType(ObjectTypeTree, body) @@ -123,6 +126,9 @@ func (tree *Tree) Serialize() ([]byte, error) { } // Entry looks up a tree entry by name. +// +// Lookups are not recursive. +// It returns nil if no such entry exists. func (tree *Tree) Entry(name []byte) *TreeEntry { low, high := 0, len(tree.Entries)-1 for low <= high { @@ -138,3 +144,37 @@ func (tree *Tree) Entry(name []byte) *TreeEntry { } return nil } + +// EntryRecursive looks up a tree entry by path. +// +// Lookups are recursive. +// It returns nil if no such entry exists. +func (tree *Tree) EntryRecursive(repo *Repository, path [][]byte) (*TreeEntry, error) { + if len(path) == 0 { + return nil, errors.New("furgit: tree: empty path") + } + + currentTree := tree + for i, part := range path { + entry := currentTree.Entry(part) + if entry == nil { + return nil, nil + } + if i == len(path)-1 { + return entry, nil + } + obj, err := repo.ReadObject(entry.ID) + if err != nil { + return nil, err + } + nextTree, ok := obj.(*Tree) + if !ok { + return nil, fmt.Errorf("furgit: tree: expected tree object at %s, got %T", part, obj) + // TODO: It may be useful to check the mode instead of reporting + // an object type error. + } + currentTree = nextTree + } + + return nil, nil +} diff --git a/pack_pack.go b/pack_pack.go index 4930c139..15eedf60 100644 --- a/pack_pack.go +++ b/pack_pack.go @@ -20,7 +20,6 @@ const ( packVersion2 = 2 ) -// packlocation identifies the path to a pack file and an offset inside it. type packlocation struct { PackPath string Offset uint64 @@ -75,7 +75,8 @@ func (repo *Repository) resolvePackedRef(refname string) (Hash, error) { return Hash{}, ErrInvalidObject } -// ResolveHEAD reads HEAD and returns the ref that HEAD points to. +// ResolveHEAD reads HEAD and returns the fully qualified +// ref name it points to. func (repo *Repository) ResolveHEAD() (string, error) { data, err := os.ReadFile(repo.repoPath("HEAD")) if err != nil { @@ -12,7 +12,7 @@ import ( "git.sr.ht/~runxiyu/furgit/config" ) -// Repository represents the root of a Git repository. +// Repository represents a Git repository. type Repository struct { rootPath string hashSize int @@ -29,9 +29,11 @@ type Repository struct { closeOnce sync.Once } -// OpenRepository opens the repository at the provided path. The path is expected to be -// the actual repository directory, i.e., the repository itself for bare repositories, -// or the .git subdirectory for non-bare repositories. +// OpenRepository opens the repository at the provided path. +// +// The path is expected to be the actual repository directory, i.e., +// the repository itself for bare repositories, or the .git +// subdirectory for non-bare repositories. func OpenRepository(path string) (*Repository, error) { fi, err := os.Stat(path) if err != nil { @@ -129,7 +131,8 @@ func (repo *Repository) packFile(rel string) (*packFile, error) { return pf, nil } -// ParseHash converts a hex string into a Hash, validating it matches the repository's hash size. +// ParseHash converts a hex string into a Hash, validating +// it matches the repository's hash size. func (repo *Repository) ParseHash(s string) (Hash, error) { var id Hash if len(s)%2 != 0 { |
