package git import ( "bytes" "crypto/sha1" "crypto/sha256" "encoding/hex" "hash" "regexp" ) type ChecksumAlgorithm int const ( _ = ChecksumAlgorithm(iota) SHA1 SHA256 ) // in bytes func (alg ChecksumAlgorithm) DigestSize() int { switch alg { case SHA1: return 20 case SHA256: return 32 default: panic("unreachable") } } // the "SHA-1" type Checksum struct { Algorithm ChecksumAlgorithm Raw []byte } func (checksum Checksum) contract() { if checksum.Algorithm.DigestSize() != len(checksum.Raw) { panic("unreachable") } } // Equal checks for equality of the checksum and algorithm. Two different // Checksums could still refer to the same data, using different checksum // algorithms. func (checksum Checksum) Equal(other Checksum) bool { return checksum.Algorithm == other.Algorithm && bytes.Equal(checksum.Raw, other.Raw) } func (checksum Checksum) Hex() string { checksum.contract() return hex.EncodeToString(checksum.Raw) } func FromHexSHA1(hexits string) Checksum { if !regexp.MustCompile(`^[0-9a-f]{40}$`).MatchString(hexits) { panic("not a git SHA1") } raw, err := hex.DecodeString(hexits) if err != nil { panic("unreachable") } return Checksum{ Algorithm: SHA1, Raw: raw, } } func (alg ChecksumAlgorithm) Slurp(datas ...[]byte) Checksum { hasher := alg.Hasher() for _, data := range datas { n, err := hasher.Write(data) if err != nil || n != len(data) { panic("unreachable") } } return hasher.Checksum() } type Hasher struct { alg ChecksumAlgorithm hasher hash.Hash } func (alg ChecksumAlgorithm) Hasher() *Hasher { switch alg { default: panic("unreachable") case SHA1: return &Hasher{SHA1, sha1.New()} case SHA256: return &Hasher{SHA256, sha256.New()} } } func (hasher Hasher) Checksum() Checksum { raw := hasher.hasher.Sum(nil) if len(raw) != hasher.alg.DigestSize() { panic("unreachable") } return Checksum{hasher.alg, raw} } func (hasher Hasher) Write(p []byte) (int, error) { n, err := hasher.hasher.Write(p) // TODO is n==len(p) truly guaranteed? if err != nil || n != len(p) { panic("unreachable") } return n, err }