Skip to content

Commit

Permalink
feat: git commit
Browse files Browse the repository at this point in the history
  • Loading branch information
derdilla committed Jul 4, 2024
1 parent b71459b commit 4688694
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 23 deletions.
105 changes: 87 additions & 18 deletions rust/vcs/src/git/objects.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
use std::collections::HashMap;
use std::fmt::format;
use std::io::Bytes;
use std::io::{Bytes, Read};

pub(crate) trait BinSerializable {
/// Read git object contents without header or compression.
Expand All @@ -9,7 +7,7 @@ pub(crate) trait BinSerializable {
}

pub enum GitObject {
Commit,
Commit(GitCommit),
Tree,
Tag,
Blob(GitBlob),
Expand All @@ -26,7 +24,17 @@ pub enum GitObjectType {

impl GitObject {
/// Serialize a git object including the header and compression.
pub fn serialize(&self) -> Vec<u8> {
pub fn serialize(self) -> Vec<u8> {
match self {
GitObject::Commit(commit) => {
commit.serialize();
}
GitObject::Tree => {}
GitObject::Tag => {}
GitObject::Blob(blob) => {
blob.serialize();
}
}
todo!()
}
}
Expand All @@ -50,17 +58,66 @@ impl BinSerializable for GitBlob {

// https://wyag.thb.lt/#orgfe2859f
pub struct GitCommit {
kvlm: Vec<(String, String)>,
}

impl GitCommit {
/// Reference to a tree object.
tree: String,
pub fn get_tree(&self) -> Option<String> {
self.kvlm
.iter().filter(|(k,_)| k == "tree")
.map(|(_k,v)| v.trim_matches(|e| e == '\n').to_string())
.next()
}
/// References to commits this commit is based on.
///
/// - merge commits may have multiple
/// - the first commit may have none
parent: Vec<String>,
author: String,
commiter: String,
pub fn get_parents(&self) -> Vec<String> {
self.kvlm
.iter().filter(|(k,_)| k == "parent")
.map(|(_k, v)| v.trim_matches(|e| e == '\n').to_string())
.collect::<Vec<String>>()
}
/// Like: `Scott Chacon <[email protected]> 1243040974 -0700`
pub fn get_author(&self) -> Option<String> {
self.kvlm
.iter().filter(|(k,_)| k == "author")
.map(|(_k,v)| v.trim_matches(|e| e == '\n').to_string())
.next()
}
pub fn get_commiter(&self) -> Option<String> {
self.kvlm
.iter().filter(|(k,_)| k == "committer")
.map(|(_k,v)| v.trim_matches(|e| e == '\n').to_string())
.next()
}
/// PGP signature of the object.
gpgsig: String,
pub fn get_gpgsig(&self) -> Option<String> {
self.kvlm
.iter().filter(|(k,_)| k == "gpgsig")
.map(|(_k,v)| v.trim_matches(|e| e == '\n').to_string())
.next()
}
pub fn get_message(&self) -> Option<String> {
self.kvlm
.iter().filter(|(k,_)| k == "__message__")
.map(|(_k,v)| v.trim_matches(|e| e == '\n').to_string())
.next()
}

}

impl BinSerializable for GitCommit {
fn deserialize(data: Vec<u8>) -> Self {
GitCommit {
kvlm: kvlm_parse(data.bytes()),
}
}

fn serialize(self) -> Vec<u8> {
kvlm_serialize(self.kvlm)
}
}

/// Recursively parse a Key-Value List with Message.
Expand Down Expand Up @@ -88,7 +145,6 @@ fn kvlm_parse(mut raw: Bytes<&[u8]>) -> Vec<(String, String)> {
let mut key: Option<String> = None;
let mut value = String::new();
for line in lines {
print!("{}, {}", in_message_block, &line);
if in_message_block {
value.push_str(line.as_str());
} else if line.starts_with(" ") || line.is_empty() {
Expand All @@ -99,7 +155,6 @@ fn kvlm_parse(mut raw: Bytes<&[u8]>) -> Vec<(String, String)> {
if let Some(last_key) = key {
let last_value = value.replace("\n ", "\n");
kv_entries.push((last_key, last_value));
value = String::new();
key = None;
}
in_message_block = true;
Expand Down Expand Up @@ -133,10 +188,10 @@ fn kvlm_parse(mut raw: Bytes<&[u8]>) -> Vec<(String, String)> {
fn kvlm_serialize(kvlm: Vec<(String, String)>) -> Vec<u8> { // TODO: test
let mut out = String::new();
for (k, v) in kvlm {
let v = v.replace("\n", "\n ");
if k == "__message__" {
out.push_str(format!("\n{v}").as_str())
} else {
let v = v.replace("\n", "\n ");
out.push_str(format!("{k} {v} \n").as_str());
}
}
Expand All @@ -146,11 +201,10 @@ fn kvlm_serialize(kvlm: Vec<(String, String)>) -> Vec<u8> { // TODO: test
#[cfg(test)]
mod tests {
use std::io::Read;
use crate::git::objects::kvlm_parse;

#[test]
fn kvlm_parses() {
let parsed = kvlm_parse("tree 29ff16c9c14e2652b22f8b78bb08a5a07930c147
use crate::git::objects::{BinSerializable, GitCommit, kvlm_parse};

const SAMPLE_COMMIT: &str = "tree 29ff16c9c14e2652b22f8b78bb08a5a07930c147
parent 206941306e8a8af65b66eaaaea388a7ae24d49a0
author Thibault Polge <[email protected]> 1527025023 +0200
committer Thibault Polge <[email protected]> 1527025044 +0200
Expand All @@ -169,7 +223,11 @@ gpgsig -----BEGIN PGP SIGNATURE-----\n \n iQIzBAABCAAdFiEExwXquOM8bWb4Q2zVGxM2Fx
=lgTX
-----END PGP SIGNATURE-----
Create first draft".as_bytes().bytes());
Create first draft";

#[test]
fn kvlm_parses() {
let parsed = kvlm_parse(SAMPLE_COMMIT.as_bytes().bytes());
// TODO: verify \n is wanted
assert_eq!(parsed.get(0).unwrap().0, "tree");
assert_eq!(parsed.get(0).unwrap().1, "29ff16c9c14e2652b22f8b78bb08a5a07930c147\n");
Expand All @@ -187,4 +245,15 @@ Create first draft".as_bytes().bytes());
assert_eq!(parsed.get(5).unwrap().0, "__message__");
assert_eq!(parsed.get(5).unwrap().1, "Create first draft");
}

#[test]
fn git_commit_deserialize() {
let commit = GitCommit::deserialize(SAMPLE_COMMIT.as_bytes().to_vec());
assert_eq!(commit.get_tree(), Some(String::from("29ff16c9c14e2652b22f8b78bb08a5a07930c147")));
assert_eq!(commit.get_parents().iter().next().unwrap().clone(), String::from("206941306e8a8af65b66eaaaea388a7ae24d49a0"));
assert_eq!(commit.get_author(), Some(String::from("Thibault Polge <[email protected]> 1527025023 +0200")));
assert_eq!(commit.get_commiter(), Some(String::from("Thibault Polge <[email protected]> 1527025044 +0200")));
assert!(commit.get_gpgsig().unwrap().contains("iQIzBAABCAAdFiEExwXquOM8bWb4Q2zVGxM2FxoLkGQFAlsEjZQACgkQGxM2FxoL"));
assert_eq!(commit.get_message(), Some(String::from("Create first draft")));
}
}
8 changes: 3 additions & 5 deletions rust/vcs/src/git/repo.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
use std::ffi::OsStr;
use iniconf::{IniFile, IniFileOpenError};
use std::fs;
use std::io::Read;
use std::path::{Path, PathBuf};
use log::warn;
use sha1::{Sha1, Digest};
use crate::git;
use crate::git::objects::{GitBlob, GitObject, BinSerializable, GitObjectType};
use crate::git::objects::{GitBlob, GitObject, BinSerializable, GitObjectType, GitCommit};

pub struct Repository {
/// Where the files meant to be in version control live.
Expand Down Expand Up @@ -142,7 +140,7 @@ impl Repository {
let path = self.repo_path(vec!["objects", &sha[0..2], &sha[2..sha.len()]], None, Some(true));
if path.as_ref().is_some_and(|p| p.is_file()) {
if let Ok(data) = fs::read(path.unwrap()) {
let mut data = flate2::read::ZlibDecoder::new(&data[..]);
let data = flate2::read::ZlibDecoder::new(&data[..]);
let mut data = data.bytes();
let mut obj_type = String::new();
while let Some(Ok(byte)) = data.next() {
Expand All @@ -165,7 +163,7 @@ impl Repository {
assert_eq!(obj_len as usize, remaining_bits.len());

let obj = match obj_type.as_str() {
"commit" => GitObject::Commit,
"commit" => GitObject::Commit(GitCommit::deserialize(remaining_bits)),
"tree" => GitObject::Tree,
"tag" => GitObject::Tag,
"blob" => GitObject::Blob(GitBlob::deserialize(remaining_bits)),
Expand Down

0 comments on commit 4688694

Please sign in to comment.