├── .gitignore ├── Cargo.toml ├── README.md └── src ├── fs ├── inmem.rs ├── mac.rs └── mod.rs ├── index ├── diff.rs └── mod.rs ├── lib.rs ├── main.rs └── object ├── blob.rs ├── commit.rs ├── mod.rs └── tree.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "clumsy" 3 | version = "0.1.3" 4 | authors = ["uzimaru0000 "] 5 | edition = "2018" 6 | 7 | [features] 8 | json = ["serde", "chrono/serde"] 9 | 10 | [dependencies] 11 | libflate = "1.0.2" 12 | sha-1 = { version = "0.9.1", default-features = true } 13 | chrono = { version = "0.4.15", features = ["serde"] } 14 | hex = "0.4.2" 15 | serde = { version = "1.0.116", features = ["derive"], optional = true } 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # clumsy 2 | 3 | **clumsy is a clone of git implemented in rust.** 4 | 5 | ## features 6 | 7 | - json 8 | - You can serialize git objects and other structures with serde. 9 | -------------------------------------------------------------------------------- /src/fs/inmem.rs: -------------------------------------------------------------------------------- 1 | use super::{FileSystem, Metadata}; 2 | #[cfg(feature = "json")] 3 | use serde::ser::SerializeMap; 4 | #[cfg(feature = "json")] 5 | use serde::{Serialize, Serializer}; 6 | use std::collections::HashMap; 7 | use std::io; 8 | 9 | #[derive(Debug, Clone)] 10 | enum Entity { 11 | Dir(HashMap), 12 | File(Vec), 13 | } 14 | 15 | impl Entity { 16 | pub fn change_dir(&self, path: String) -> io::Result<&Entity> { 17 | path.split("/").try_fold(self, |st, x| match st { 18 | Self::File(_) => Err(io::Error::from(io::ErrorKind::NotFound)), 19 | Self::Dir(dir) => dir.get(x).ok_or(io::Error::from(io::ErrorKind::NotFound)), 20 | }) 21 | } 22 | 23 | pub fn change_dir_mut(&mut self, path: String) -> io::Result<&mut Entity> { 24 | path.split("/").try_fold(self, |st, x| match st { 25 | Self::File(_) => Err(io::Error::from(io::ErrorKind::NotFound)), 26 | Self::Dir(dir) => dir 27 | .get_mut(x) 28 | .ok_or(io::Error::from(io::ErrorKind::NotFound)), 29 | }) 30 | } 31 | 32 | pub fn read(&self) -> io::Result> { 33 | if let Self::File(data) = self { 34 | Ok(data.clone()) 35 | } else { 36 | Err(io::Error::from(io::ErrorKind::NotFound)) 37 | } 38 | } 39 | 40 | pub fn write(&mut self, name: String, data: &[u8]) -> io::Result<()> { 41 | if let Self::Dir(dir) = self { 42 | dir.insert(name, Self::File(data.to_vec())); 43 | Ok(()) 44 | } else { 45 | Err(io::Error::from(io::ErrorKind::NotFound)) 46 | } 47 | } 48 | 49 | pub fn make_dir(&mut self, name: String) -> io::Result<()> { 50 | if let Self::Dir(dir) = self { 51 | dir.insert(name, Self::Dir(HashMap::new())); 52 | Ok(()) 53 | } else { 54 | Err(io::Error::from(io::ErrorKind::NotFound)) 55 | } 56 | } 57 | 58 | pub fn remove(&mut self, name: String) -> io::Result<()> { 59 | let (path, name) = path_split(name); 60 | match path.len() { 61 | 0 => if let Self::Dir(dir) = self { 62 | dir.remove(&name).ok_or(io::Error::from(io::ErrorKind::InvalidInput)).map(|_| ()) 63 | } else { 64 | Err(io::Error::from(io::ErrorKind::InvalidInput)) 65 | }, 66 | _ => self.change_dir_mut(path.join("/")).and_then(|x| x.remove(name)) 67 | } 68 | } 69 | } 70 | 71 | #[cfg(feature = "json")] 72 | impl Serialize for Entity { 73 | fn serialize(&self, serializer: S) -> Result 74 | where 75 | S: Serializer, 76 | { 77 | match self { 78 | Self::Dir(dir) => { 79 | let mut s = serializer.serialize_map(Some(dir.len()))?; 80 | for (k, v) in dir { 81 | s.serialize_entry(k, v)?; 82 | } 83 | s.end() 84 | } 85 | Self::File(file) => serializer.serialize_bytes(file), 86 | } 87 | } 88 | } 89 | 90 | #[derive(Debug, Clone)] 91 | #[cfg_attr(feature = "json", derive(Serialize))] 92 | pub struct InMemFileSystem { 93 | root: Entity, 94 | } 95 | 96 | impl InMemFileSystem { 97 | pub fn init() -> Self { 98 | let root = Entity::Dir( 99 | vec![( 100 | ".git".to_owned(), 101 | Entity::Dir( 102 | vec![ 103 | ("objects".to_owned(), Entity::Dir(HashMap::new())), 104 | ( 105 | "refs".to_owned(), 106 | Entity::Dir( 107 | vec![("heads".to_owned(), Entity::Dir(HashMap::new()))] 108 | .into_iter() 109 | .collect::>(), 110 | ), 111 | ), 112 | ( 113 | "HEAD".to_owned(), 114 | Entity::File(b"ref: refs/heads/master".to_vec()), 115 | ), 116 | ] 117 | .into_iter() 118 | .collect::>(), 119 | ), 120 | )] 121 | .into_iter() 122 | .collect::>(), 123 | ); 124 | 125 | Self { root } 126 | } 127 | } 128 | 129 | impl FileSystem for InMemFileSystem { 130 | fn read(&self, path: String) -> io::Result> { 131 | self.root.change_dir(path).and_then(|x| x.read()) 132 | } 133 | 134 | fn write(&mut self, path: String, data: &[u8]) -> io::Result<()> { 135 | let (dir_name, file) = path_split(path); 136 | 137 | if dir_name.len() > 0 { 138 | self.root.change_dir_mut(dir_name.join("/")) 139 | } else { 140 | Ok(&mut self.root) 141 | } 142 | .and_then(|x| x.write(file, data)) 143 | } 144 | 145 | fn stat(&self, path: String) -> io::Result { 146 | let entity = self.root.change_dir(path)?; 147 | 148 | if let Entity::File(_) = entity { 149 | Ok(Metadata { 150 | dev: 0, 151 | ino: 0, 152 | mode: 33188, 153 | uid: 0, 154 | gid: 0, 155 | size: 0, 156 | mtime: 0, 157 | mtime_nsec: 0, 158 | ctime: 0, 159 | ctime_nsec: 0, 160 | }) 161 | } else { 162 | Err(io::Error::from(io::ErrorKind::InvalidData)) 163 | } 164 | } 165 | 166 | fn create_dir(&mut self, path: String) -> io::Result<()> { 167 | let (dir_name, dir) = path_split(path); 168 | self.root 169 | .change_dir_mut(dir_name.join("/")) 170 | .and_then(|x| x.make_dir(dir)) 171 | } 172 | 173 | fn rename(&mut self, from: String, to: String) -> io::Result<()> { 174 | let file = self.read(from.clone())?; 175 | self.remove(from.clone())?; 176 | self.write(to, &file) 177 | } 178 | 179 | fn remove(&mut self, path: String) -> io::Result<()> { 180 | self.root.remove(path) 181 | } 182 | } 183 | 184 | fn path_split(path: String) -> (Vec, String) { 185 | let iter = path.split("/").collect::>(); 186 | 187 | match iter.as_slice() { 188 | [path @ .., last] => ( 189 | path.iter().map(|&x| String::from(x)).collect::>(), 190 | last.to_string(), 191 | ), 192 | _ => (Vec::new(), String::new()), 193 | } 194 | } 195 | 196 | #[test] 197 | fn test_fs_read() { 198 | let fs = InMemFileSystem::init(); 199 | let data = fs.read(".git/HEAD".to_string()); 200 | 201 | println!("{:?}", data); 202 | 203 | if let Err(_) = data { 204 | assert!(false); 205 | } 206 | } 207 | 208 | #[test] 209 | fn test_fs_write() { 210 | let mut fs = InMemFileSystem::init(); 211 | let result = fs.write(".git/objects/hoge".to_string(), b"hello"); 212 | 213 | println!("{:?}", fs); 214 | 215 | if let Err(_) = result { 216 | assert!(false); 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/fs/mac.rs: -------------------------------------------------------------------------------- 1 | use super::{FileSystem, Metadata}; 2 | use std::env; 3 | use std::fs; 4 | use std::fs::File; 5 | use std::io; 6 | use std::io::prelude::*; 7 | #[cfg(target_os = "macos")] 8 | use std::os::macos::fs::MetadataExt; 9 | use std::path::PathBuf; 10 | 11 | #[cfg(target_os = "macos")] 12 | pub struct MacOSFileSystem { 13 | root: PathBuf, 14 | } 15 | 16 | #[cfg(target_os = "macos")] 17 | impl MacOSFileSystem { 18 | pub fn init() -> io::Result { 19 | Ok(MacOSFileSystem { 20 | root: env::current_dir()?, 21 | }) 22 | } 23 | } 24 | 25 | #[cfg(target_os = "macos")] 26 | impl FileSystem for MacOSFileSystem { 27 | fn read(&self, path: String) -> io::Result> { 28 | let mut file = File::open(self.root.join(path))?; 29 | let mut buf = Vec::new(); 30 | file.read_to_end(&mut buf)?; 31 | Ok(buf) 32 | } 33 | 34 | fn write(&mut self, path: String, data: &[u8]) -> io::Result<()> { 35 | let mut file = File::create(self.root.join(path))?; 36 | file.write_all(data)?; 37 | file.flush()?; 38 | Ok(()) 39 | } 40 | 41 | fn stat(&self, path: String) -> io::Result { 42 | let path = self.root.join(path); 43 | let metadata = path.metadata()?; 44 | 45 | Ok(Metadata { 46 | dev: metadata.st_dev() as u32, 47 | ino: metadata.st_ino() as u32, 48 | mode: metadata.st_mode(), 49 | uid: metadata.st_uid(), 50 | gid: metadata.st_gid(), 51 | size: metadata.st_size() as u32, 52 | mtime: metadata.st_mtime() as u32, 53 | mtime_nsec: metadata.st_mtime_nsec() as u32, 54 | ctime: metadata.st_ctime() as u32, 55 | ctime_nsec: metadata.st_ctime_nsec() as u32, 56 | }) 57 | } 58 | 59 | fn create_dir(&mut self, path: String) -> io::Result<()> { 60 | let path = self.root.join(path); 61 | fs::create_dir_all(path) 62 | } 63 | 64 | fn rename(&mut self, from: String, to: String) -> io::Result<()> { 65 | let from = self.root.join(from); 66 | let to = self.root.join(to); 67 | fs::rename(from, to) 68 | } 69 | 70 | fn remove(&mut self, path: String) -> io::Result<()> { 71 | let path = self.root.join(path); 72 | fs::remove_file(path) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/fs/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod inmem; 2 | pub mod mac; 3 | 4 | use std::io; 5 | 6 | pub trait FileSystem { 7 | fn read(&self, path: String) -> io::Result>; 8 | fn write(&mut self, path: String, data: &[u8]) -> io::Result<()>; 9 | fn stat(&self, path: String) -> io::Result; 10 | fn create_dir(&mut self, path: String) -> io::Result<()>; 11 | fn rename(&mut self, from: String, to: String) -> io::Result<()>; 12 | fn remove(&mut self, path: String) -> io::Result<()>; 13 | } 14 | 15 | #[derive(Debug)] 16 | pub struct Metadata { 17 | pub dev: u32, 18 | pub ino: u32, 19 | pub mode: u32, 20 | pub uid: u32, 21 | pub gid: u32, 22 | pub size: u32, 23 | pub mtime: u32, 24 | pub mtime_nsec: u32, 25 | pub ctime: u32, 26 | pub ctime_nsec: u32, 27 | } 28 | -------------------------------------------------------------------------------- /src/index/diff.rs: -------------------------------------------------------------------------------- 1 | use super::{Index, Entry}; 2 | use std::collections::HashMap; 3 | 4 | #[derive(Debug)] 5 | pub enum Diff { 6 | Remove(Entry), 7 | Rename(Entry, Entry), 8 | Modify(Entry, Entry), 9 | Add(Entry), 10 | None 11 | } 12 | 13 | struct DiffBuilder { 14 | flag: bool 15 | } 16 | 17 | impl DiffBuilder { 18 | fn new(flag: bool) -> Self { 19 | Self { 20 | flag 21 | } 22 | } 23 | 24 | fn modify(&self, f: Entry, s: Entry) -> Diff { 25 | if self.flag { 26 | Diff::Modify(f, s) 27 | } else { 28 | Diff::Modify(s, f) 29 | } 30 | } 31 | 32 | fn rename(&self, f: Entry, s: Entry) -> Diff { 33 | if self.flag { 34 | Diff::Rename(f, s) 35 | } else { 36 | Diff::Rename(s, f) 37 | } 38 | } 39 | 40 | fn add(&self, f: Entry) -> Diff { 41 | if self.flag { 42 | Diff::Add(f) 43 | } else { 44 | Diff::Remove(f) 45 | } 46 | } 47 | 48 | fn remove(&self, f: Entry) -> Diff { 49 | if self.flag { 50 | Diff::Remove(f) 51 | } else { 52 | Diff::Add(f) 53 | } 54 | } 55 | } 56 | 57 | pub fn diff_index(prev: Index, next: Index) -> Vec { 58 | let ( 59 | builder, 60 | entries_by_name, 61 | entries_by_hash, 62 | iter 63 | ) = if prev.entries.len() <= next.entries.len() { 64 | ( 65 | DiffBuilder::new(true), 66 | entries2hashmap(&prev.entries, |x| x.name.clone()), 67 | entries2hashmap(&prev.entries, |x| hex::encode(&x.hash)), 68 | next.entries.iter() 69 | ) 70 | } else { 71 | ( 72 | DiffBuilder::new(false), 73 | entries2hashmap(&next.entries, |x| x.name.clone()), 74 | entries2hashmap(&next.entries, |x| hex::encode(&x.hash)), 75 | prev.entries.iter() 76 | ) 77 | }; 78 | 79 | let diff = iter.map(|entry| match entries_by_name.get(&entry.name) { 80 | Some(e) => if entry.hash != e.hash { 81 | builder.modify(entry.clone(), e.clone()) 82 | } else { 83 | Diff::None 84 | }, 85 | None => if let Some(e) = entries_by_hash.get(&hex::encode(&entry.hash)) { 86 | if let None = entries_by_name.get(&e.name) { 87 | builder.rename(entry.clone(), e.clone()) 88 | } else { 89 | builder.add(entry.clone()) 90 | } 91 | } else { 92 | builder.remove(entry.clone()) 93 | }, 94 | }).collect(); 95 | 96 | diff 97 | } 98 | 99 | fn entries2hashmap(entries: &[Entry], key_fn: F) -> HashMap 100 | where F: Fn(&Entry) -> String 101 | { 102 | entries.iter().map(|x| (key_fn(x), x.clone())).collect::>() 103 | } 104 | -------------------------------------------------------------------------------- /src/index/mod.rs: -------------------------------------------------------------------------------- 1 | use chrono::{DateTime, TimeZone, Utc}; 2 | use sha1::{Digest, Sha1}; 3 | use std; 4 | use std::fmt; 5 | 6 | pub mod diff; 7 | 8 | #[derive(Debug, Clone)] 9 | pub struct Entry { 10 | pub c_time: DateTime, 11 | pub m_time: DateTime, 12 | pub dev: u32, 13 | pub inode: u32, 14 | pub mode: u32, 15 | pub uid: u32, 16 | pub gid: u32, 17 | pub size: u32, 18 | pub hash: Vec, 19 | pub name: String, 20 | } 21 | 22 | #[derive(Debug, Clone)] 23 | pub struct Index { 24 | pub entries: Vec, 25 | } 26 | 27 | impl Entry { 28 | pub fn new( 29 | c_time: DateTime, 30 | m_time: DateTime, 31 | dev: u32, 32 | inode: u32, 33 | mode: u32, 34 | uid: u32, 35 | gid: u32, 36 | size: u32, 37 | hash: Vec, 38 | name: String, 39 | ) -> Self { 40 | Self { 41 | c_time, 42 | m_time, 43 | dev, 44 | inode, 45 | mode, 46 | uid, 47 | gid, 48 | size, 49 | hash, 50 | name, 51 | } 52 | } 53 | 54 | pub fn from(bytes: &[u8]) -> Option { 55 | let c_time = hex_to_num(&bytes[0..4]); 56 | let c_time_nano = hex_to_num(&bytes[4..8]); 57 | let m_time = hex_to_num(&bytes[8..12]); 58 | let m_time_nano = hex_to_num(&bytes[12..16]); 59 | let dev = hex_to_num(&bytes[16..20]); 60 | let inode = hex_to_num(&bytes[20..24]); 61 | let mode = hex_to_num(&bytes[24..28]); 62 | let uid = hex_to_num(&bytes[28..32]); 63 | let gid = hex_to_num(&bytes[32..36]); 64 | let size = hex_to_num(&bytes[36..40]); 65 | let hash = Vec::from(&bytes[40..60]); 66 | let name_size = hex_to_num(&bytes[60..62]); 67 | let name = String::from_utf8(Vec::from(&bytes[62..(62 + name_size as usize)])).ok()?; 68 | 69 | let entry = Self { 70 | c_time: Utc.timestamp(c_time.into(), c_time_nano), 71 | m_time: Utc.timestamp(m_time.into(), m_time_nano), 72 | dev, 73 | inode, 74 | mode, 75 | uid, 76 | gid, 77 | size, 78 | hash, 79 | name, 80 | }; 81 | 82 | Some(entry) 83 | } 84 | 85 | pub fn as_bytes(&self) -> Vec { 86 | let ctime = self.c_time.timestamp() as u32; 87 | let ctime_nano = self.c_time.timestamp_subsec_nanos(); 88 | let mtime = self.m_time.timestamp() as u32; 89 | let mtime_nano = self.m_time.timestamp_subsec_nanos(); 90 | 91 | let meta = [ 92 | ctime, ctime_nano, mtime, mtime_nano, self.dev, self.inode, self.mode, self.uid, 93 | self.gid, self.size, 94 | ] 95 | .iter() 96 | .flat_map(|&x| Vec::from(x.to_be_bytes())) 97 | .collect::>(); 98 | 99 | let name_size = self.name.len() as u16; 100 | let name = self.name.as_bytes(); 101 | 102 | let len = 62 + name_size as usize; 103 | 104 | let padding = (0..(8 - len % 8)).map(|_| b'\0').collect::>(); 105 | 106 | [ 107 | meta, 108 | self.hash.clone(), 109 | Vec::from(name_size.to_be_bytes()), 110 | name.to_vec(), 111 | padding, 112 | ] 113 | .concat() 114 | } 115 | 116 | pub fn size(&self) -> usize { 117 | let size = 62 + self.name.len(); 118 | size + (8 - size % 8) 119 | } 120 | } 121 | 122 | impl fmt::Display for Entry { 123 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 124 | write!( 125 | f, 126 | "{} {} 0\t{}", 127 | num_to_mode(self.mode as u16), 128 | hex::encode(&self.hash), 129 | self.name 130 | ) 131 | } 132 | } 133 | 134 | impl Index { 135 | pub fn new(entries: Vec) -> Self { 136 | Self { entries } 137 | } 138 | 139 | pub fn from(bytes: &[u8]) -> Option { 140 | // インデックスファイルじゃない 141 | if &bytes[0..4] != b"DIRC" { 142 | return None; 143 | } 144 | 145 | // バージョン2だけに対応 146 | if hex_to_num(&bytes[4..8]) != 2 { 147 | return None; 148 | } 149 | 150 | let entry_num = hex_to_num(&bytes[8..12]); 151 | let entries = (0..entry_num) 152 | .try_fold((0, Vec::new()), |(offs, mut vec), _| { 153 | let entry = Entry::from(&bytes[(12 + offs)..])?; 154 | let size = entry.size(); 155 | vec.push(entry); 156 | Some((offs + size, vec)) 157 | }) 158 | .map(|(_, entries)| entries)?; 159 | 160 | Some(Self::new(entries)) 161 | } 162 | 163 | pub fn as_bytes(&self) -> Vec { 164 | let header = [ 165 | *b"DIRC", 166 | [0x00, 0x00, 0x00, 0x02], 167 | (self.entries.len() as u32).to_be_bytes(), 168 | ] 169 | .concat(); 170 | 171 | let entries = self 172 | .entries 173 | .iter() 174 | .flat_map(|x| x.as_bytes()) 175 | .collect::>(); 176 | 177 | let content = [header, entries].concat(); 178 | let hash = Vec::from(Sha1::digest(&content).as_slice()); 179 | 180 | [content, hash].concat() 181 | } 182 | } 183 | 184 | impl fmt::Display for Index { 185 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 186 | self.entries.iter().try_for_each(|e| write!(f, "{}\n", e)) 187 | } 188 | } 189 | 190 | fn hex_to_num(hex: &[u8]) -> u32 { 191 | hex.iter() 192 | .rev() 193 | .fold((0u32, 1u32), |(sum, offs), &x| { 194 | (sum + (x as u32 * offs), offs << 8) 195 | }) 196 | .0 197 | } 198 | 199 | fn num_to_mode(val: u16) -> String { 200 | let file_type = val >> 13; 201 | let (user, group, other) = { 202 | let permission = val & 0x01ff; 203 | let user = (permission & 0x01c0) >> 6; 204 | let group = (permission & 0x0038) >> 3; 205 | let other = permission & 0x0007; 206 | 207 | (user, group, other) 208 | }; 209 | 210 | format!("{:03b}{}{}{}", file_type, user, group, other) 211 | } 212 | 213 | #[test] 214 | fn test_hex_to_num() { 215 | let expected = hex_to_num(&[0x00, 0x00, 0x02, 0x62]); 216 | assert_eq!(expected, 0x0262); 217 | } 218 | 219 | #[test] 220 | fn test_entry_from() { 221 | let bytes = [ 222 | 0x5f, 0x54, 0xeb, 0x3e, 0x16, 0x01, 0xd8, 0xd8, 0x5f, 0x54, 0xeb, 0x3e, 0x16, 0x01, 0xd8, 223 | 0xd8, 0x01, 0x00, 0x00, 0x04, 0x01, 0x9c, 0xd3, 0x5c, 0x00, 0x00, 0x81, 0xa4, 0x00, 0x00, 224 | 0x01, 0xf5, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0xe6, 0x9d, 0xe2, 0x9b, 0xb2, 225 | 0xd1, 0xd6, 0x43, 0x4b, 0x8b, 0x29, 0xae, 0x77, 0x5a, 0xd8, 0xc2, 0xe4, 0x8c, 0x53, 0x91, 226 | 0x00, 0x1b, 0x68, 0x6f, 0x67, 0x65, 0x2f, 0x68, 0x75, 0x67, 0x61, 0x2f, 0x62, 0x61, 0x72, 227 | 0x2f, 0x70, 0x69, 0x79, 0x6f, 0x2f, 0x2e, 0x67, 0x69, 0x74, 0x6b, 0x65, 0x65, 0x70, 0x00, 228 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 229 | ]; 230 | 231 | let expected = Entry::from(&bytes); 232 | if let Some(entry) = expected { 233 | assert_eq!(entry.size(), 96); 234 | assert!(true); 235 | } else { 236 | assert!(false); 237 | } 238 | } 239 | 240 | #[test] 241 | fn test_index_from() { 242 | let bytes = [ 243 | 0x44, 0x49, 0x52, 0x43, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x5f, 0x4a, 0x42, 244 | 0x31, 0x07, 0x0c, 0xb5, 0x6a, 0x5f, 0x4a, 0x42, 0x31, 0x07, 0x0c, 0xb5, 0x6a, 0x01, 0x00, 245 | 0x00, 0x04, 0x01, 0x8f, 0xe1, 0xbd, 0x00, 0x00, 0x81, 0xa4, 0x00, 0x00, 0x01, 0xf5, 0x00, 246 | 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x13, 0x96, 0xef, 0x6c, 0x0b, 0x94, 0x4e, 0x24, 0xfc, 247 | 0x22, 0xf5, 0x1f, 0x18, 0x13, 0x6c, 0xd6, 0x2f, 0xfd, 0x5b, 0x0b, 0x8f, 0x00, 0x0a, 0x2e, 248 | 0x67, 0x69, 0x74, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 249 | 0x00, 0x00, 0x5f, 0x53, 0x1c, 0xf0, 0x32, 0x72, 0x44, 0x06, 0x5f, 0x53, 0x1c, 0xf0, 0x32, 250 | 0x72, 0x44, 0x06, 0x01, 0x00, 0x00, 0x04, 0x01, 0x92, 0x0d, 0xc7, 0x00, 0x00, 0x81, 0xa4, 251 | 0x00, 0x00, 0x01, 0xf5, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x01, 0x5e, 0xf7, 0xa9, 0xac, 252 | 0x08, 0xc2, 0x97, 0x50, 0xad, 0x7b, 0x6f, 0xe6, 0x96, 0xed, 0x28, 0x8b, 0x62, 0x34, 0x41, 253 | 0xfd, 0xbc, 0x00, 0x0a, 0x43, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x74, 0x6f, 0x6d, 0x6c, 0x00, 254 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5f, 0x50, 0x4f, 0xac, 0x22, 0x80, 0x54, 0x35, 255 | 0x5f, 0x50, 0x4f, 0xac, 0x22, 0x80, 0x54, 0x35, 0x01, 0x00, 0x00, 0x04, 0x01, 0x99, 0x1a, 256 | 0x71, 0x00, 0x00, 0x81, 0xa4, 0x00, 0x00, 0x01, 0xf5, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 257 | 0x00, 0x09, 0x11, 0xb4, 0x81, 0xfc, 0x1b, 0x15, 0xa5, 0x9c, 0x2b, 0x55, 0x83, 0x02, 0x5d, 258 | 0x09, 0x8e, 0x11, 0x8b, 0x90, 0xe1, 0x2e, 0x00, 0x09, 0x52, 0x45, 0x41, 0x44, 0x4d, 0x45, 259 | 0x2e, 0x6d, 0x64, 0x00, 0x5f, 0x55, 0x7c, 0xfd, 0x11, 0xc3, 0x75, 0x61, 0x5f, 0x55, 0x7c, 260 | 0xfd, 0x11, 0xc3, 0x75, 0x61, 0x01, 0x00, 0x00, 0x04, 0x01, 0x9a, 0x04, 0x12, 0x00, 0x00, 261 | 0x81, 0xa4, 0x00, 0x00, 0x01, 0xf5, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x25, 0x24, 0x97, 262 | 0x04, 0x99, 0x02, 0xe3, 0xf4, 0x4c, 0x0e, 0xd5, 0xc4, 0xe1, 0xd7, 0xa2, 0x69, 0xf0, 0x42, 263 | 0x56, 0xce, 0xcc, 0x51, 0x00, 0x10, 0x73, 0x72, 0x63, 0x2f, 0x69, 0x6e, 0x64, 0x65, 0x78, 264 | 0x2f, 0x6d, 0x6f, 0x64, 0x2e, 0x72, 0x73, 0x00, 0x00, 0x5f, 0x52, 0xee, 0x27, 0x1e, 0x41, 265 | 0x92, 0x4a, 0x5f, 0x52, 0xee, 0x27, 0x1e, 0x41, 0x92, 0x4a, 0x01, 0x00, 0x00, 0x04, 0x01, 266 | 0x8f, 0xe1, 0xc0, 0x00, 0x00, 0x81, 0xa4, 0x00, 0x00, 0x01, 0xf5, 0x00, 0x00, 0x00, 0x14, 267 | 0x00, 0x00, 0x03, 0xa1, 0xb2, 0x98, 0x40, 0xf6, 0x70, 0xca, 0x7a, 0x8b, 0x1a, 0xfb, 0x5f, 268 | 0x6e, 0x9b, 0x23, 0x88, 0x98, 0x07, 0xb4, 0x81, 0x26, 0x00, 0x0a, 0x73, 0x72, 0x63, 0x2f, 269 | 0x6c, 0x69, 0x62, 0x2e, 0x72, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5f, 270 | 0x54, 0x90, 0xb0, 0x10, 0xf6, 0x2a, 0xc0, 0x5f, 0x54, 0x90, 0xb0, 0x10, 0xf6, 0x2a, 0xc0, 271 | 0x01, 0x00, 0x00, 0x04, 0x01, 0x90, 0x09, 0xa3, 0x00, 0x00, 0x81, 0xa4, 0x00, 0x00, 0x01, 272 | 0xf5, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x05, 0xe3, 0x66, 0x67, 0xdb, 0xbb, 0xdf, 0x59, 273 | 0x23, 0x08, 0x81, 0x4e, 0x83, 0x65, 0x14, 0xeb, 0xf6, 0x6d, 0x03, 0x58, 0xb3, 0x21, 0x00, 274 | 0x0b, 0x73, 0x72, 0x63, 0x2f, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x72, 0x73, 0x00, 0x00, 0x00, 275 | 0x00, 0x00, 0x00, 0x00, 0x5f, 0x4e, 0xbd, 0x71, 0x24, 0xc8, 0xb3, 0x69, 0x5f, 0x4e, 0xbd, 276 | 0x71, 0x24, 0xc8, 0xb3, 0x69, 0x01, 0x00, 0x00, 0x04, 0x01, 0x92, 0x6b, 0xf1, 0x00, 0x00, 277 | 0x81, 0xa4, 0x00, 0x00, 0x01, 0xf5, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x03, 0xb8, 0x3e, 278 | 0x31, 0xbc, 0x74, 0xba, 0x52, 0x26, 0xd5, 0xf2, 0x74, 0xb5, 0x09, 0xe3, 0x09, 0xa4, 0x6c, 279 | 0x55, 0x15, 0x35, 0x95, 0x00, 0x12, 0x73, 0x72, 0x63, 0x2f, 0x6f, 0x62, 0x6a, 0x65, 0x63, 280 | 0x74, 0x2f, 0x62, 0x6c, 0x6f, 0x62, 0x2e, 0x72, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 281 | 0x00, 0x00, 0x5f, 0x50, 0x82, 0xed, 0x20, 0xa8, 0xe1, 0xb8, 0x5f, 0x50, 0x82, 0xed, 0x20, 282 | 0xa8, 0xe1, 0xb8, 0x01, 0x00, 0x00, 0x04, 0x01, 0x95, 0x70, 0x0d, 0x00, 0x00, 0x81, 0xa4, 283 | 0x00, 0x00, 0x01, 0xf5, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x13, 0xdf, 0xf6, 0x77, 0xcb, 284 | 0xc7, 0x39, 0xcb, 0x92, 0x36, 0xee, 0x69, 0x77, 0xdd, 0x11, 0x0b, 0x19, 0x97, 0x16, 0xae, 285 | 0x80, 0x5d, 0x00, 0x14, 0x73, 0x72, 0x63, 0x2f, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2f, 286 | 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x2e, 0x72, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 287 | 0x5f, 0x50, 0x7c, 0xa6, 0x2f, 0xfe, 0xf2, 0x7a, 0x5f, 0x50, 0x7c, 0xa6, 0x2f, 0xfe, 0xf2, 288 | 0x7a, 0x01, 0x00, 0x00, 0x04, 0x01, 0x92, 0x6b, 0xed, 0x00, 0x00, 0x81, 0xa4, 0x00, 0x00, 289 | 0x01, 0xf5, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x05, 0x86, 0xfd, 0xac, 0x7b, 0x8b, 0xd5, 290 | 0x3e, 0x80, 0xc0, 0x95, 0x64, 0x49, 0xf5, 0xc9, 0xda, 0x13, 0xea, 0x7c, 0xb1, 0x53, 0x9c, 291 | 0x00, 0x11, 0x73, 0x72, 0x63, 0x2f, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2f, 0x6d, 0x6f, 292 | 0x64, 0x2e, 0x72, 0x73, 0x00, 0x5f, 0x4e, 0xe6, 0x2d, 0x16, 0xa9, 0x8b, 0x26, 0x5f, 0x4e, 293 | 0xe6, 0x2d, 0x16, 0xa9, 0x8b, 0x26, 0x01, 0x00, 0x00, 0x04, 0x01, 0x93, 0x74, 0x33, 0x00, 294 | 0x00, 0x81, 0xa4, 0x00, 0x00, 0x01, 0xf5, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x0a, 0x1e, 295 | 0xda, 0x8b, 0x2d, 0xb1, 0xdd, 0xa2, 0x7e, 0x83, 0x41, 0x5d, 0x64, 0x38, 0x84, 0x81, 0x13, 296 | 0x12, 0xcd, 0x17, 0x75, 0xb9, 0x00, 0x12, 0x73, 0x72, 0x63, 0x2f, 0x6f, 0x62, 0x6a, 0x65, 297 | 0x63, 0x74, 0x2f, 0x74, 0x72, 0x65, 0x65, 0x2e, 0x72, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 298 | 0x00, 0x00, 0x00, 0x54, 0x52, 0x45, 0x45, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x2d, 0x31, 0x20, 299 | 0x31, 0x0a, 0x73, 0x72, 0x63, 0x00, 0x2d, 0x31, 0x20, 0x31, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 300 | 0x63, 0x74, 0x00, 0x2d, 0x31, 0x20, 0x30, 0x0a, 0x01, 0xd4, 0xa6, 0x28, 0xcc, 0xab, 0x7a, 301 | 0x38, 0x71, 0x5e, 0x9e, 0x22, 0x0b, 0x17, 0xc8, 0x89, 0xe0, 0x63, 0xdd, 0x3b, 302 | ]; 303 | 304 | let index = Index::from(&bytes); 305 | if let Some(i) = index { 306 | assert_eq!(i.entries.len(), 10); 307 | } else { 308 | assert!(false); 309 | } 310 | } 311 | 312 | #[test] 313 | fn test_num_to_mode() { 314 | let val = num_to_mode(33188); 315 | assert_eq!(val, String::from("100644")); 316 | } 317 | 318 | #[test] 319 | fn test_entry_as_bytes() { 320 | let bytes = [ 321 | 0x5f, 0x54, 0xeb, 0x3e, 0x16, 0x01, 0xd8, 0xd8, 0x5f, 0x54, 0xeb, 0x3e, 0x16, 0x01, 0xd8, 322 | 0xd8, 0x01, 0x00, 0x00, 0x04, 0x01, 0x9c, 0xd3, 0x5c, 0x00, 0x00, 0x81, 0xa4, 0x00, 0x00, 323 | 0x01, 0xf5, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0xe6, 0x9d, 0xe2, 0x9b, 0xb2, 324 | 0xd1, 0xd6, 0x43, 0x4b, 0x8b, 0x29, 0xae, 0x77, 0x5a, 0xd8, 0xc2, 0xe4, 0x8c, 0x53, 0x91, 325 | 0x00, 0x1b, 0x68, 0x6f, 0x67, 0x65, 0x2f, 0x68, 0x75, 0x67, 0x61, 0x2f, 0x62, 0x61, 0x72, 326 | 0x2f, 0x70, 0x69, 0x79, 0x6f, 0x2f, 0x2e, 0x67, 0x69, 0x74, 0x6b, 0x65, 0x65, 0x70, 0x00, 327 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 328 | ]; 329 | 330 | let entry = Entry::from(&bytes); 331 | if let Some(e) = entry { 332 | assert_eq!(e.as_bytes(), Vec::from(&bytes[..])); 333 | } else { 334 | assert!(false); 335 | } 336 | } 337 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod fs; 2 | pub mod index; 3 | pub mod object; 4 | 5 | use chrono::{Local, TimeZone, Utc}; 6 | use fs::FileSystem; 7 | use index::{Entry, Index}; 8 | use index::diff::{diff_index, Diff}; 9 | use libflate::zlib::{Decoder, Encoder}; 10 | use object::blob::Blob; 11 | use object::commit; 12 | use object::commit::Commit; 13 | use object::tree; 14 | use object::tree::Tree; 15 | use object::GitObject; 16 | use std::io; 17 | use std::io::prelude::*; 18 | 19 | #[derive(Debug)] 20 | pub struct Git { 21 | pub file_system: F, 22 | } 23 | 24 | impl Git { 25 | pub fn new(file_system: F) -> Self { 26 | Self { file_system } 27 | } 28 | 29 | pub fn read_index(&self) -> io::Result> { 30 | self.file_system.read(".git/index".to_string()) 31 | } 32 | 33 | pub fn write_index(&mut self, index: &Index) -> io::Result<()> { 34 | self.file_system 35 | .write(".git/index".to_string(), &index.as_bytes()) 36 | } 37 | 38 | pub fn read_object(&self, hash: String) -> io::Result> { 39 | let (sub_dir, file) = hash.split_at(2); 40 | self.file_system 41 | .read(format!(".git/objects/{}/{}", sub_dir, file)) 42 | } 43 | 44 | pub fn write_object(&mut self, object: &GitObject) -> io::Result<()> { 45 | let hash = hex::encode(object.calc_hash()); 46 | let (sub_dir, file) = hash.split_at(2); 47 | 48 | let path = format!(".git/objects/{}", sub_dir); 49 | // ディレクトリがなかったら 50 | if let Err(_) = self.file_system.stat(path.clone()) { 51 | self.file_system.create_dir(path.clone())?; 52 | } 53 | 54 | let path = format!("{}/{}", path, file); 55 | 56 | let mut encoder = Encoder::new(Vec::new())?; 57 | encoder.write_all(&object.as_bytes())?; 58 | let bytes = encoder.finish().into_result()?; 59 | 60 | self.file_system.write(path, &bytes) 61 | } 62 | 63 | pub fn head_ref(&self) -> io::Result { 64 | let path = ".git/HEAD".to_string(); 65 | let file = self.file_system.read(path)?; 66 | let refs = 67 | String::from_utf8(file).map_err(|_| io::Error::from(io::ErrorKind::InvalidData))?; 68 | 69 | let (prefix, path) = refs.split_at(5); 70 | 71 | if prefix != "ref: " { 72 | return Err(io::Error::from(io::ErrorKind::InvalidData)); 73 | } 74 | 75 | Ok(path.trim().to_string()) 76 | } 77 | 78 | pub fn read_ref(&self, path: String) -> io::Result { 79 | let path = format!(".git/{}", path); 80 | let file = self.file_system.read(path)?; 81 | let hash = 82 | String::from_utf8(file).map_err(|_| io::Error::from(io::ErrorKind::InvalidData))?; 83 | 84 | Ok(hash.trim().to_string()) 85 | } 86 | 87 | pub fn write_ref(&mut self, path: String, hash: &[u8]) -> io::Result<()> { 88 | let path = format!(".git/{}", path); 89 | self.file_system.write(path, hex::encode(hash).as_bytes()) 90 | } 91 | 92 | pub fn cat_file_p(&self, bytes: &[u8]) -> io::Result { 93 | let mut d = Decoder::new(&bytes[..])?; 94 | let mut buf = Vec::new(); 95 | d.read_to_end(&mut buf)?; 96 | 97 | GitObject::new(&buf).ok_or(io::Error::from(io::ErrorKind::InvalidData)) 98 | } 99 | 100 | pub fn ls_files_stage(&self, bytes: &[u8]) -> io::Result { 101 | Index::from(&bytes).ok_or(io::Error::from(io::ErrorKind::InvalidData)) 102 | } 103 | 104 | pub fn hash_object(&self, bytes: &[u8]) -> io::Result { 105 | let blob = Blob::from(&bytes).ok_or(io::Error::from(io::ErrorKind::InvalidInput))?; 106 | Ok(blob) 107 | } 108 | 109 | pub fn update_index(&self, idx: Index, hash: &[u8], file_name: String) -> io::Result { 110 | let metadata = self.file_system.stat(file_name.clone())?; 111 | let entry = Entry::new( 112 | Utc.timestamp(metadata.ctime as i64, metadata.ctime_nsec), 113 | Utc.timestamp(metadata.mtime as i64, metadata.mtime_nsec), 114 | metadata.dev, 115 | metadata.ino, 116 | metadata.mode, 117 | metadata.uid, 118 | metadata.gid, 119 | metadata.size, 120 | Vec::from(hash), 121 | file_name.clone(), 122 | ); 123 | 124 | let mut entries: Vec = idx 125 | .entries 126 | .into_iter() 127 | .filter(|x| x.name != entry.name && x.hash != entry.hash) 128 | .collect(); 129 | entries.push(entry); 130 | entries.sort_by(|a, b| a.name.cmp(&b.name)); 131 | 132 | Ok(Index::new(entries)) 133 | } 134 | 135 | pub fn write_tree(&self) -> io::Result { 136 | let bytes = self.read_index()?; 137 | let index = self.ls_files_stage(&bytes)?; 138 | 139 | let contents = index 140 | .entries 141 | .iter() 142 | // TODO: 一旦modeは `100644` で固定 143 | .map(|x| tree::File::new(100644, x.name.clone(), &x.hash)) 144 | .collect::>(); 145 | 146 | Ok(Tree::new(contents)) 147 | } 148 | 149 | pub fn commit_tree( 150 | &self, 151 | name: String, 152 | email: String, 153 | tree_hash: String, 154 | message: String, 155 | ) -> io::Result { 156 | let parent = self.head_ref().and_then(|x| self.read_ref(x)).ok(); 157 | let offs = { 158 | let local = Local::now(); 159 | *local.offset() 160 | }; 161 | let ts = offs.from_utc_datetime(&Utc::now().naive_utc()); 162 | let author = commit::User::new(name.clone(), email.clone(), ts); 163 | let commit = Commit::new(tree_hash, parent, author.clone(), author.clone(), message); 164 | 165 | Ok(commit) 166 | } 167 | 168 | pub fn update_ref(&mut self, path: String, hash: &[u8]) -> io::Result<()> { 169 | self.write_ref(path, hash) 170 | } 171 | 172 | pub fn reset_index(&mut self, hash: String) -> io::Result> { 173 | let commit = self 174 | .read_object(hash) 175 | .and_then(|x| self.cat_file_p(&x)) 176 | .and_then(|x| match x { 177 | GitObject::Commit(commit) => Ok(commit), 178 | _ => Err(io::Error::from(io::ErrorKind::InvalidData)), 179 | })?; 180 | 181 | let prev_index = self.read_index().and_then(|x| self.ls_files_stage(&x))?; 182 | let next_index = self.tree2index(commit.tree.clone())?; 183 | 184 | Ok(diff_index(prev_index, next_index)) 185 | } 186 | 187 | pub fn diff_apply(&mut self, diff: Vec) -> io::Result<()> { 188 | diff.iter().try_for_each(|d| match d { 189 | Diff::Add(e) => 190 | self.read_object(hex::encode(e.hash.clone())) 191 | .and_then(|x| self.cat_file_p(&x)) 192 | .and_then(|x| match x { 193 | GitObject::Blob(blob) => Ok(blob), 194 | _ => Err(io::Error::from(io::ErrorKind::InvalidData)), 195 | }) 196 | .and_then(|blob| self.file_system.write(e.name.clone(), blob.content.as_bytes())), 197 | Diff::Modify(e, _) => 198 | self.read_object(hex::encode(e.hash.clone())) 199 | .and_then(|x| self.cat_file_p(&x)) 200 | .and_then(|x| match x { 201 | GitObject::Blob(blob) => Ok(blob), 202 | _ => Err(io::Error::from(io::ErrorKind::InvalidData)), 203 | }) 204 | .and_then(|blob| self.file_system.write(e.name.clone(), blob.content.as_bytes())), 205 | Diff::Rename(n, p) => self.file_system.rename(p.name.clone(), n.name.clone()), 206 | Diff::Remove(e) => self.file_system.remove(e.name.clone()), 207 | Diff::None => Ok(()), 208 | }) 209 | } 210 | 211 | pub fn tree2index(&mut self, hash: String) -> io::Result 212 | { 213 | let idx = Index::new(Vec::new()); 214 | self.helper_tree2index(idx, hash, String::new()) 215 | } 216 | 217 | fn helper_tree2index(&mut self, idx: Index, hash: String, name: String) -> io::Result { 218 | let obj = self 219 | .read_object(hash.clone()) 220 | .and_then(|x| self.cat_file_p(&x))?; 221 | 222 | match obj { 223 | GitObject::Blob(blob) => { 224 | let meta = self.file_system.stat(name.clone()).unwrap_or(fs::Metadata { 225 | dev: 0, 226 | ino: 0, 227 | mode: 33188, 228 | uid: 0, 229 | gid: 0, 230 | size: 0, 231 | mtime: 0, 232 | mtime_nsec: 0, 233 | ctime: 0, 234 | ctime_nsec: 0, 235 | }); 236 | 237 | let entry = Entry::new( 238 | Utc.timestamp(meta.ctime as i64, meta.ctime_nsec), 239 | Utc.timestamp(meta.mtime as i64, meta.mtime_nsec), 240 | meta.dev, 241 | meta.ino, 242 | meta.mode, 243 | meta.uid, 244 | meta.gid, 245 | meta.size, 246 | blob.calc_hash(), 247 | name 248 | ); 249 | 250 | let mut entries: Vec = idx 251 | .entries 252 | .into_iter() 253 | .filter(|x| x.name != entry.name || x.hash != entry.hash) 254 | .collect(); 255 | entries.push(entry); 256 | entries.sort_by(|a, b| a.name.cmp(&b.name)); 257 | 258 | Ok(Index::new(entries)) 259 | }, 260 | GitObject::Tree(tree) => tree.contents.iter().try_fold(idx, |acc, x| { 261 | self.helper_tree2index(acc, hex::encode(&x.hash), format!("{}{}{}", name, if name.is_empty() { "" } else { "/" }, x.name.clone())) 262 | }), 263 | _ => Err(io::Error::from(io::ErrorKind::InvalidData)), 264 | } 265 | } 266 | } 267 | 268 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use clumsy::fs::inmem::InMemFileSystem; 2 | use clumsy::fs::mac::MacOSFileSystem; 3 | use clumsy::fs::FileSystem; 4 | use clumsy::object::GitObject; 5 | use clumsy::*; 6 | use std::io; 7 | 8 | use libflate::zlib::{Decoder, Encoder}; 9 | use std::fs::File; 10 | use std::io::prelude::*; 11 | 12 | fn main() -> io::Result<()> { 13 | let args: Vec = std::env::args().collect(); 14 | let fs = MacOSFileSystem::init()?; 15 | let mut git = Git::new(fs); 16 | 17 | let sub_cmd = args.get(1).unwrap().clone(); 18 | match sub_cmd.as_str() { 19 | "cat-file" => { 20 | let obj = cat_file_p(args.get(2).unwrap().clone())?; 21 | println!("{}", obj); 22 | Ok(()) 23 | } 24 | "hash-object" => { 25 | let blob = hash_object(args.get(2).unwrap().clone())?; 26 | println!("{}", hex::encode(blob.calc_hash())); 27 | Ok(()) 28 | } 29 | "add" => { 30 | let bytes = git.file_system.read(args.get(2).unwrap().clone())?; 31 | add(&mut git, args.get(2).unwrap().clone(), &bytes) 32 | } 33 | "commit" => commit(&mut git, args.get(2).unwrap().clone()), 34 | "switch" => switch(&mut git, args.get(2).unwrap().clone()), 35 | "log" => { 36 | let obj = log(&mut git)?; 37 | obj.iter().for_each(|x| println!("{}", x)); 38 | Ok(()) 39 | }, 40 | _ => Ok(()), 41 | } 42 | } 43 | 44 | pub fn cat_file_p(hash: String) -> io::Result { 45 | let (sub_dir, file) = hash.split_at(2); 46 | let path = format!(".git/objects/{}/{}", sub_dir, file); 47 | 48 | let mut file = File::open(path)?; 49 | let mut buf = Vec::new(); 50 | file.read_to_end(&mut buf)?; 51 | 52 | let mut d = Decoder::new(&buf[..])?; 53 | let mut buf = Vec::new(); 54 | d.read_to_end(&mut buf)?; 55 | 56 | GitObject::new(&buf).ok_or(io::Error::from(io::ErrorKind::InvalidData)) 57 | } 58 | 59 | pub fn hash_object(path: String) -> io::Result { 60 | let mut file = File::open(path)?; 61 | let mut buf = Vec::new(); 62 | file.read_to_end(&mut buf)?; 63 | 64 | object::blob::Blob::from(&buf).ok_or(io::Error::from(io::ErrorKind::InvalidInput)) 65 | } 66 | 67 | pub fn add(git: &mut Git, file_name: String, bytes: &[u8]) -> io::Result<()> { 68 | // git hash-object -w path 69 | let blob = git.hash_object(&bytes).map(GitObject::Blob)?; 70 | git.write_object(&blob)?; 71 | 72 | // git update-index --add --cacheinfo 73 | let index = git.read_index().and_then(|x| git.ls_files_stage(&x))?; 74 | let index = git.update_index(index, &blob.calc_hash(), file_name)?; 75 | git.write_index(&index)?; 76 | println!("write_index"); 77 | 78 | Ok(()) 79 | } 80 | 81 | fn commit(git: &mut Git, message: String) -> io::Result<()> { 82 | // git write-tree 83 | let tree = git.write_tree().map(GitObject::Tree)?; 84 | git.write_object(&tree)?; 85 | 86 | let tree_hash = tree.calc_hash(); 87 | // echo message | git commit-tree 88 | let commit = git 89 | .commit_tree( 90 | "uzimaru0000".to_string(), 91 | "shuji365630@gmail.com".to_string(), 92 | hex::encode(tree_hash), 93 | message, 94 | ) 95 | .map(GitObject::Commit)?; 96 | git.write_object(&commit)?; 97 | 98 | // git update-ref refs/heads/master 99 | git.update_ref(git.head_ref()?, &commit.calc_hash())?; 100 | 101 | Ok(()) 102 | } 103 | 104 | fn log(git: &mut Git) -> io::Result> { 105 | let commit = git 106 | .head_ref() 107 | .and_then(|x| git.read_ref(x)) 108 | .and_then(|x| git.read_object(x)) 109 | .and_then(|x| git.cat_file_p(&x))?; 110 | 111 | Ok((0..) 112 | .scan(Some(commit), |st, _| { 113 | let next = match st { 114 | Some(GitObject::Commit(commit)) => { 115 | if let Some(parent) = &commit.parent { 116 | git 117 | .read_object(parent.clone()) 118 | .and_then(|x| git.cat_file_p(&x)) 119 | .ok() 120 | } else { 121 | None 122 | } 123 | } 124 | _ => None, 125 | }; 126 | let curr = st.clone(); 127 | *st = next; 128 | curr 129 | }) 130 | .collect::>()) 131 | } 132 | 133 | fn switch(git: &mut Git, branch: String) -> io::Result<()> { 134 | let commit_hash = git.read_ref(format!("refs/heads/{}", branch))?; 135 | let diff = git.reset_index(commit_hash.clone())?; 136 | 137 | git.diff_apply(diff)?; 138 | 139 | let commit = git 140 | .read_object(commit_hash) 141 | .and_then(|x| git.cat_file_p(&x)) 142 | .and_then(|x| match x { 143 | GitObject::Commit(commit) => Ok(commit), 144 | _ => Err(io::Error::from(io::ErrorKind::InvalidData)), 145 | })?; 146 | let idx = git.tree2index(commit.tree)?; 147 | 148 | git.file_system.write( 149 | ".git/HEAD".to_string(), 150 | format!("ref: refs/heads/{}", branch).as_bytes(), 151 | )?; 152 | 153 | git.write_index(&idx)?; 154 | 155 | Ok(()) 156 | } 157 | -------------------------------------------------------------------------------- /src/object/blob.rs: -------------------------------------------------------------------------------- 1 | use super::ObjectType; 2 | #[cfg(feature = "json")] 3 | use serde::Serialize; 4 | use sha1::{Digest, Sha1}; 5 | use std::fmt; 6 | 7 | #[derive(Debug, Clone)] 8 | #[cfg_attr(feature = "json", derive(Serialize))] 9 | pub struct Blob { 10 | pub size: usize, 11 | pub content: String, 12 | } 13 | 14 | impl Blob { 15 | pub fn new(content: String) -> Self { 16 | Self { 17 | size: content.len(), 18 | content, 19 | } 20 | } 21 | 22 | pub fn from(bytes: &[u8]) -> Option { 23 | let content = String::from_utf8(bytes.to_vec()); 24 | 25 | match content { 26 | Ok(content) => Some(Self { 27 | size: content.len(), 28 | content, 29 | }), 30 | _ => None, 31 | } 32 | } 33 | 34 | pub fn calc_hash(&self) -> Vec { 35 | Vec::from(Sha1::digest(&self.as_bytes()).as_slice()) 36 | } 37 | 38 | pub fn as_bytes(&self) -> Vec { 39 | let header = format!("{} {}\0", ObjectType::Blob.to_string(), self.size); 40 | let store = format!("{}{}", header, self.to_string()); 41 | 42 | Vec::from(store.as_bytes()) 43 | } 44 | } 45 | 46 | impl fmt::Display for Blob { 47 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 48 | write!(f, "{}", self.content) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/object/commit.rs: -------------------------------------------------------------------------------- 1 | use chrono::{DateTime, FixedOffset, TimeZone, Utc}; 2 | #[cfg(feature = "json")] 3 | use serde::Serialize; 4 | use sha1::{Digest, Sha1}; 5 | use std::fmt; 6 | 7 | #[derive(Debug, Clone)] 8 | #[cfg_attr(feature = "json", derive(Serialize))] 9 | pub struct User { 10 | pub name: String, 11 | pub email: String, 12 | pub ts: DateTime, 13 | } 14 | 15 | #[derive(Debug, Clone)] 16 | #[cfg_attr(feature = "json", derive(Serialize))] 17 | pub struct Commit { 18 | pub tree: String, 19 | pub parent: Option, 20 | pub author: User, 21 | pub committer: User, 22 | pub message: String, 23 | } 24 | 25 | impl User { 26 | pub fn new(name: String, email: String, ts: DateTime) -> Self { 27 | Self { name, email, ts } 28 | } 29 | 30 | pub fn from(bytes: &[u8]) -> Option { 31 | let name = String::from_utf8( 32 | bytes 33 | .into_iter() 34 | .take_while(|&&x| x != b'<') 35 | .map(|&x| x) 36 | .collect(), 37 | ) 38 | .map(|x| String::from(x.trim())) 39 | .ok()?; 40 | 41 | let info = String::from_utf8( 42 | bytes 43 | .into_iter() 44 | .skip_while(|&&x| x != b'<') 45 | .map(|&x| x) 46 | .collect(), 47 | ) 48 | .ok()?; 49 | 50 | let mut info_iter = info.splitn(3, " "); 51 | 52 | let email = info_iter 53 | .next() 54 | .map(|x| String::from(x.trim_matches(|x| x == '<' || x == '>')))?; 55 | let ts = Utc.timestamp(info_iter.next().and_then(|x| x.parse::().ok())?, 0); 56 | let offset = info_iter 57 | .next() 58 | .and_then(|x| x.parse::().ok()) 59 | .map(|x| { 60 | if x < 0 { 61 | FixedOffset::west(x / 100 * 60 * 60) 62 | } else { 63 | FixedOffset::east(x / 100 * 60 * 60) 64 | } 65 | })?; 66 | 67 | Some(Self::new( 68 | name, 69 | email, 70 | offset.from_utc_datetime(&ts.naive_utc()), 71 | )) 72 | } 73 | } 74 | 75 | impl fmt::Display for User { 76 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 77 | write!( 78 | f, 79 | "{} <{}> {} {:+05}", 80 | self.name, 81 | self.email, 82 | self.ts.timestamp(), 83 | self.ts.offset().local_minus_utc() / 36 84 | ) 85 | } 86 | } 87 | 88 | impl Commit { 89 | pub fn new( 90 | tree: String, 91 | parent: Option, 92 | author: User, 93 | committer: User, 94 | message: String, 95 | ) -> Self { 96 | Self { 97 | tree, 98 | parent, 99 | author, 100 | committer, 101 | message, 102 | } 103 | } 104 | 105 | pub fn from(bytes: &[u8]) -> Option { 106 | let mut iter = bytes.split(|&x| x == b'\n').filter(|x| x != b""); 107 | 108 | let tree = iter 109 | .next() 110 | .map(|x| { 111 | x.splitn(2, |&x| x == b' ') 112 | .skip(1) 113 | .flatten() 114 | .map(|&x| x) 115 | .collect::>() 116 | }) 117 | .and_then(|x| String::from_utf8(x).ok())?; 118 | 119 | let parent = &iter 120 | .next() 121 | .map(|x| { 122 | x.splitn(2, |&x| x == b' ') 123 | .map(Vec::from) 124 | .map(|x| String::from_utf8(x).ok().unwrap_or_default()) 125 | .collect::>() 126 | }) 127 | .ok_or(Vec::new()) 128 | .and_then(|x| match x[0].as_str() { 129 | "parent" => Ok(x[1].clone()), 130 | _ => Err([x[0].as_bytes(), b" ", x[1].as_bytes()].concat()), 131 | }); 132 | 133 | let author = match parent { 134 | Ok(_) => iter.next().map(|x| Vec::from(x)), 135 | Err(v) => Some(v.clone()), 136 | } 137 | .map(|x| { 138 | x.splitn(2, |&x| x == b' ') 139 | .skip(1) 140 | .flatten() 141 | .map(|&x| x) 142 | .collect::>() 143 | }) 144 | .and_then(|x| User::from(x.as_slice()))?; 145 | 146 | let committer = iter 147 | .next() 148 | .map(|x| { 149 | x.splitn(2, |&x| x == b' ') 150 | .skip(1) 151 | .flatten() 152 | .map(|&x| x) 153 | .collect::>() 154 | }) 155 | .and_then(|x| User::from(x.as_slice()))?; 156 | 157 | let message = iter 158 | .next() 159 | .map(Vec::from) 160 | .and_then(|x| String::from_utf8(x).ok())?; 161 | 162 | Some(Self::new( 163 | tree, 164 | parent.clone().ok(), 165 | author, 166 | committer, 167 | message, 168 | )) 169 | } 170 | 171 | pub fn calc_hash(&self) -> Vec { 172 | Vec::from(Sha1::digest(&self.as_bytes()).as_slice()) 173 | } 174 | 175 | pub fn as_bytes(&self) -> Vec { 176 | let content = format!("{}", self); 177 | let header = format!("commit {}\0", content.len()); 178 | let val = format!("{}{}", header, content); 179 | 180 | Vec::from(val.as_bytes()) 181 | } 182 | } 183 | 184 | impl fmt::Display for Commit { 185 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 186 | let tree = format!("tree {}", self.tree); 187 | let parent = self 188 | .parent 189 | .clone() 190 | .map(|x| format!("parent {}\n", x)) 191 | .unwrap_or_default(); 192 | let author = format!("author {}", self.author); 193 | let committer = format!("committer {}", self.committer); 194 | 195 | write!( 196 | f, 197 | "{}\n{}{}\n{}\n\n{}\n", 198 | tree, parent, author, committer, self.message 199 | ) 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/object/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod blob; 2 | pub mod commit; 3 | pub mod tree; 4 | 5 | use blob::Blob; 6 | use commit::Commit; 7 | #[cfg(feature = "json")] 8 | use serde::ser::{Serialize, SerializeStruct, Serializer}; 9 | use std::fmt; 10 | use tree::Tree; 11 | 12 | #[derive(Debug, Copy, Clone)] 13 | pub enum ObjectType { 14 | Blob, 15 | Tree, 16 | Commit, 17 | } 18 | 19 | impl ObjectType { 20 | pub fn from(s: &str) -> Option { 21 | let mut header = s.split_whitespace(); 22 | 23 | match header.next()? { 24 | "blob" => Some(ObjectType::Blob), 25 | "tree" => Some(ObjectType::Tree), 26 | "commit" => Some(ObjectType::Commit), 27 | _ => None, 28 | } 29 | } 30 | 31 | pub fn to_string(self) -> String { 32 | match self { 33 | ObjectType::Blob => String::from("blob"), 34 | ObjectType::Tree => String::from("tree"), 35 | ObjectType::Commit => String::from("commit"), 36 | } 37 | } 38 | } 39 | 40 | #[derive(Debug, Clone)] 41 | pub enum GitObject { 42 | Blob(Blob), 43 | Tree(Tree), 44 | Commit(Commit), 45 | } 46 | 47 | impl GitObject { 48 | pub fn new(bytes: &[u8]) -> Option { 49 | let mut iter = bytes.splitn(2, |&byte| byte == b'\0'); 50 | 51 | let obj_type = iter 52 | .next() 53 | .and_then(|x| String::from_utf8(x.to_vec()).ok()) 54 | .and_then(|x| ObjectType::from(&x))?; 55 | 56 | iter.next().and_then(|x| match obj_type { 57 | ObjectType::Blob => Blob::from(x).map(GitObject::Blob), 58 | ObjectType::Tree => Tree::from(x).map(GitObject::Tree), 59 | ObjectType::Commit => Commit::from(x).map(GitObject::Commit), 60 | }) 61 | } 62 | 63 | pub fn calc_hash(&self) -> Vec { 64 | match self { 65 | Self::Blob(obj) => obj.calc_hash(), 66 | Self::Tree(obj) => obj.calc_hash(), 67 | Self::Commit(obj) => obj.calc_hash(), 68 | } 69 | } 70 | 71 | pub fn as_bytes(&self) -> Vec { 72 | match self { 73 | Self::Blob(obj) => obj.as_bytes(), 74 | Self::Tree(obj) => obj.as_bytes(), 75 | Self::Commit(obj) => obj.as_bytes(), 76 | } 77 | } 78 | } 79 | 80 | #[cfg(feature = "json")] 81 | impl Serialize for GitObject { 82 | fn serialize(&self, serializer: S) -> Result 83 | where 84 | S: Serializer, 85 | { 86 | let mut s = serializer.serialize_struct("GitObject", 2)?; 87 | match self { 88 | GitObject::Blob(blob) => { 89 | s.serialize_field("Blob", blob)?; 90 | } 91 | GitObject::Tree(tree) => { 92 | s.serialize_field("Tree", tree)?; 93 | } 94 | GitObject::Commit(commit) => { 95 | s.serialize_field("Commit", commit)?; 96 | } 97 | } 98 | s.serialize_field("hash", &hex::encode(self.calc_hash()))?; 99 | s.end() 100 | } 101 | } 102 | 103 | impl fmt::Display for GitObject { 104 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 105 | match self { 106 | Self::Blob(obj) => obj.fmt(f), 107 | Self::Tree(obj) => obj.fmt(f), 108 | Self::Commit(obj) => obj.fmt(f), 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/object/tree.rs: -------------------------------------------------------------------------------- 1 | use super::ObjectType; 2 | #[cfg(feature = "json")] 3 | use serde::Serialize; 4 | use sha1::{Digest, Sha1}; 5 | use std::fmt; 6 | 7 | #[derive(Debug, Clone)] 8 | #[cfg_attr(feature = "json", derive(Serialize))] 9 | pub struct Tree { 10 | pub contents: Vec, 11 | } 12 | 13 | #[derive(Debug, Clone)] 14 | #[cfg_attr(feature = "json", derive(Serialize))] 15 | pub struct File { 16 | pub mode: usize, 17 | pub name: String, 18 | pub hash: Vec, 19 | } 20 | 21 | impl File { 22 | pub fn new(mode: usize, name: String, hash: &[u8]) -> Self { 23 | Self { 24 | mode, 25 | name, 26 | hash: hash.to_vec(), 27 | } 28 | } 29 | 30 | pub fn from(header: &[u8], hash: &[u8]) -> Option { 31 | let split_header = String::from_utf8(header.to_vec()).ok()?; 32 | 33 | let mut iter = split_header.split_whitespace(); 34 | 35 | let mode = iter.next().and_then(|x| x.parse::().ok())?; 36 | let name = iter.next()?; 37 | 38 | Some(Self::new(mode, String::from(name), hash)) 39 | } 40 | 41 | pub fn encode(&self) -> Vec { 42 | let header = format!("{} {}\0", self.mode, self.name); 43 | [header.as_bytes(), &self.hash].concat() 44 | } 45 | } 46 | 47 | impl fmt::Display for File { 48 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 49 | write!( 50 | f, 51 | "{:>06} ??? {}\t{}", 52 | self.mode, 53 | hex::encode(&self.hash), 54 | self.name 55 | ) 56 | } 57 | } 58 | 59 | impl Tree { 60 | pub fn new(contents: Vec) -> Self { 61 | Self { contents } 62 | } 63 | 64 | pub fn from(bytes: &[u8]) -> Option { 65 | let contents: Vec = Vec::new(); 66 | let mut iter = bytes.split(|&b| b == b'\0'); 67 | 68 | let mut header = iter.next()?; 69 | let contents = iter.try_fold(contents, |mut acc, x| { 70 | let (hash, next_header) = x.split_at(20); 71 | let file = File::from(header, hash)?; 72 | 73 | acc.push(file); 74 | header = next_header; 75 | Some(acc) 76 | })?; 77 | 78 | Some(Self { contents }) 79 | } 80 | pub fn calc_hash(&self) -> Vec { 81 | let bytes = self.as_bytes(); 82 | Vec::from(Sha1::digest(&bytes).as_slice()) 83 | } 84 | 85 | pub fn as_bytes(&self) -> Vec { 86 | let content: Vec = self.contents.iter().flat_map(|x| x.encode()).collect(); 87 | let header = format!("{} {}\0", ObjectType::Tree.to_string(), content.len()); 88 | 89 | [header.as_bytes(), content.as_slice()].concat() 90 | } 91 | } 92 | 93 | impl fmt::Display for Tree { 94 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 95 | write!( 96 | f, 97 | "{}", 98 | (&self.contents) 99 | .into_iter() 100 | .map(|f| format!("{}", f)) 101 | .collect::>() 102 | .join("\n") 103 | ) 104 | } 105 | } 106 | --------------------------------------------------------------------------------