Commit

Author:

Hash:

Timestamp:

+116 -0 +/-2 browse

Kevin Schoon [me@kevinschoon.com]

9ad7915f43c917efedf8985ac1a3aace75b73ce3

Mon, 08 Sep 2025 18:17:58 +0000 (2 months ago)

add object serializer / deserializer for repositories
add object serializer / deserializer for repositories

This adds some new helper code which lets you serialize / deserialize objects
directly into a git repository.
1diff --git a/crates/git/src/error.rs b/crates/git/src/error.rs
2index 98872f2..8a93bd4 100644
3--- a/crates/git/src/error.rs
4+++ b/crates/git/src/error.rs
5 @@ -16,6 +16,7 @@ pub enum Error {
6 BlobIsBinary,
7 ObjectNotATree,
8 NotAReference,
9+ ObjectWriter(String),
10 ConfigError(String),
11 }
12
13 @@ -62,6 +63,9 @@ impl Display for Error {
14 f,
15 "could not create a new repository {path:?}: {git_error:?}"
16 ),
17+ Error::ObjectWriter(message) => {
18+ write!(f, "failed to write object to repository: {message}")
19+ }
20 }
21 }
22 }
23 diff --git a/crates/git/src/wrapper.rs b/crates/git/src/wrapper.rs
24index bd40516..23f4145 100644
25--- a/crates/git/src/wrapper.rs
26+++ b/crates/git/src/wrapper.rs
27 @@ -27,6 +27,19 @@ pub struct Selector<'a> {
28 pub limit: Option<i64>,
29 }
30
31+ /// Serialize allows you to translate an object into content stored in a repository
32+ pub trait WriteObject {
33+ /// Return everything that is needed to store the object in the git
34+ /// repository as a vec of (path, bytes).
35+ fn to_content(&self) -> Vec<(&Path, &[u8])>;
36+ }
37+
38+ /// Deserialize reads an object from the git repository
39+ pub trait ReadObject: Default {
40+ /// Populate the object with content from the path
41+ fn set_content(&mut self, path: &Path, content: &[u8]);
42+ }
43+
44 /// Helpful wrapper around a git repository on the file system.
45 pub struct Wrapper {
46 path: PathBuf,
47 @@ -1085,6 +1098,52 @@ impl Wrapper {
48 )?;
49 Ok(())
50 }
51+
52+ pub fn write_object<T>(
53+ &self,
54+ username: &str,
55+ email: &str,
56+ message: &str,
57+ object: &T,
58+ ) -> Result<(), Error>
59+ where
60+ T: WriteObject,
61+ {
62+ let content = object.to_content();
63+ self.write(username, email, message, content.as_slice())
64+ }
65+
66+ pub fn read_object<T>(&self, paths: &[&Path]) -> Result<T, Error>
67+ where
68+ T: ReadObject,
69+ {
70+ let commit = self
71+ .repository
72+ .find_commit(self.repository.head()?.target().unwrap())?;
73+ let tree = &commit.tree()?;
74+ let objects =
75+ paths
76+ .iter()
77+ .try_fold(Vec::new(), |mut accm, path| match tree.get_path(path) {
78+ Ok(entry) => match entry.kind() {
79+ Some(ObjectType::Blob) => {
80+ accm.push((*path, entry.id()));
81+ Ok(accm)
82+ }
83+ _ => Err(Error::ObjectWriter(format!(
84+ "object at {path:?} is not a blob"
85+ ))),
86+ },
87+ Err(e) => Err(Error::ObjectWriter(e.to_string())),
88+ })?;
89+ let mut object = T::default();
90+ objects.iter().try_for_each(|(path, id)| {
91+ let blob = self.repository.find_blob(*id)?;
92+ object.set_content(path, blob.content());
93+ Ok::<_, Error>(())
94+ })?;
95+ Ok(object)
96+ }
97 }
98
99 #[cfg(test)]
100 @@ -1095,6 +1154,28 @@ mod tests {
101
102 use super::*;
103
104+ #[derive(Default)]
105+ struct TestObject {
106+ pub message: String,
107+ }
108+
109+ impl WriteObject for TestObject {
110+ fn to_content(&self) -> Vec<(&Path, &[u8])> {
111+ vec![(Path::new("message"), self.message.as_bytes())]
112+ }
113+ }
114+
115+ impl ReadObject for TestObject {
116+ fn set_content(&mut self, path: &Path, content: &[u8]) {
117+ match path.to_str().unwrap() {
118+ "message" => {
119+ self.message = String::from_utf8_lossy(content).to_string();
120+ }
121+ _ => unreachable!(),
122+ }
123+ }
124+ }
125+
126 #[test]
127 fn test_git_init() {
128 let test_dir = tempfile::tempdir().unwrap();
129 @@ -1272,4 +1353,35 @@ mod tests {
130 == "qux"
131 );
132 }
133+
134+ #[test]
135+ fn read_write_object() {
136+ let test_dir = tempfile::tempdir().unwrap();
137+ let test_repo_path =
138+ testing::init(test_dir.path(), false, &[&testing::init_commit()]).unwrap();
139+ let repository = Wrapper::new(&test_repo_path).unwrap();
140+ repository
141+ .write_object(
142+ "Automated Commit",
143+ "hello@example.org",
144+ "Object Content Test",
145+ &TestObject {
146+ message: String::from("world!"),
147+ },
148+ )
149+ .unwrap();
150+
151+ assert!(
152+ repository
153+ .read_string(Path::new("message"), None)
154+ .unwrap()
155+ .unwrap()
156+ == "world!"
157+ );
158+
159+ let object = repository
160+ .read_object::<TestObject>(&[Path::new("message")])
161+ .unwrap();
162+ assert!(object.message == "world!");
163+ }
164 }