ghp/
import.rs

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
9/// `import_dir` takes a directory, a branch and a message and will create a commit on that branch
10/// with the contents of the directory.
11pub 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        // TODO: Check if in git repo
66
67        try!(self.start_commit());
68
69        for entry in WalkDir::new(&self.dir) {
70            let entry = entry.unwrap(); // TODO: Clean up unwrap
71            if entry.metadata().unwrap().is_file() {
72                // TODO: Should make this a trace log and not a just a println
73                // println!("adding {}", entry.path().display());
74                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}