1use std::path::{Path, PathBuf};
2use std::process::{Command, Stdio, ChildStdin};
3use std::io::{Write, Read};
4use error::{Result, Error};
5use std::fs::File;
6use std::fs::Permissions;
7use walkdir::WalkDir;
8
9pub fn import_dir<P>(dir: P, branch: &str, message: &str) -> Result<()>
12 where P: AsRef<Path> + Clone
13{
14 let mut cmd = try!(Command::new("git")
15 .arg("fast-import")
16 .arg("--date-format=now")
17 .arg("--quiet")
18 .stdin(Stdio::piped())
19 .spawn());
20
21
22 let stdin = match cmd.stdin.take() {
23 Some(buf) => buf,
24 None => return Err(Error::from("did not capture stdin")),
25 };
26
27 try!(Import::new(stdin, branch, &dir, message).import());
28
29 try!(cmd.kill());
30
31 Ok(())
32}
33
34#[cfg(unix)]
35fn is_executable(permissions: &Permissions) -> bool {
36 use std::os::unix::fs::PermissionsExt;
37 permissions.mode() & 0o700 == 0o700
38}
39
40#[cfg(windows)]
41fn is_executable(_: &Permissions) -> bool {
42 false
43}
44
45struct Import {
46 stdin: ChildStdin,
47 branch: String,
48 message: String,
49 dir: PathBuf,
50}
51
52impl Import {
53 pub fn new<P>(stdin: ChildStdin, branch: &str, dir: P, message: &str) -> Import
54 where P: AsRef<Path>
55 {
56 Import {
57 stdin: stdin,
58 branch: branch.to_owned(),
59 message: message.to_owned(),
60 dir: dir.as_ref().to_owned(),
61 }
62 }
63
64 pub fn import(&mut self) -> Result<()> {
65 try!(self.start_commit());
68
69 for entry in WalkDir::new(&self.dir) {
70 let entry = entry.unwrap(); if entry.metadata().unwrap().is_file() {
72 try!(self.add_file(entry.path()));
75 }
76 }
77
78 try!(self.stdin.write("\n".as_bytes()));
79
80 Ok(())
81 }
82
83 fn start_commit(&mut self) -> Result<()> {
84 let name = try!(self.get_config("user.name"));
85 let email = try!(self.get_config("user.email"));
86 let branch = self.branch.clone();
87 let message = self.message.clone();
88
89 try!(self.write(&format!("commit refs/heads/{}\n", branch)));
90 try!(self.write(&format!("committer {} <{}> now\n",
91 &*name.replace("\n", ""),
92 &*email.replace("\n", ""))));
93
94 try!(self.write(&format!("data {}\n{}\n", message.len(), message)));
95
96 if let Ok(rev) = self.get_prev_commit() {
97 if rev.len() > 4 {
98 try!(self.write(&format!("from {}\n", &*rev.replace("\n", ""))));
99 }
100 }
101
102 try!(self.write("deleteall\n"));
103
104 Ok(())
105 }
106
107 fn add_file<P>(&mut self, filename: P) -> Result<()>
108 where P: AsRef<Path>
109 {
110 let filename = filename.as_ref();
111
112
113 let dir = self.dir.clone();
114 let filename_rel = match filename.strip_prefix(&dir) {
115 Ok(path) => path,
116 _ => filename,
117 };
118
119 let filename_str = match filename_rel.to_str() {
120 Some(name) => name,
121 None => return Err(Error::from("could not convert string to utf8")),
122 };
123
124 let mut file = try!(File::open(filename));
125 let metadata = try!(file.metadata());
126
127 if is_executable(&metadata.permissions()) {
128 try!(self.write(&format!("M 100755 inline {}\n", filename_str)));
129 } else {
130 try!(self.write(&format!("M 100644 inline {}\n", filename_str)));
131 }
132
133 try!(self.write(&format!("data {}\n", metadata.len())));
134
135 let bytes = {
136 let mut bytes = vec![0u8; metadata.len() as usize];
137 try!(file.read(&mut bytes));
138 bytes
139 };
140
141 try!(self.stdin.write(&bytes));
142
143 try!(self.write("\n"));
144
145 Ok(())
146 }
147
148 fn get_prev_commit(&self) -> Result<String> {
149 let output = try!(Command::new("git")
150 .arg("rev-list")
151 .arg("--max-count=1")
152 .arg(&self.branch)
153 .arg("--")
154 .output());
155
156 Ok(try!(String::from_utf8(output.stdout)))
157 }
158
159 fn write(&mut self, val: &str) -> Result<usize> {
160 Ok(try!(self.stdin.write(&val.as_bytes())))
161 }
162
163 fn get_config(&self, key: &str) -> Result<String> {
164 let output = try!(Command::new("git")
165 .arg("config")
166 .arg(key)
167 .output());
168
169 Ok(try!(String::from_utf8(output.stdout)))
170 }
171}