From f6977ed56225e879a095bb744c79ec65490fdf9f Mon Sep 17 00:00:00 2001 From: Nicolay Roness Date: Thu, 12 Mar 2026 15:13:31 +0100 Subject: [PATCH] trekker ut funksjonalitet i plumbing.rs --- main.c | 6 ++ src/index.rs | 36 +++++------ src/main.rs | 158 ++++++------------------------------------------ src/object.rs | 64 +++++++++++++------- src/plumbing.rs | 122 +++++++++++++++++++++++++++++++++++++ src/tree.rs | 44 ++++++++++---- 6 files changed, 239 insertions(+), 191 deletions(-) create mode 100644 main.c create mode 100644 src/plumbing.rs diff --git a/main.c b/main.c new file mode 100644 index 0000000..88fa740 --- /dev/null +++ b/main.c @@ -0,0 +1,6 @@ +#include + +int main() { + printf("Hello, Smudtech\n"); + return 0; +} diff --git a/src/index.rs b/src/index.rs index 710a140..f6443f6 100644 --- a/src/index.rs +++ b/src/index.rs @@ -1,5 +1,6 @@ use crate::util::*; use crate::hash::*; +use crate::object::FileMode; use std::fs; use std::iter; @@ -237,6 +238,7 @@ impl IndexEntry { let size = take_u32(bytes); let key = take_hash(bytes); let flags = take_u16(bytes); + if ((flags >> 12) & 0x3) > 0 { todo!("Support non-zero stage") } @@ -253,7 +255,7 @@ impl IndexEntry { let name_bytes = take_n_bytes(bytes, name_len); let name = String::from_utf8_lossy(&name_bytes).into_owned(); if name_len > 512 { - panic!("[ERROR] Suspicous name_len {}: flag={:016b}, name={}", name_len, flags, name); + panic!("[ERROR] Suspicious name_len {}: flag={:016b}, name={}", name_len, flags, name); } IndexEntry { @@ -281,16 +283,25 @@ impl IndexEntry { (flags & 0x0FFF).into() } + pub fn file_mode(&self) -> FileMode { + match self.mode { + 0o100644 => FileMode::Blob, + 0o100755 => FileMode::BlobExe, + 0o120000 => FileMode::BlobSym, + _ => FileMode::Blob, + } + } + pub fn mode_as_octal(&self) -> String { - let object_type = (self.mode >> 12) & 0x00F; - let permission = self.mode & 0x1FF; - format!("{:02o}{:04o}", object_type, permission) + let object_type = (self.mode >> 12) & 0x00F; + let permission = self.mode & 0x1FF; + format!("{:02o}{:04o}", object_type, permission) } } impl std::fmt::Display for IndexEntry { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{} {} {}\t{}", + write!(f, "{:06} {} {}\t{}", self.mode_as_octal(), self.key, 0, @@ -344,21 +355,6 @@ mod test { assert_eq!(key, expected); } - #[test] - fn parse_mode_from_index() { - let filename = String::from("examples/index"); - - let index = Index::read(&filename); - let object_type = index.entries[0].object_type(); - let permission = index.entries[0].permission(); - - let expected_type = 0o10; - let expected_permission = 0o0644; - - assert_eq!(object_type, expected_type); - assert_eq!(permission, expected_permission); - } - #[test] fn list_entry_from_index() { let filename = String::from("examples/index"); diff --git a/src/main.rs b/src/main.rs index 7b4e022..cd42e09 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,170 +1,50 @@ use std::env; -use std::fs; -use std::io; -use std::io::{Write}; -use std::fs::File; -use std::path::{Path, PathBuf, Component}; +use std::path::PathBuf; use std::process::exit; -use std::time::SystemTime; -mod compress; -mod commit; mod command; -mod object; +mod commit; +mod compress; mod hash; mod index; +mod object; +mod plumbing; mod tree; mod util; -use commit::*; use command::*; -use hash::*; -use tree::*; use index::*; +use plumbing::*; +use tree::*; use util::*; -use object::*; const ROOT: &str = ".git"; const INDEX_FILE: &str = ".git/index"; -const BRANCH: &str = "smudtech"; +const BRANCH: &str = "main"; const IGNORE: [&str; 3] = [".git", "playground", "target"]; -fn get_author() -> Stamp { - Stamp { - name: "Nicolay Roness".to_string(), - email: "nicolay.caspersen.roness@sparebank1.no".to_string(), - timestamp: SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_secs() as u32, - } -} - -fn get_parent() -> Option { - let path = format!("{}/refs/heads/{}", ROOT, BRANCH); - if Path::new(&path).exists() { - let parent_hex = fs::read_to_string(path).unwrap(); - Some(Hash::from_hex(&parent_hex[..40])) - } else { - println!("[INFO] refs do not exists"); - None - } -} - -fn remove_leading_dot_slash(path: PathBuf) -> PathBuf { - let components: Vec<_> = path.components().collect(); - - if let Some(Component::CurDir) = components.first() { - components.iter().skip(1).collect() - } else { - path.to_path_buf() - } -} - -fn write_objects(path: PathBuf) -> Vec { - let mut entries = Vec::new(); - if path.is_dir() { - let dir = std::fs::read_dir(path).expect("Unable to read directory"); - for path in dir { - let path = path.unwrap().path(); - if IGNORE.iter().any(|i| path.ends_with(i)) { - println!("[INFO] ignoring {}", path.to_string_lossy()); - continue; - } - - let sub_directory = write_objects(path); - entries.extend(sub_directory); - } - } else { - let hash = write_blob(&path); - match hash { - Ok(hash) => { - let path = remove_leading_dot_slash(path); - let filename = path.to_string_lossy(); - let entry = IndexEntry::create(hash, &filename); - entries.push(entry); - } - Err(err) => println!("[ERROR]: Unable to write blob {:?}: {}", path, err) - } - } - entries -} - -fn update_index(entries: Vec) { - let index = Index::read(INDEX_FILE).extend(entries); - let index_bytes = index.to_bytes(); - let mut index = File::create(String::from(INDEX_FILE)).unwrap(); - index.write_all(&index_bytes).unwrap() -} - -fn write_blob(file: &PathBuf) -> Result { - let content = fs::read(file)?; - write_object(ObjectKind::Blob, content) -} - -fn write_cache(cache: TreeCache) -> Result { - let mut trees_as_bytes: Vec<(String, Vec)> = Vec::new(); - - for blob in cache.blobs { - let name = format!("{}", blob.name.to_string_lossy()); - trees_as_bytes.push((name, blob.as_bytes())); - } - for (dir, cache) in cache.trees { - let hash = write_cache(cache).unwrap_or_else(|err| - panic!("[ERROR]: Unable to create tree for '{}': {}", dir.to_string_lossy(), err) - ); - let mut bytes = format!( - "{:06} {}\0", - ObjectKind::Tree as i32, - dir.to_string_lossy(), - ).into_bytes(); - bytes.extend_from_slice(&hash.0); - let name = format!("{}/", dir.to_string_lossy()); - trees_as_bytes.push((name, bytes)); - } - trees_as_bytes.sort_by_key(|(n, _)| n.clone()); +fn add(path: PathBuf) { + // nit add + // ^ + let files = collect_files(path); - let tree: Vec = trees_as_bytes - .into_iter() - .flat_map(|(_, t)| t) - .collect(); + // git hash-object -w + let objects = write_objects(files); - write_object(ObjectKind::Tree, tree) + // git update-index + update_index(objects); } -fn commit_tree(key: Hash, message: String) -> Result { - // create commit - let parent = get_parent(); - let author = get_author(); - let committer = get_author(); - let commit = Commit::create(key, parent, author, committer, message); - - // write commit - let commit_content = commit.to_string().into_bytes(); - write_object(ObjectKind::Commit, commit_content) -} -fn update_refs(commit: Hash) -> Result<(), io::Error> { - let path = format!("{}/refs/heads/{}", ROOT, BRANCH); - let content = format!("{}", commit).into_bytes(); - write_to_file(path, content) -} -fn add(path: PathBuf) { - // hash-object -w - let entries = write_objects(path); - // update-index (loopet for alle entries - update_index(entries); -} fn commit(message: String) { - /* == Git commit == */ // 0. read staging area (index) let index = Index::read(INDEX_FILE); @@ -172,7 +52,7 @@ fn commit(message: String) { let cache = TreeCache::from_index(index); let tree_hash = write_cache(cache).unwrap(); - // 2. write to commit + // 2. write-commit let commit_hash = commit_tree(tree_hash, message).unwrap(); // 3. update refs @@ -202,7 +82,9 @@ fn main() { match command { Command::Add(path) => add(path), - Command::Commit(message) => commit(message) + Command::Commit(message) => commit(message), }; } + + diff --git a/src/object.rs b/src/object.rs index ebc6ca6..9dfd5e5 100644 --- a/src/object.rs +++ b/src/object.rs @@ -10,35 +10,56 @@ use std::path::Path; #[derive(Debug, Copy, Clone)] pub enum ObjectKind { - Blob = 100644, - BlobExe = 100755, - BlobSym = 120000, - Tree = 40000, - Commit = 0 + Blob, + Tree, + Commit, } -impl FromStr for ObjectKind { - type Err = (); - fn from_str(input: &str) -> Result { - match input { - "100644" => Ok(ObjectKind::Blob), - "100755" => Ok(ObjectKind::BlobExe), - "120000" => Ok(ObjectKind::BlobSym), - "040000" => Ok(ObjectKind::Tree), - _ => panic!("ERROR: Invalid object mode: {}", input) +impl std::fmt::Display for ObjectKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ObjectKind::Blob => write!(f, "blob"), + ObjectKind::Tree => write!(f, "tree"), + ObjectKind::Commit => write!(f, "commit"), } } +} +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum FileMode { + Blob, + BlobExe, + BlobSym, + Tree, } -impl std::fmt::Display for ObjectKind { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl FileMode { + pub fn value(&self) -> i32 { match self { - ObjectKind::Blob => write!(f, "blob"), - ObjectKind::BlobExe => write!(f, "blob"), - ObjectKind::BlobSym => write!(f, "blob"), - ObjectKind::Tree => write!(f, "tree"), - ObjectKind::Commit => write!(f, "commit"), + FileMode::Blob => 100644, + FileMode::BlobExe => 100755, + FileMode::BlobSym => 120000, + FileMode::Tree => 040000, + } + } + + pub fn object_kind(&self) -> ObjectKind { + match self { + FileMode::Tree => ObjectKind::Tree, + _ => ObjectKind::Blob, + } + } +} + +impl FromStr for FileMode { + type Err = (); + fn from_str(input: &str) -> Result { + match input { + "100644" => Ok(FileMode::Blob), + "100755" => Ok(FileMode::BlobExe), + "120000" => Ok(FileMode::BlobSym), + "40000" | "040000" => Ok(FileMode::Tree), + _ => panic!("[ERROR]: Invalid file mode: {}", input) } } } @@ -54,7 +75,6 @@ pub fn write_object(object_type: ObjectKind, content: Vec) -> Result Vec { + if path.is_dir() { + paths_in_dir(path).into_iter().flat_map(collect_files).collect() + } else { + vec![path] + } +} + +fn paths_in_dir(path: PathBuf) -> Vec { + fs::read_dir(path) + .expect("Unable to read directory") + .filter_map(|e| e.ok()) + .map(|e| e.path()) + .filter(|p| !IGNORE.iter().any(|i| p.ends_with(i))) + .collect() +} + +pub fn write_objects(files: Vec) -> Vec<(PathBuf, Hash)> { + files.into_iter().filter_map(|path| { + match write_blob(&path) { + Ok(hash) => Some((path, hash)), + Err(err) => { + println!("[ERROR]: Unable to write blob {:?}: {}", path, err); + None + } + } + }).collect() +} + +fn write_blob(file: &PathBuf) -> Result { + let content = fs::read(file)?; + write_object(ObjectKind::Blob, content) +} + +pub fn remove_leading_dot_slash(path: PathBuf) -> PathBuf { + let components: Vec<_> = path.components().collect(); + + if let Some(Component::CurDir) = components.first() { + components.iter().skip(1).collect() + } else { + path.to_path_buf() + } +} + +fn blob_to_index_entry((path, hash): (PathBuf, Hash)) -> IndexEntry { + let path = remove_leading_dot_slash(path); + IndexEntry::create(hash, &path.to_string_lossy()) +} + +pub fn update_index(blobs: Vec<(PathBuf, Hash)>) { + let new_entries = blobs + .into_iter() + .map(blob_to_index_entry) + .collect(); + + let index = Index::read(INDEX_FILE).extend(new_entries); + write_index(index); +} + +fn write_index(index: Index) { + let index_bytes = index.to_bytes(); + let mut index = File::create(String::from(INDEX_FILE)).unwrap(); + index.write_all(&index_bytes).unwrap() +} + +pub fn write_cache(cache: TreeCache) -> Result { + cache.write() +} + +pub fn commit_tree(key: Hash, message: String) -> Result { + // create commit + let parent = get_parent(); + let author = get_author(); + let committer = get_author(); + let commit = Commit::create(key, parent, author, committer, message); + + // write commit + let commit_content = commit.to_string().into_bytes(); + write_object(ObjectKind::Commit, commit_content) +} + +fn get_author() -> Stamp { + Stamp { + name: "Nicolay Roness".to_string(), + email: "nicolay.caspersen.roness@sparebank1.no".to_string(), + timestamp: SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs() as u32, + } +} + +pub fn get_parent() -> Option { + let path = format!("{}/refs/heads/{}", ROOT, BRANCH); + if Path::new(&path).exists() { + let parent_hex = fs::read_to_string(path).unwrap(); + Some(Hash::from_hex(&parent_hex[..40])) + } else { + println!("[INFO] refs do not exists"); + None + } +} + + +pub fn update_refs(commit: Hash) -> Result<(), io::Error> { + let path = format!("{}/refs/heads/{}", ROOT, BRANCH); + let content = format!("{}", commit).into_bytes(); + write_to_file(path, content) +} diff --git a/src/tree.rs b/src/tree.rs index c459030..5a70ee1 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -4,7 +4,7 @@ use crate::index::*; use crate::take_hash; use std::fmt; - +use std::io; use std::str::FromStr; use std::path::{PathBuf, Component}; use std::collections::HashMap; @@ -19,11 +19,11 @@ pub struct TreeCache { #[derive(Debug)] pub struct TreeEntry { pub key: Hash, - pub mode: ObjectKind, + pub mode: FileMode, pub name: PathBuf, } impl TreeEntry { - pub fn new(key: Hash, mode: ObjectKind, name: PathBuf) -> Self { + pub fn new(key: Hash, mode: FileMode, name: PathBuf) -> Self { TreeEntry { key, mode, name } } @@ -37,7 +37,7 @@ impl TreeEntry { println!("{} {}", i, s); } - let mode: ObjectKind = ObjectKind::from_str(data[0]).unwrap(); + let mode: FileMode = FileMode::from_str(data[0]).unwrap(); let name = data[1].parse().unwrap(); *bytes = &rest[1..]; @@ -58,8 +58,8 @@ impl TreeEntry { pub fn as_bytes(&self) -> Vec { let mut bytes = format!( - "{:06} {}\0", - self.mode as i32, + "{:06} {}\0", + self.mode.value(), self.name.to_string_lossy(), ).into_bytes(); bytes.extend_from_slice(&self.key.0); @@ -69,9 +69,9 @@ impl TreeEntry { impl fmt::Display for TreeEntry { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:06} {} {} {}", - self.mode as i32, - self.mode, + write!(f, "{:06} {} {} {}", + self.mode.value(), + self.mode.object_kind(), self.key, self.name.to_string_lossy()) } @@ -98,12 +98,12 @@ impl TreeCache { let sub_cache = cache.get_or_create_tree_mut(base); - let mode = ObjectKind::from_str(&entry.mode_as_octal()).unwrap(); + let mode = entry.file_mode(); let entry = TreeEntry::new(entry.key, mode, rest); sub_cache.add_tree(entry); } else { - let mode = ObjectKind::from_str(&entry.mode_as_octal()).unwrap(); + let mode = entry.file_mode(); let blob = TreeEntry::new(entry.key, mode, entry.name.into()); cache.add_blob(blob); } @@ -112,6 +112,28 @@ impl TreeCache { cache } + pub fn write(self) -> Result { + let mut entries: Vec<(String, Vec)> = Vec::new(); + + for blob in self.blobs { + let name = blob.name.to_string_lossy().into_owned(); + entries.push((name, blob.as_bytes())); + } + + for (dir, subtree) in self.trees { + let hash = subtree.write().unwrap_or_else(|err| + panic!("[ERROR]: Unable to create tree for '{}': {}", dir.to_string_lossy(), err) + ); + let entry = TreeEntry::new(hash, FileMode::Tree, dir.clone()); + let sort_key = format!("{}/", dir.to_string_lossy()); + entries.push((sort_key, entry.as_bytes())); + } + + entries.sort_by_key(|(n, _)| n.clone()); + let tree: Vec = entries.into_iter().flat_map(|(_, b)| b).collect(); + write_object(ObjectKind::Tree, tree) + } + pub fn get_or_create_tree_mut(&mut self, tree_name: PathBuf) -> &mut TreeCache { self.trees.entry(tree_name).or_insert(TreeCache::new()) }