// Copyright (c) 2025, Christian Lee Seibold // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright notice, this // list of conditions and the following disclaimer. // // 2. Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // // 3. Neither the name of the copyright holder nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. package main import ( "bytes" "fmt" "os" "path" "sort" "time" "github.com/klauspost/compress/zstd" ) type TreeObject struct { hash Hash entries []TreeEntry } func DecodeTree(vcsDirectory string, hash Hash) TreeObject { file_contents, err := os.ReadFile(path.Join(vcsDirectory, "objects", hash.HexString())) if err != nil { return TreeObject{} } zr, _ := zstd.NewReader(bytes.NewReader(file_contents)) result := new(bytes.Buffer) result.ReadFrom(zr) zr.Close() _, contents, _ := bytes.Cut(result.Bytes(), []byte{00}) entries := []TreeEntry{} reader := bytes.NewReader(contents) for { entry := ParseTreeEntry(reader) if (entry != TreeEntry{}) { entries = append(entries, entry) } else { break } } return TreeObject{hash, entries} } func (t TreeObject) FindEntryName(name string) TreeEntry { for _, entry := range t.entries { if entry.name == name { return entry } } return TreeEntry{} } func (t TreeObject) Encode() []byte { sort.SliceStable(t.entries, func(i, j int) bool { return caseInsensitiveStringCompare(t.entries[i].name, t.entries[j].name) == -1 }) buf := bytes.Buffer{} for _, entry := range t.entries { buf.Write(entry.Encode()) } return buf.Bytes() } func (t TreeObject) ToIndex(modTime time.Time) *Index { newIndex := make(Index) for _, entry := range t.entries { if entry.t == "blob" { newIndex[entry.name] = IndexEntry{"c", "100644", entry.name, entry.hash, modTime} } else if entry.t == "tree" { // TODO } } return &newIndex } type TreeEntry struct { t string // blob or tree hash Hash name string } func ParseTreeEntry(reader *bytes.Reader) TreeEntry { // Get next space character to get the mode modebuf := bytes.Buffer{} for { b, err := reader.ReadByte() if err != nil { return TreeEntry{} } if b == ' ' { break } modebuf.WriteByte(b) } // Read until NUL character into name name := bytes.Buffer{} for { b, err := reader.ReadByte() if err != nil { return TreeEntry{} } if b == 0 { break } name.WriteByte(b) } // Create array for the hash var h Hash _, err := reader.Read(h[:]) if err != nil { return TreeEntry{} } // Create tree entry based on mode mode := modebuf.String() entryType := "blob" if mode != "100644" { entryType = "tree" } return TreeEntry{entryType, h, name.String()} } func (te TreeEntry) Encode() []byte { mode := "" if te.t == "blob" { mode = "100644" // Default mode for blobs } buf := bytes.Buffer{} buf.WriteString(fmt.Sprintf("%s %s\000", mode, te.name)) buf.Write(te.hash[:]) return buf.Bytes() }