diff --git a/cmd/mygit/hashobject.go b/cmd/mygit/hashobject.go index c9c92ca..7161da7 100644 --- a/cmd/mygit/hashobject.go +++ b/cmd/mygit/hashobject.go @@ -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()) } }, } diff --git a/cmd/mygit/lstree.go b/cmd/mygit/lstree.go index 317c4f2..4ef150a 100644 --- a/cmd/mygit/lstree.go +++ b/cmd/mygit/lstree.go @@ -3,6 +3,8 @@ package main import ( "bytes" "compress/zlib" + "crypto/sha1" + "encoding/hex" "fmt" "io" "os" @@ -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 { @@ -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) diff --git a/cmd/mygit/lstree_test.go b/cmd/mygit/lstree_test.go new file mode 100644 index 0000000..79f34b5 --- /dev/null +++ b/cmd/mygit/lstree_test.go @@ -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) + } + }) + } +} diff --git a/cmd/mygit/writetree.go b/cmd/mygit/writetree.go new file mode 100644 index 0000000..66c89fd --- /dev/null +++ b/cmd/mygit/writetree.go @@ -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 +} diff --git a/your_git.sh b/your_git.sh old mode 100644 new mode 100755