///|
/// SHA-1 over `content`, as git spells it for a blob.
///
/// Git hashes `blob \0`, and the hex of that is the object id the
/// index stores — which is what makes "has this file changed" answerable by
/// reading it, without trusting a timestamp. SHA-1 is here rather than in a
/// dependency because it is forty lines and every target can run it.
fn blob_id(content : Bytes) -> String {
let header = "blob " + content.length().to_string() + "\u{0}"
let header_bytes = @utf8.encode(header)
sha1_hex(header_bytes + content)
}
///|
fn sha1_hex(data : Bytes) -> String {
let state : Array[Int32] = [
0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0,
]
// Pad to 64-byte blocks: 0x80, then zeros, then the bit length as a 64-bit
// big-endian number.
let length = data.length()
let blocks : Array[Byte] = []
for i in 0..= 0; i = i - 1 {
blocks.push(((bits >> (8 * i)) & 0xFFL).to_byte())
}
let mut offset = 0
while offset < blocks.length() {
let words : Array[Int32] = []
for i in 0..<16 {
let at = offset + 4 * i
words.push(
(blocks[at].to_int() << 24) |
(blocks[at + 1].to_int() << 16) |
(blocks[at + 2].to_int() << 8) |
blocks[at + 3].to_int(),
)
}
for i in 16..<80 {
let value = words[i - 3] ^ words[i - 8] ^ words[i - 14] ^ words[i - 16]
words.push(((value << 1) | ((value >> 31) & 1)) & 0xFFFFFFFF)
}
let mut a = state[0]
let mut b = state[1]
let mut c = state[2]
let mut d = state[3]
let mut e = state[4]
for i in 0..<80 {
let (f, k) = if i < 20 {
((b & c) | ((b ^ 0xFFFFFFFF) & d), 0x5A827999)
} else if i < 40 {
(b ^ c ^ d, 0x6ED9EBA1)
} else if i < 60 {
((b & c) | (b & d) | (c & d), 0x8F1BBCDC)
} else {
(b ^ c ^ d, 0xCA62C1D6)
}
let temp = (((a << 5) | ((a >> 27) & 0x1F)) + f + e + k + words[i]) &
0xFFFFFFFF
e = d
d = c
c = ((b << 30) | ((b >> 2) & 0x3FFFFFFF)) & 0xFFFFFFFF
b = a
a = temp
}
state[0] = (state[0] + a) & 0xFFFFFFFF
state[1] = (state[1] + b) & 0xFFFFFFFF
state[2] = (state[2] + c) & 0xFFFFFFFF
state[3] = (state[3] + d) & 0xFFFFFFFF
state[4] = (state[4] + e) & 0xFFFFFFFF
offset = offset + 64
}
let out = StringBuilder()
for word in state {
for i = 3; i >= 0; i = i - 1 {
let byte = (word >> (8 * i)) & 0xFF
out.write_char(hex_digit((byte >> 4) & 0xF))
out.write_char(hex_digit(byte & 0xF))
}
}
out.to_string()
}