Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#include <stdio.h>

int main() {
printf("Hello, Smudtech\n");
return 0;
}
36 changes: 16 additions & 20 deletions src/index.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::util::*;
use crate::hash::*;
use crate::object::FileMode;

use std::fs;
use std::iter;
Expand Down Expand Up @@ -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")
}
Expand All @@ -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 {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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");
Expand Down
158 changes: 20 additions & 138 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,178 +1,58 @@
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<Hash> {
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<IndexEntry> {
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<IndexEntry>) {
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<Hash, io::Error> {
let content = fs::read(file)?;
write_object(ObjectKind::Blob, content)
}

fn write_cache(cache: TreeCache) -> Result<Hash, io::Error> {
let mut trees_as_bytes: Vec<(String, Vec<u8>)> = 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 <path>
// ^
let files = collect_files(path);

let tree: Vec<u8> = 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<Hash, io::Error> {
// 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 <path>
let entries = write_objects(path);

// update-index <entry> (loopet for alle entries
update_index(entries);
}

fn commit(message: String) {
/* == Git commit == */
// 0. read staging area (index)
let index = Index::read(INDEX_FILE);

// 1. write-tree
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
Expand Down Expand Up @@ -202,7 +82,9 @@ fn main() {

match command {
Command::Add(path) => add(path),
Command::Commit(message) => commit(message)
Command::Commit(message) => commit(message),
};
}



64 changes: 42 additions & 22 deletions src/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Self, Self::Err> {
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<Self, Self::Err> {
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)
}
}
}
Expand All @@ -54,7 +75,6 @@ pub fn write_object(object_type: ObjectKind, content: Vec<u8>) -> Result<Hash, i
let path_str = format!("{}/{}", ROOT, hash.to_object_path());
let path = Path::new(&path_str);
if path.exists() {
println!("[INFO] {} {} already exists", object_type, hash);
return Ok(hash)
}

Expand Down
Loading