Skip to content

Commit

Permalink
feat(write-tree): initial draft of write-tree
Browse files Browse the repository at this point in the history
Works for directories containing just files.
When we run on a directory containing subdirs it writes
the correct file but the hash for the root tree is incorrect.
Needs further investigating.
  • Loading branch information
Kidsan committed Mar 12, 2024
1 parent b07ac9a commit df0c73a
Show file tree
Hide file tree
Showing 5 changed files with 229 additions and 5 deletions.
6 changes: 1 addition & 5 deletions cmd/mygit/hashobject.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,7 @@ var hashObjectCmd = &cobra.Command{
fmt.Println(sha)

if wFlag {
path := blobNameToPath(sha)
if _, err := os.Stat(path); err == nil {
os.Remove(path)
}
cobra.CheckErr(os.WriteFile(path, compressed.Bytes(), 0644))
writeObjectToDisk(sha, compressed.Bytes())
}
},
}
Expand Down
21 changes: 21 additions & 0 deletions cmd/mygit/lstree.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package main
import (
"bytes"
"compress/zlib"
"crypto/sha1"
"encoding/hex"
"fmt"
"io"
"os"
Expand Down Expand Up @@ -58,6 +60,24 @@ type Child struct {
typ string
}

func (t *Tree) Encode() string {
var b strings.Builder
var childString strings.Builder
for _, v := range t.Children {
childString.WriteString(fmt.Sprintf("%s %s\x00", v.mode, v.name))
childString.Write(v.sha)
}
b.WriteString(fmt.Sprintf("tree %d\x00", len(childString.String()))) // size is the number of bytes
b.WriteString(childString.String())
return b.String()
}

func (t *Tree) GetSHA() string {
hasher := sha1.New()
hasher.Write([]byte(t.Encode()))
return hex.EncodeToString(hasher.Sum(nil))
}

func NewGitTreeFromString(input []byte) *Tree {
var firstLineEnd int
for i, v := range input {
Expand Down Expand Up @@ -105,6 +125,7 @@ func NewGitTreeFromString(input []byte) *Tree {
case "100644", "100755", "120000":
c.typ = "blob"
default:
c.mode = "040000" // add trailing 0
c.typ = "tree"
}
children = append(children, c)
Expand Down
104 changes: 104 additions & 0 deletions cmd/mygit/lstree_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package main

import (
"fmt"
"testing"
)

func TestTree_Encode(t *testing.T) {
type fields struct {
}
tests := []struct {
name string
Size int
Children []*Child
fields fields
want string
}{
{
name: "Test 1",
want: "tree 59\x00100644 file1.txt\x003b18e512dba79e4c8300dd08aeb3yi7f8e728b8dad",
Children: []*Child{
{
mode: "100644",
name: "file1.txt",
sha: []byte("3b18e512dba79e4c8300dd08aeb3yi7f8e728b8dad"),
typ: "blob",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tr := &Tree{
Size: tt.Size,
Children: tt.Children,
}
if got := tr.Encode(); got != tt.want {
t.Errorf("Tree.Encode() = %v, want %v", got, tt.want)
}
})
}
}

func TestTree_GetSHA(t *testing.T) {
tests := []struct {
name string
Size int
Children []*Child
want string
}{
{
name: "Test 1",
want: "cc518e1c8b5356ef4f448ef6725fb4fbe6871509",
Children: []*Child{
{
mode: "100644",
name: "file1.txt",
sha: []byte{59, 24, 229, 18, 219, 167, 158, 76, 131, 0, 221, 8, 174, 179, 127, 142, 114, 139, 141, 173},
typ: "blob",
},
{
mode: "100644",
name: "file2.txt",
sha: []byte{208, 225, 233, 84, 85, 117, 75, 211, 29, 86, 38, 13, 25, 167, 119, 79, 215, 174, 190, 93},
typ: "blob",
},
},
},
{
name: "Test 2",
want: "b67fea69b76b86cf0843c69bdc954366598175e0",
Children: []*Child{
{
mode: "100644",
name: "file1.txt",
sha: []byte{59, 24, 229, 18, 219, 167, 158, 76, 131, 0, 221, 8, 174, 179, 127, 142, 114, 139, 141, 173},
typ: "blob",
},
{
mode: "100644",
name: "file2.txt",
sha: []byte{208, 225, 233, 84, 85, 117, 75, 211, 29, 86, 38, 13, 25, 167, 119, 79, 215, 174, 190, 93},
typ: "blob",
},
{
mode: "040000",
name: "foo",
sha: []byte{128, 147, 227, 82, 216, 203, 25, 44, 44, 86, 143, 233, 208, 135, 40, 228, 201, 173, 40, 230},
typ: "tree",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tr := &Tree{
Children: tt.Children,
}
if got := fmt.Sprintf("%s", tr.GetSHA()); got != tt.want {
t.Errorf("Tree.GetSHA() = %v, want %v", got, tt.want)
}
})
}
}
103 changes: 103 additions & 0 deletions cmd/mygit/writetree.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package main

import (
"bytes"
"compress/zlib"
"crypto/sha1"
"encoding/hex"
"fmt"
"os"
"strings"

"github.com/spf13/cobra"
)

var writeTreeCmd = &cobra.Command{
Use: "write-tree",
Run: func(cmd *cobra.Command, args []string) {
workDir, err := os.Getwd()
cobra.CheckErr(err)
root := buildDirectoryTree(workDir)
fmt.Println(fmt.Sprintf("%s", root.GetSHA()))
},
}

func init() {
rootcmd.AddCommand(writeTreeCmd)
}

func writeObjectToDisk(sha string, data []byte) {
path := blobNameToPath(sha)
dirs := strings.Split(path, "/")
dir := strings.Join(dirs[:len(dirs)-1], "/")
if _, err := os.Stat(dir); os.IsNotExist(err) {
os.Mkdir(dir, 0755)
}
if _, err := os.Stat(path); err == nil {
os.Remove(path)
}
cobra.CheckErr(os.WriteFile(path, data, 0644))
}

func buildDirectoryTree(dirname string) *Tree {
entries, err := os.ReadDir(dirname)
cobra.CheckErr(err)

var children []*Child

for _, file := range entries {
if file.Name() == ".git" {
continue
}
if file.IsDir() {
subTree := buildDirectoryTree(dirname + "/" + file.Name())
encoded := subTree.Encode()
hasher := sha1.New()
hasher.Write([]byte(encoded))
treeSha := hasher.Sum(nil)
children = append(children, &Child{
mode: "040000",
sha: []byte(fmt.Sprintf("%s", treeSha)),
name: file.Name(),
typ: "tree",
})
continue
}
contents, err := os.ReadFile(dirname + "/" + file.Name())
cobra.CheckErr(err)

content := string(contents)
g := GitObject{Size: len(bytes.Runes([]byte(content))), Content: content}
a := g.ToBlob()

var compressed bytes.Buffer
w := zlib.NewWriter(&compressed)
w.Write(a)
cobra.CheckErr(w.Close())

hasher := sha1.New()
hasher.Write(a)
hash := hasher.Sum(nil)
sha := hex.EncodeToString(hash)
children = append(children, &Child{
mode: "100644",
sha: hash,
name: file.Name(),
typ: "blob",
})

writeObjectToDisk(sha, compressed.Bytes())
}
tree := Tree{
Children: children,
}
encoded := tree.Encode()

var compressed bytes.Buffer
w := zlib.NewWriter(&compressed)
w.Write([]byte(encoded))
cobra.CheckErr(w.Close())

writeObjectToDisk(tree.GetSHA(), compressed.Bytes())
return &tree
}
Empty file modified your_git.sh
100644 → 100755
Empty file.

0 comments on commit df0c73a

Please sign in to comment.