gabriel / muse public
ids.py python
109 lines 3.3 KB
Raw
sha256:502e812e12319e30302d07d165425ceeb21687ad30e8af3a439b1f6de36c64d6 fix: update stale tag tests to use label for free-form anno… Sonnet 4.6 19 days ago
1 """Canonical ID derivation for all Muse object types."""
2
3 import hashlib
4 from typing import TYPE_CHECKING
5
6 if TYPE_CHECKING:
7 from muse.core.types import Manifest
8
9 DEFAULT_HASH_ALGO: str = "sha256"
10
11
12 def long_id(hex_digest: str, algo: str = DEFAULT_HASH_ALGO, *, strip: bool = False) -> str:
13 """Canonical long-form of an algorithm-prefixed object ID."""
14 prefix = f"{algo}:"
15 if hex_digest.startswith(prefix):
16 return hex_digest[len(prefix):] if strip else hex_digest
17 return hex_digest if strip else f"{prefix}{hex_digest}"
18
19
20 def hash_blob(data: bytes, hash_algo: str = DEFAULT_HASH_ALGO) -> str:
21 """Return the canonical content-addressed ID for raw bytes.
22
23 The type is baked into the hash: ``sha256("blob <size>\\0<data>")``.
24
25 Args:
26 data: Raw bytes to hash.
27 hash_algo: Hash algorithm name (default :data:`DEFAULT_HASH_ALGO`).
28
29 Returns:
30 A ``<hash_algo>:<hex>`` content-addressed ID string.
31 """
32 header = f"blob {len(data)}\0".encode()
33 return long_id(hashlib.new(hash_algo, header + data).hexdigest(), hash_algo)
34
35
36 _SEP: str = "\x00"
37
38
39 def hash_snapshot(
40 manifest: "Manifest",
41 directories: list[str] | None = None,
42 hash_algo: str = DEFAULT_HASH_ALGO,
43 ) -> str:
44 """Return the canonical content-addressed ID for a snapshot manifest.
45
46 The type is baked into the hash: ``sha256("snapshot <size>\\0<canonical>")``.
47 """
48 from muse.core.types import split_id
49 parts = sorted(
50 f"{path}{_SEP}{split_id(oid)[1]}" for path, oid in manifest.items()
51 )
52 if directories:
53 parts.extend(f"dir{_SEP}{d}" for d in sorted(directories))
54 canonical = _SEP.join(parts).encode()
55 header = f"snapshot {len(canonical)}\0".encode()
56 return long_id(hashlib.new(hash_algo, header + canonical).hexdigest(), hash_algo)
57
58
59 def hash_commit(
60 parent_ids: list[str],
61 snapshot_id: str,
62 message: str,
63 committed_at_iso: str,
64 author: str = "",
65 signer_public_key: str = "",
66 hash_algo: str = DEFAULT_HASH_ALGO,
67 ) -> str:
68 """Return the canonical content-addressed ID for a commit.
69
70 The type is baked into the hash: ``sha256("commit <size>\\0<canonical>")``.
71 """
72 from muse.core.types import split_id
73 parts = [
74 _SEP.join(sorted(split_id(p)[1] for p in parent_ids)),
75 split_id(snapshot_id)[1] if snapshot_id else "",
76 message,
77 committed_at_iso,
78 author,
79 signer_public_key,
80 ]
81 canonical = _SEP.join(parts).encode()
82 header = f"commit {len(canonical)}\0".encode()
83 return long_id(hashlib.new(hash_algo, header + canonical).hexdigest(), hash_algo)
84
85
86 def commit_identity_bytes(
87 parent_ids: list[str],
88 snapshot_id: str,
89 message: str,
90 committed_at_iso: str,
91 author: str = "",
92 signer_public_key: str = "",
93 ) -> bytes:
94 """Return the canonical bytes that are hashed to produce a commit ID.
95
96 Used by signature verification — the signed payload is these bytes.
97 """
98 from muse.core.types import split_id
99 parts = [
100 _SEP.join(sorted(split_id(p)[1] for p in parent_ids)),
101 split_id(snapshot_id)[1] if snapshot_id else "",
102 message,
103 committed_at_iso,
104 author,
105 signer_public_key,
106 ]
107 canonical = _SEP.join(parts).encode()
108 header = f"commit {len(canonical)}\0".encode()
109 return header + canonical
File History 1 commit
sha256:502e812e12319e30302d07d165425ceeb21687ad30e8af3a439b1f6de36c64d6 fix: update stale tag tests to use label for free-form anno… Sonnet 4.6 19 days ago