1use crate::error::{Error, Result};
7use crate::objects::{parse_commit, CommitData, CommitRef, ObjectId, ObjectKind};
8
9use super::Odb;
10
11impl Odb {
12 pub fn read_commit(&self, oid: &ObjectId) -> Result<CommitData> {
32 let object = self.read(oid)?;
33 expect_commit(oid, object.kind)?;
34 parse_commit(&object.data)
35 }
36
37 pub fn commit_tree(&self, oid: &ObjectId) -> Result<ObjectId> {
49 let object = self.read(oid)?;
50 expect_commit(oid, object.kind)?;
51 Ok(CommitRef::parse(&object.data)?.tree())
52 }
53}
54
55fn expect_commit(oid: &ObjectId, actual: ObjectKind) -> Result<()> {
56 if actual == ObjectKind::Commit {
57 return Ok(());
58 }
59 Err(Error::UnexpectedObjectKind {
60 oid: *oid,
61 expected: ObjectKind::Commit,
62 actual,
63 })
64}
65
66#[cfg(test)]
67mod tests {
68 #![allow(clippy::expect_used, clippy::unwrap_used)]
69
70 use super::*;
71 use tempfile::TempDir;
72
73 fn commit_with_parent(odb: &Odb, tree: ObjectId, parent: Option<ObjectId>) -> ObjectId {
74 let parent = parent.map(|p| format!("parent {p}\n")).unwrap_or_default();
75 let body = format!(
76 "tree {tree}\n{parent}author A <a@example.com> 1 +0000\ncommitter C <c@example.com> 2 +0100\n\nsubject\n\nbody\n"
77 );
78 odb.write(ObjectKind::Commit, body.as_bytes()).unwrap()
79 }
80
81 #[test]
82 fn read_commit_matches_parse_commit() {
83 let dir = TempDir::new().unwrap();
84 let odb = Odb::new(dir.path());
85 let tree = odb.write(ObjectKind::Tree, b"").unwrap();
86 let root = commit_with_parent(&odb, tree, None);
87 let child = commit_with_parent(&odb, tree, Some(root));
88
89 let read = odb.read_commit(&child).unwrap();
90 let parsed = parse_commit(&odb.read(&child).unwrap().data).unwrap();
91 assert_eq!(read, parsed);
92 assert_eq!(read.tree, tree);
93 assert_eq!(read.parents, vec![root]);
94 assert_eq!(odb.commit_tree(&child).unwrap(), tree);
95 }
96
97 #[test]
98 fn non_commits_are_unexpected_kinds() {
99 let dir = TempDir::new().unwrap();
100 let odb = Odb::new(dir.path());
101 let tree = odb.write(ObjectKind::Tree, b"").unwrap();
102 let blob = odb.write(ObjectKind::Blob, b"data").unwrap();
103 let commit = commit_with_parent(&odb, tree, None);
104 let tag = odb
105 .write(
106 ObjectKind::Tag,
107 format!("object {commit}\ntype commit\ntag t\n\nmsg\n").as_bytes(),
108 )
109 .unwrap();
110
111 for (oid, kind) in [
112 (tree, ObjectKind::Tree),
113 (blob, ObjectKind::Blob),
114 (tag, ObjectKind::Tag),
115 ] {
116 for err in [
117 odb.read_commit(&oid).unwrap_err(),
118 odb.commit_tree(&oid).unwrap_err(),
119 ] {
120 assert!(
121 matches!(
122 err,
123 Error::UnexpectedObjectKind {
124 oid: o,
125 expected: ObjectKind::Commit,
126 actual,
127 } if o == oid && actual == kind
128 ),
129 "{err:?}"
130 );
131 assert_eq!(
132 err.to_string(),
133 format!("object {oid} is a {kind}, not a commit")
134 );
135 }
136 }
137 }
138
139 #[test]
140 fn missing_and_malformed_commits_are_errors() {
141 let dir = TempDir::new().unwrap();
142 let odb = Odb::new(dir.path());
143 let missing = ObjectId::from_hex("1111111111111111111111111111111111111111").unwrap();
144 assert!(matches!(
145 odb.read_commit(&missing),
146 Err(Error::ObjectNotFound(_))
147 ));
148 assert!(matches!(
149 odb.commit_tree(&missing),
150 Err(Error::ObjectNotFound(_))
151 ));
152
153 let no_tree = odb
154 .write(
155 ObjectKind::Commit,
156 b"author A <a@example.com> 0 +0000\n\nno tree\n",
157 )
158 .unwrap();
159 assert!(matches!(
160 odb.read_commit(&no_tree),
161 Err(Error::CorruptObject(_))
162 ));
163 assert!(matches!(
164 odb.commit_tree(&no_tree),
165 Err(Error::CorruptObject(_))
166 ));
167 }
168}