1#[macro_use]
6extern crate num_derive;
7#[macro_use]
8extern crate nom;
9
10mod delta;
11mod packfile;
12mod store;
13mod transport;
14mod utils;
15
16use crate::packfile::refs::{create_refs, resolve_ref, update_head, Refs};
17use crate::packfile::PackFile;
18use crate::store::commit::Commit;
19use crate::store::object::{GitObject, GitObjectType};
20use crate::store::tree::{EntryMode, Tree, TreeEntry};
21use crate::utils::sha1_hash;
22use byteorder::{BigEndian, WriteBytesExt};
23use rustc_serialize::hex::FromHex;
24use std::fs;
25use std::fs::{File, Permissions};
26use std::io::{Error, ErrorKind, Result as IOResult, Write};
27use std::iter::FromIterator;
28use std::os::unix::fs::MetadataExt;
29use std::os::unix::fs::PermissionsExt;
30use std::path::PathBuf;
31use transport::Transport;
32
33pub struct Repo {
35 dir: String,
36 refs: Refs,
37 count_objects: usize,
38 pack: Option<PackFile>,
39}
40
41impl Repo {
42 pub fn clone_from(url: &str, dir: Option<String>) -> IOResult<Self> {
58 let mut transport = Transport::from_url(url, dir)?;
59 let dir = transport.dir();
60 let refs = transport.discover_refs()?;
61 let mut packfile_parser = transport.fetch_packfile(&refs)?;
62 let packfile = packfile_parser.parse(Some(&dir), None)?;
63 packfile.write(&dir)?;
64 create_refs(&dir, &refs)?;
65 update_head(&dir, &refs)?;
66 let repo = Repo {
67 dir,
68 refs,
69 count_objects: packfile_parser.count_objects(),
70 pack: Some(packfile),
71 };
72 repo.checkout_head()?;
73 Ok(repo)
74 }
75
76 pub fn refs(self) -> Refs {
87 self.refs
88 }
89
90 pub fn dir(self) -> String {
99 self.dir
100 }
101
102 pub fn count_objects(self) -> usize {
113 self.count_objects
114 }
115
116 pub fn commits(&self) -> IOResult<Vec<GitObject>> {
126 let tip = resolve_ref(&self.dir, "HEAD")?;
127 let mut result = Vec::new();
128 let head = self.read_object(&tip)?;
129 if let Some(commit) = head.as_commit() {
130 result.push(head.clone());
131 self.search_parents(&mut result, &commit)?;
132 }
133 Ok(result)
134 }
135
136 fn search_parents(
137 &self,
138 mut vec_of_commits: &mut Vec<GitObject>,
139 commit: &Commit,
140 ) -> IOResult<()> {
141 if commit.has_parents() {
142 for parent in commit.parents.iter() {
143 let obj = self.read_object(parent)?;
144 if obj.object_type == GitObjectType::Commit {
145 vec_of_commits.push(obj.clone());
146 self.search_parents(&mut vec_of_commits, &obj.as_commit().unwrap())?;
147 }
148 }
149 }
150 Ok(())
151 }
152
153 fn checkout_head(&self) -> IOResult<()> {
154 let tip = resolve_ref(&self.dir, "HEAD")?;
155 let mut idx = Vec::new();
156 self.walk(&tip)
157 .and_then(|t| self.walk_tree(&self.dir, &t, &mut idx).ok());
158 write_index(&self.dir, &mut idx[..])?;
159 Ok(())
160 }
161
162 fn walk(&self, sha: &str) -> Option<Tree> {
163 self.read_object(sha)
164 .ok()
165 .and_then(|object| match object.object_type {
166 GitObjectType::Commit => object.as_commit().and_then(|c| self.extract_tree(&c)),
167 GitObjectType::Tree => object.as_tree(),
168 _ => None,
169 })
170 }
171
172 fn walk_tree(&self, parent: &str, tree: &Tree, idx: &mut Vec<IndexEntry>) -> IOResult<()> {
173 for entry in &tree.entries {
174 let &TreeEntry {
175 ref path,
176 ref mode,
177 ref sha,
178 } = entry;
179 let mut full_path = PathBuf::new();
180 full_path.push(parent);
181 full_path.push(path);
182 match *mode {
183 EntryMode::SubDirectory => {
184 fs::create_dir_all(&full_path)?;
185 let path_str = full_path.to_str().unwrap();
186 self.walk(sha)
187 .and_then(|t| self.walk_tree(path_str, &t, idx).ok());
188 }
189 EntryMode::Normal | EntryMode::Executable => {
190 let object = self.read_object(sha)?;
191 let mut file = File::create(&full_path)?;
192 file.write_all(&object.content[..])?;
193 let meta = file.metadata()?;
194 let mut perms: Permissions = meta.permissions();
195
196 let raw_mode = match *mode {
197 EntryMode::Normal => 33188,
198 _ => 33261,
199 };
200 perms.set_mode(raw_mode);
201 fs::set_permissions(&full_path, perms)?;
202
203 let idx_entry = get_index_entry(
204 &self.dir,
205 full_path.to_str().unwrap(),
206 mode.clone(),
207 sha.clone(),
208 )?;
209 idx.push(idx_entry);
210 }
211 ref e => panic!("Unsupported Entry Mode {:?}", e),
212 }
213 }
214 Ok(())
215 }
216
217 pub fn read_object(&self, sha: &str) -> IOResult<GitObject> {
218 GitObject::open(&self.dir, sha).or_else(|_| {
220 let pack = self
222 .pack
223 .as_ref()
224 .ok_or_else(|| Error::new(ErrorKind::Other, "can't read pack object"))?;
225 pack.find_by_sha(sha).map(|o| o.unwrap())
226 })
227 }
228
229 fn extract_tree(&self, commit: &Commit) -> Option<Tree> {
230 let sha = commit.tree;
231 self.read_tree(sha)
232 }
233
234 fn read_tree(&self, sha: &str) -> Option<Tree> {
235 self.read_object(sha).ok().and_then(|obj| obj.as_tree())
236 }
237}
238
239#[derive(Debug)]
240struct IndexEntry {
241 ctime: i64,
242 mtime: i64,
243 device: i32,
244 inode: u64,
245 mode: u16,
246 uid: u32,
247 gid: u32,
248 size: i64,
249 sha: Vec<u8>,
250 file_mode: EntryMode,
251 path: String,
252}
253
254fn write_index(repo: &str, entries: &mut [IndexEntry]) -> IOResult<()> {
255 let mut path = PathBuf::new();
256 path.push(repo);
257 path.push(".git");
258 path.push("index");
259 let mut idx_file = File::create(path)?;
260 let encoded = encode_index(entries)?;
261 idx_file.write_all(&encoded[..])?;
262 Ok(())
263}
264
265fn encode_index(idx: &mut [IndexEntry]) -> IOResult<Vec<u8>> {
266 let mut encoded = index_header(idx.len())?;
267 idx.sort_by(|a, b| a.path.cmp(&b.path));
268 let entries: Result<Vec<_>, _> = idx.iter().map(|e| encode_entry(e)).collect();
269 let mut encoded_entries = entries?.concat();
270 encoded.append(&mut encoded_entries);
271 let mut hash = sha1_hash(&encoded);
272 encoded.append(&mut hash);
273 Ok(encoded)
274}
275
276fn index_header(num_entries: usize) -> IOResult<Vec<u8>> {
277 let mut header = Vec::with_capacity(12);
278 let magic = 1_145_655_875; let version: u32 = 2;
280 header.write_u32::<BigEndian>(magic)?;
281 header.write_u32::<BigEndian>(version)?;
282 header.write_u32::<BigEndian>(num_entries as u32)?;
283 Ok(header)
284}
285
286fn encode_entry(entry: &IndexEntry) -> IOResult<Vec<u8>> {
287 let mut buf: Vec<u8> = Vec::with_capacity(62);
288 let &IndexEntry {
289 ctime,
290 mtime,
291 device,
292 inode,
293 mode,
294 uid,
295 gid,
296 size,
297 ..
298 } = entry;
299 let &IndexEntry {
300 ref sha,
301 ref file_mode,
302 ref path,
303 ..
304 } = entry;
305 let flags = (path.len() & 0xFFF) as u16;
306 let (encoded_type, perms) = match *file_mode {
307 EntryMode::Normal | EntryMode::Executable => (8u32, mode as u32),
308 EntryMode::Symlink => (10u32, 0u32),
309 EntryMode::Gitlink => (14u32, 0u32),
310 _ => unreachable!("Tried to create an index entry for a non-indexable object"),
311 };
312 let encoded_mode = (encoded_type << 12) | perms;
313
314 let path_and_padding = {
315 let mut v: Vec<u8> = Vec::from_iter(path.as_bytes().iter().cloned());
322 v.push(0u8);
323 let padding_size = 8 - ((v.len() - 2) % 8);
324 let padding = vec![0u8; padding_size];
325 if padding_size != 8 {
326 v.extend(padding);
327 }
328 v
329 };
330
331 buf.write_u32::<BigEndian>(ctime as u32)?;
332 buf.write_u32::<BigEndian>(0u32)?;
333 buf.write_u32::<BigEndian>(mtime as u32)?;
334 buf.write_u32::<BigEndian>(0u32)?;
335 buf.write_u32::<BigEndian>(device as u32)?;
336 buf.write_u32::<BigEndian>(inode as u32)?;
337 buf.write_u32::<BigEndian>(encoded_mode)?;
338 buf.write_u32::<BigEndian>(uid as u32)?;
339 buf.write_u32::<BigEndian>(gid as u32)?;
340 buf.write_u32::<BigEndian>(size as u32)?;
341 buf.extend_from_slice(&sha);
342 buf.write_u16::<BigEndian>(flags)?;
343 buf.extend(path_and_padding);
344 Ok(buf)
345}
346
347fn get_index_entry(
348 root: &str,
349 path: &str,
350 file_mode: EntryMode,
351 sha: String,
352) -> IOResult<IndexEntry> {
353 let file = File::open(path)?;
354 let meta = file.metadata()?;
355
356 let relative_path = PathBuf::from(path.trim_start_matches(root).trim_start_matches('/'));
359 let decoded_sha = sha
360 .from_hex()
361 .map_err(|_| Error::new(ErrorKind::Other, "can't decode sha"))?;
362
363 Ok(IndexEntry {
364 ctime: meta.ctime(),
365 mtime: meta.mtime(),
366 device: meta.dev() as i32,
367 inode: meta.ino(),
368 mode: meta.mode() as u16,
369 uid: meta.uid(),
370 gid: meta.gid(),
371 size: meta.size() as i64,
372 sha: decoded_sha,
373 file_mode,
374 path: relative_path.to_str().unwrap().to_owned(),
375 })
376}