diff options
| author | jet2tlf <jet2tlf@gmail.com> | 2024-06-03 03:23:19 +0000 |
|---|---|---|
| committer | jet2tlf <jet2tlf@gmail.com> | 2024-06-03 03:23:19 +0000 |
| commit | b73cbe26f85fd9547e0d91c9e85b6f47bb57ea0d (patch) | |
| tree | 7505a311ef20f396e776f1c0037c8712df4151d0 /cmd/mygit/main.go | |
| parent | 81e395cdeb0894423d0ad833a074538674a4bc2d (diff) | |
| download | git-go-b73cbe26f85fd9547e0d91c9e85b6f47bb57ea0d.tar.gz git-go-b73cbe26f85fd9547e0d91c9e85b6f47bb57ea0d.zip | |
codecrafters submit [skip ci]
Diffstat (limited to 'cmd/mygit/main.go')
| -rw-r--r-- | cmd/mygit/main.go | 370 |
1 files changed, 224 insertions, 146 deletions
diff --git a/cmd/mygit/main.go b/cmd/mygit/main.go index 1443195..686392e 100644 --- a/cmd/mygit/main.go +++ b/cmd/mygit/main.go @@ -4,197 +4,275 @@ import ( "bytes" "compress/zlib" "crypto/sha1" - "encoding/hex" "fmt" "io" - "io/ioutil" "os" - "path/filepath" - "sort" - "strings" + "strconv" + "time" ) -func main() { - if len(os.Args) < 2 { - fmt.Fprintf(os.Stderr, "usage: mygit <command> [<args>...]\n") - os.Exit(1) +func init_repo() { + for _, dir := range []string{".git", ".git/objects", ".git/refs"} { + if err := os.MkdirAll(dir, 0755); err != nil { + fmt.Fprintf(os.Stderr, "Error creating directory: %s\n", err) + } } - switch command := os.Args[1]; command { - case "init": - for _, dir := range []string{".git", ".git/objects", ".git/refs"} { - if err := os.MkdirAll(dir, 0755); err != nil { - fmt.Fprintf(os.Stderr, "Error creating directory: %s\n", err) - } + headFileContents := []byte("ref: refs/heads/master\n") + if err := os.WriteFile(".git/HEAD", headFileContents, 0644); err != nil { + fmt.Fprintf(os.Stderr, "Error writing file: %s\n", err) + } + + fmt.Println("Initialized git directory") +} + +func read_obj(blob_sha string) []byte { + path := fmt.Sprintf(".git/objects/%s/%s", blob_sha[:2], blob_sha[2:]) + + file, err := os.Open(path) + if err != nil { + fmt.Printf("Failed to open blob file: %s\n", err) + } + + reader, err := zlib.NewReader(file) + if err != nil { + fmt.Printf("Failed to instantiate zlib reader: %s\n", err) + } + defer func() { + if err := reader.Close(); err != nil { + fmt.Printf("Failed to close zlib reader: %s\n", err) } + }() + + var buffer bytes.Buffer + + _, err = io.Copy(&buffer, reader) + if err != nil { + fmt.Printf("Failed to write to stdout: %s\n", err) + } + + _, err = buffer.ReadBytes(byte(' ')) + if err != nil { + fmt.Printf("Failed to read from buffer: %s\n", err) + } + + size_byte, err := buffer.ReadBytes(byte(0)) + if err != nil { + fmt.Printf("Failed to read from buffer: %s\n", err) + } + + size, err := strconv.Atoi(string(size_byte[:len(size_byte)-1])) + if err != nil { + fmt.Printf("Failed to convert number of bytes into integer: %s\n", err) + } + + buffer.Truncate(size) + return buffer.Bytes() +} - headFileContents := []byte("ref: refs/heads/main\n") - if err := os.WriteFile(".git/HEAD", headFileContents, 0644); err != nil { - fmt.Fprintf(os.Stderr, "Error writing file: %s\n", err) +func create_obj(content []byte) []byte { + hash_writer := sha1.New() + var blob_content_buffer bytes.Buffer + zlib_writer := zlib.NewWriter(&blob_content_buffer) + writer := io.MultiWriter(hash_writer, zlib_writer) + writer.Write(content) + + sha := hash_writer.Sum(nil) + sha_string := fmt.Sprintf("%x", sha) + + zlib_writer.Close() + + blob_dir := fmt.Sprintf(".git/objects/%s", sha_string[:2]) + + err := os.MkdirAll(blob_dir, 0755) + if err != nil { + fmt.Printf("Failed to create directory for object: %s\n", err) + } + + blob_path := fmt.Sprintf("%s/%s", blob_dir, sha_string[2:]) + + err = os.WriteFile(blob_path, blob_content_buffer.Bytes(), 0644) + if err != nil { + fmt.Printf("Failed to write blob to file: %s\n", err) + } + + return sha +} + +func hash_file(path string) []byte { + f, err := os.ReadFile(path) + if err != nil { + fmt.Printf("Failed to read from given file: %s\n", err) + } + + content := []byte(fmt.Sprintf("blob %d\x00", len(f))) + content = append(content, f...) + return create_obj(content) +} + +func read_tree(hash string) { + file, err := os.Open(fmt.Sprintf(".git/objects/%s/%s", hash[:2], hash[2:])) + if err != nil { + fmt.Printf("Failed to open tree object file: %s\n", err) + } + + reader, err := zlib.NewReader(file) + + if err != nil { + fmt.Printf("Failed to instantiate zlib reader: %s\n", err) + } + + defer func() { + if err := reader.Close(); err != nil { + fmt.Printf("Failed to close zlib reader: %s\n", err) } + }() - fmt.Println("Initialized git directory") + var buffer bytes.Buffer - case "cat-file": - object := os.Args[3] - filePath := fmt.Sprintf(".git/objects/%s/%s", object[:2], object[2:]) - file, err := os.Open(filePath) + _, err = io.Copy(&buffer, reader) + if err != nil { + fmt.Printf("Failed to write to stdout: %s\n", err) + } + + _, err = buffer.ReadBytes(byte(' ')) + if err != nil { + fmt.Printf("Failed to read from buffer: %s\n", err) + } + + size_byte, _ := buffer.ReadBytes(byte(0)) + + _, err = strconv.Atoi(string(size_byte[:len(size_byte)-1])) + if err != nil { + fmt.Printf("Failed to convert tree object size to integer: %s\n", err) + } + + sha_buffer := make([]byte, 20) + + for { + _, err = buffer.ReadBytes(byte(' ')) if err != nil { - fmt.Fprintf(os.Stderr, "Error opening file: %s\n", err) - os.Exit(1) + fmt.Printf("Failed to read from buffer first: %s\n", err) } - defer file.Close() - r, err := zlib.NewReader(file) + name, err := buffer.ReadBytes(byte(0)) if err != nil { - fmt.Fprintf(os.Stderr, "Error creating zlib reader: %s\n", err) - os.Exit(1) + fmt.Printf("Failed to read from buffer second: %s\n", err) } - defer r.Close() - w, err := io.ReadAll(r) + fmt.Println(string(name[:len(name)-1])) + + _, err = io.ReadFull(&buffer, sha_buffer) if err != nil { - fmt.Fprintf(os.Stderr, "Error reading zlib data: %s\n", err) - os.Exit(1) + fmt.Printf("Failed to read 20 bytes from buffer: %s\n", err) } - parts := bytes.Split(w, []byte("\x00")) - if len(parts) < 2 { - fmt.Fprintf(os.Stderr, "Invalid zlib data\n") - os.Exit(1) + if buffer.Len() == 0 { + break } + } +} - fmt.Print(string(parts[1])) +func hash_tree(dir string) []byte { + entries, err := os.ReadDir(dir) + if err != nil { + fmt.Printf("Failed to list directory: %s\n", err) + } - case "hash-object": - object := os.Args[3] - file, err := os.ReadFile(object) - if err != nil { - fmt.Fprintf(os.Stderr, "Error reading file: %s\n", err) - os.Exit(1) - } - stats, err := os.Stat(object) - if err != nil { - fmt.Fprintf(os.Stderr, "Error getting file stats: %s\n", err) - os.Exit(1) - } - content := string(file) - contentAndHeader := fmt.Sprintf("blob %d\x00%s", stats.Size(), content) - sha := sha1.Sum([]byte(contentAndHeader)) - hash := fmt.Sprintf("%x", sha) - blobName := []rune(hash) - blobPath := ".git/objects/" - - for i, v := range blobName { - blobPath += string(v) - if i == 1 { - blobPath += "/" - } - } + var entries_buffer bytes.Buffer - var buffer bytes.Buffer - z := zlib.NewWriter(&buffer) - z.Write([]byte(contentAndHeader)) - z.Close() + for _, entry := range entries { + name := entry.Name() + path := fmt.Sprintf("%s/%s", dir, name) - if err := os.MkdirAll(filepath.Dir(blobPath), os.ModePerm); err != nil { - fmt.Fprintf(os.Stderr, "Error creating directory: %s\n", err) - os.Exit(1) + if name == ".git" { + continue } - f, err := os.Create(blobPath) - if err != nil { - fmt.Fprintf(os.Stderr, "Error creating file: %s\n", err) - os.Exit(1) - } - defer f.Close() + var sha []byte + var mode string - if _, err := f.Write(buffer.Bytes()); err != nil { - fmt.Fprintf(os.Stderr, "Error writing to file: %s\n", err) - os.Exit(1) + if entry.IsDir() { + mode = "40000" + sha = hash_tree(path) + } else { + mode = "100644" + sha = hash_file(path) } - fmt.Print(hash) - - case "ls-tree": - fileNames := []string{} - files, err := os.ReadDir(".") + _, err = entries_buffer.Write([]byte(fmt.Sprintf("%s %s\x00", mode, name))) if err != nil { - fmt.Fprintf(os.Stderr, "Unknown error %s\n", err) - os.Exit(1) + fmt.Printf("Failed to write to byte buffer: %s\n", err) } - for _, file := range files { - if file.Name() != ".git" { - fileNames = append(fileNames, file.Name()) - } + _, err = entries_buffer.Write(sha) + if err != nil { + fmt.Printf("Failed to write to byte buffer: %s\n", err) } + } - sort.Strings(fileNames) - fmt.Println(strings.Join(fileNames, "\n")) + content := []byte(fmt.Sprintf("tree %d\x00", entries_buffer.Len())) + content = append(content, entries_buffer.Bytes()...) + return create_obj(content) +} - case "write-tree": - currentDir, _ := os.Getwd() - h, c := calcTreeHash(currentDir) - treeHash := hex.EncodeToString(h) - os.Mkdir(filepath.Join(".git", "objects", treeHash[:2]), 0755) - var compressed bytes.Buffer - w := zlib.NewWriter(&compressed) - w.Write(c) - w.Close() - os.WriteFile(filepath.Join(".git", "objects", treeHash[:2], treeHash[2:]), compressed.Bytes(), 0644) - fmt.Println(treeHash) +func commit_tree(tree_sha, parent_sha, message string) []byte { + author := "Lucas Faria" + authorEmail := "jet2tlf@gmail.com" + currentUnixTime := time.Now().Unix() + timezone, _ := time.Now().Local().Zone() - default: - fmt.Fprintf(os.Stderr, "Unknown command %s\n", command) - os.Exit(1) - } -} + commit_content := []byte(fmt.Sprintf( + "tree %s\nparent %s\nauthor %s <%s> %s %s\ncommitter %s <%s> %s %s\n\n%s\n", + tree_sha, + parent_sha, + author, + authorEmail, + fmt.Sprint(currentUnixTime), + timezone, + author, + authorEmail, + fmt.Sprint(currentUnixTime), + timezone, + message), + ) -func calcTreeHash(dir string) ([]byte, []byte) { - fileInfos, _ := ioutil.ReadDir(dir) + content := []byte(fmt.Sprintf("commit %d\x00", len(commit_content))) + content = append(content, commit_content...) + return create_obj(content) +} - type entry struct { - fileName string - b []byte +func main() { + if len(os.Args) < 2 { + fmt.Fprintf(os.Stderr, "usage: mygit <command> [<args>...]\n") + os.Exit(1) } - var entries []entry - contentSize := 0 + switch command := os.Args[1]; command { + case "init": + init_repo() - for _, fileInfo := range fileInfos { - if fileInfo.Name() == ".git" { - continue - } + case "cat-file": + content := read_obj(os.Args[3]) + fmt.Print(string(content)) - if !fileInfo.IsDir() { - f, _ := os.Open(filepath.Join(dir, fileInfo.Name())) - b, _ := ioutil.ReadAll(f) - s := fmt.Sprintf("blob %d\u0000%s", len(b), string(b)) - sha1 := sha1.New() - io.WriteString(sha1, s) - s = fmt.Sprintf("100644 %s\u0000", fileInfo.Name()) - b = append([]byte(s), sha1.Sum(nil)...) - entries = append(entries, entry{fileInfo.Name(), b}) - contentSize += len(b) - } else { - b, _ := calcTreeHash(filepath.Join(dir, fileInfo.Name())) - s := fmt.Sprintf("40000 %s\u0000", fileInfo.Name()) - b2 := append([]byte(s), b...) - entries = append(entries, entry{fileInfo.Name(), b2}) - contentSize += len(b2) - } - } - sort.Slice(entries, func(i, j int) bool { return entries[i].fileName < entries[j].fileName }) - s := fmt.Sprintf("tree %d\u0000", contentSize) - b := []byte(s) + case "hash-object": + sha := hash_file(os.Args[3]) + fmt.Printf("%x\n", sha) - for _, entry := range entries { - b = append(b, entry.b...) - } + case "ls-tree": + read_tree(os.Args[3]) - sha1 := sha1.New() - io.WriteString(sha1, string(b)) - return sha1.Sum(nil), b + case "write-tree": + sha := hash_tree(".") + fmt.Printf("%x\n", sha) + + case "commit-tree": + sha := commit_tree(os.Args[2], os.Args[4], os.Args[6]) + fmt.Printf("%x\n", sha) + default: + fmt.Fprintf(os.Stderr, "Unknown command %s\n", command) + os.Exit(1) + } } |