libpijul_compat/
fs_representation.rs1use backend::DEFAULT_BRANCH;
8use backend::{Hash, HashRef, MutTxn, ROOT_INODE};
9use bs58;
10use flate2;
11use ignore::overrides::OverrideBuilder;
12use ignore::WalkBuilder;
13use patch::{Patch, PatchHeader};
14use rand;
15use rand::Rng;
16use std;
17use std::collections::HashSet;
18use std::fs::canonicalize;
19use std::fs::{create_dir_all, metadata, File};
20use std::io::{BufReader, Read, Write};
21use std::path::{Path, PathBuf};
22use Result;
23
24pub const PIJUL_DIR_NAME: &'static str = ".pijul";
26
27pub fn repo_dir<P: AsRef<Path>>(p: P) -> PathBuf {
29 p.as_ref().join(PIJUL_DIR_NAME)
30}
31
32pub fn pristine_dir<P: AsRef<Path>>(p: P) -> PathBuf {
36 return p.as_ref().join(PIJUL_DIR_NAME).join("pristine");
37}
38
39pub fn patches_dir<P: AsRef<Path>>(p: P) -> PathBuf {
41 return p.as_ref().join(PIJUL_DIR_NAME).join("patches");
42}
43
44pub fn branch_changes_base_path(b: &str) -> String {
50 "changes.".to_string() + &bs58::encode(b.as_bytes()).into_string()
51}
52
53pub fn branch_changes_file(p: &Path, b: &str) -> PathBuf {
55 p.join(PIJUL_DIR_NAME).join(branch_changes_base_path(b))
56}
57
58pub fn meta_file(p: &Path) -> PathBuf {
60 p.join(PIJUL_DIR_NAME).join("meta.toml")
61}
62
63pub fn id_file(p: &Path) -> PathBuf {
67 p.join(PIJUL_DIR_NAME).join("id")
68}
69
70pub fn find_repo_root<'a>(dir: &'a Path) -> Option<PathBuf> {
73 let mut p = dir.to_path_buf();
74 loop {
75 p.push(PIJUL_DIR_NAME);
76 match metadata(&p) {
77 Ok(ref attr) if attr.is_dir() => {
78 p.pop();
79 return Some(p);
80 }
81 _ => {}
82 }
83 p.pop();
84
85 if !p.pop() {
86 return None;
87 }
88 }
89}
90
91#[doc(hidden)]
92pub const ID_LENGTH: usize = 100;
93
94pub fn create<R: Rng>(dir: &Path, mut rng: R) -> std::io::Result<()> {
97 debug!("create: {:?}", dir);
98 let mut repo_dir = repo_dir(dir);
99 create_dir_all(&repo_dir)?;
100
101 repo_dir.push("pristine");
102 create_dir_all(&repo_dir)?;
103 repo_dir.pop();
104
105 repo_dir.push("patches");
106 create_dir_all(&repo_dir)?;
107 repo_dir.pop();
108
109 repo_dir.push("id");
110 let mut f = std::fs::File::create(&repo_dir)?;
111 let mut x = String::new();
112 x.extend(rng.gen_ascii_chars().take(ID_LENGTH));
113 f.write_all(x.as_bytes())?;
114 repo_dir.pop();
115
116 repo_dir.push("version");
117 let mut f = std::fs::File::create(&repo_dir)?;
118 writeln!(f, "{}", env!("CARGO_PKG_VERSION"))?;
119 repo_dir.pop();
120
121 repo_dir.push("local");
122 create_dir_all(&repo_dir)?;
123 repo_dir.pop();
124
125 repo_dir.push("hooks");
126 create_dir_all(&repo_dir)?;
127 repo_dir.pop();
128
129 repo_dir.push("local/ignore");
130 let _f = std::fs::File::create(&repo_dir)?;
131 repo_dir.pop();
132
133 Ok(())
134}
135
136pub fn patch_file_name(hash: HashRef) -> String {
138 hash.to_base58() + ".gz"
139}
140
141pub fn read_patch(repo: &Path, hash: HashRef) -> Result<Patch> {
143 let patch_dir = patches_dir(repo);
144 let path = patch_dir.join(&patch_file_name(hash));
145 debug!("read_patch, reading from {:?}", path);
146 let f = File::open(path)?;
147 let mut f = BufReader::new(f);
148 let (_, _, patch) = Patch::from_reader_compressed(&mut f)?;
149 Ok(patch)
150}
151
152pub fn read_patch_nochanges(repo: &Path, hash: HashRef) -> Result<PatchHeader> {
155 let patch_dir = patches_dir(repo);
156 let path = patch_dir.join(&patch_file_name(hash));
157 debug!("read_patch_nochanges, reading from {:?}", path);
158 let f = File::open(path)?;
159 let mut f = flate2::bufread::GzDecoder::new(BufReader::new(f));
160 Ok(PatchHeader::from_reader_nochanges(&mut f)?)
161}
162
163pub fn read_dependencies(repo: &Path, hash: HashRef) -> Result<Vec<Hash>> {
166 let patch_dir = patches_dir(repo);
167 let path = patch_dir.join(&patch_file_name(hash));
168 debug!("read_patch_nochanges, reading from {:?}", path);
169 let f = File::open(path)?;
170 let mut f = flate2::bufread::GzDecoder::new(BufReader::new(f));
171 Ok(Patch::read_dependencies(&mut f)?)
172}
173
174pub fn ignore_file(repo_root: &Path) -> PathBuf {
175 repo_root.join(PIJUL_DIR_NAME).join("local").join("ignore")
176}
177
178pub fn untracked_files<T: rand::Rng>(txn: &MutTxn<T>, repo_root: &Path) -> HashSet<PathBuf> {
179 let known_files = txn.list_files(ROOT_INODE).unwrap_or_else(|_| vec![]);
180
181 let o = OverrideBuilder::new(repo_root)
182 .add("!.pijul")
183 .unwrap()
184 .build()
185 .unwrap(); let mut w = WalkBuilder::new(repo_root);
189 w.git_ignore(false)
190 .git_exclude(false)
191 .git_global(false)
192 .hidden(false);
193
194 w.add_ignore(ignore_file(repo_root));
196 w.overrides(o);
197
198 let mut ret = HashSet::<PathBuf>::new();
199 for f in w.build() {
200 if let Ok(f) = f {
201 let p = f.path();
202 if p == repo_root {
203 continue;
204 }
205 let pb = p.to_path_buf();
206
207 if let Ok(stripped) = p.strip_prefix(repo_root) {
208 if known_files.iter().any(|t| *t == stripped) {
209 continue;
210 }
211 }
212 ret.insert(pb);
213 }
214 }
215 ret
216}
217
218pub fn get_current_branch(root: &Path) -> Result<String> {
219 debug!("path: {:?}", root);
220 let mut path = repo_dir(&canonicalize(root)?);
221 path.push("current_branch");
222 if let Ok(mut f) = File::open(&path) {
223 let mut s = String::new();
224 f.read_to_string(&mut s)?;
225 Ok(s.trim().to_string())
226 } else {
227 Ok(DEFAULT_BRANCH.to_string())
228 }
229}
230
231pub fn set_current_branch(root: &Path, branch: &str) -> Result<()> {
232 debug!("set current branch: root={:?}, branch={:?}", root, branch);
233 let mut path = repo_dir(&canonicalize(root)?);
234 path.push("current_branch");
235 let mut f = File::create(&path)?;
236 f.write_all(branch.trim().as_ref())?;
237 f.write_all(b"\n")?;
238 Ok(())
239}