1
A Git implementation, from scratch, in Go[github]
1
2
A Git implementation built from scratch in Go with no Git
3
libraries — it reads and writes the real .git object store by
4
hand (blobs, trees, commits), content-addressed with SHA-1 and
5
zlib-compressed, byte-for-byte compatible with real git.
6
7
Implements the plumbing (init, hash-object, cat-file, ls-tree,
8
write-tree, commit-tree) and a full clone of public repositories
9
over the Smart HTTP protocol — pkt-line framing, packfile
10
parsing, and ref-delta resolution including delta chains, then
11
checkout. Built end-to-end as the CodeCrafters 'Build Your Own
12
Git' challenge.
13
14
15
17
18
// parses blob object file
19
func ParseBlob(object []byte) (*Blob, error) {
20
hash := utils.Hash(object)
21
t, rest, found := bytes.Cut(object, []byte(" "))
22
if !found {
23
return nil, fmt.Errorf("malformed blob object - missing whitespace after type")
24
}
25
if string(t) != "blob" {
26
return nil, fmt.Errorf("malformed blob object - expected type 'blob' but was %s", string(t))
27
}
28
sizeBytes, contentBytes, found := bytes.Cut(rest, []byte{byte(0)})
29
if !found {
30
return nil, fmt.Errorf("malformed blob object %s", object)
31
}
32
size, err := strconv.Atoi(string(sizeBytes))
33
if err != nil {
34
return nil, fmt.Errorf("malformed blob object: %v", err)
35
}
36
37
return &Blob{Hash: hash, Size: size, Content: contentBytes[:size]}, nil
38
}
39
40
// parses blob object file
func ParseBlob(object []byte) (*Blob, error) {
hash := utils.Hash(object)
t, rest, found := bytes.Cut(object, []byte(" "))
if !found {
return nil, fmt.Errorf("malformed blob object - missing whitespace after type")
}
if string(t) != "blob" {
return nil, fmt.Errorf("malformed blob object - expected type 'blob' but was %s", string(t))
}
sizeBytes, contentBytes, found := bytes.Cut(rest, []byte{byte(0)})
if !found {
return nil, fmt.Errorf("malformed blob object %s", object)
}
size, err := strconv.Atoi(string(sizeBytes))
if err != nil {
return nil, fmt.Errorf("malformed blob object: %v", err)
}
return &Blob{Hash: hash, Size: size, Content: contentBytes[:size]}, nil
}
func hashobject(args []string) error {
write := args[0] == "-w"
var filePath string
if write {
filePath = args[1]
} else {
filePath = args[0]
}
data, err := os.ReadFile(filePath)
if err != nil {
return fmt.Errorf("can't read file %s: %v", filePath, err)
}
header := fmt.Sprintf("blob %d\x00", len(data))
objectFileContent := append([]byte(header), data...)
hash := utils.Hash(objectFileContent)
func readPktLine(data []byte) (message []byte, size int, rest []byte, err error) {
sizeUint, err := strconv.ParseUint(string(data[0:4]), 16, 32)
if err != nil {
return nil, 0, nil, err
}
size = int(sizeUint)
// flush
if size == 0 {
return []byte{}, 0, data[4:], nil
}
message = data[4:int(size)]
rest = data[int(size):]
return message, int(size), rest, nil
}
func parseObjectHeader(bytes []byte) (ttype string, size int, consumed int) {
// read first byte - C, type, size
// 7th bit is C bit
c := bytes[0] & 0b10000000
// 6,5,4 are type bits
t := (bytes[0] & 0b01110000) >> 4
// 3,2,1,0 are size bits
size = int(bytes[0] & 0b00001111)
shift := 4
consumed = 1
for ; c == 0b10000000; consumed++ {
b := bytes[consumed]
c = b & 0b10000000
// next 7 bits contribute to size
next := int(b & 0b01111111)
// shift those byte in needed position
// join current size bits with next
size = size | (next << shift)
// increase shift for a length of this message
shift += 7
}
return typeByteToString(t), size, consumed
}