libpijul_compat/
output.rs

1use backend::*;
2use patch::*;
3use record::InodeUpdate;
4use {ErrorKind, Result};
5
6use rand;
7use std;
8use std::borrow::Cow;
9use std::collections::{HashMap, HashSet};
10use std::fs;
11use std::path::{Path, PathBuf};
12use tempdir;
13
14#[cfg(not(windows))]
15use std::os::unix::fs::PermissionsExt;
16
17#[cfg(not(windows))]
18fn set_permissions(name: &Path, permissions: u16) -> Result<()> {
19    let metadata = std::fs::metadata(&name)?;
20    let mut current = metadata.permissions();
21    debug!(
22        "setting mode for {:?} to {:?} (currently {:?})",
23        name, permissions, current
24    );
25    current.set_mode(permissions as u32);
26    std::fs::set_permissions(name, current)?;
27    Ok(())
28}
29
30#[cfg(windows)]
31fn set_permissions(_name: &Path, _permissions: u16) -> Result<()> {
32    Ok(())
33}
34
35#[derive(Debug)]
36struct OutputItem {
37    parent: Inode,
38    meta: FileMetadata,
39    key: Key<PatchId>,
40    inode: Option<Inode>,
41    is_zombie: bool,
42    related: Related,
43}
44
45#[derive(Debug, PartialEq, Eq)]
46pub enum Related {
47    No,
48    Ancestor,
49    Exact,
50}
51
52fn is_related(prefixes: &Prefixes, key: Key<PatchId>) -> Related {
53    if prefixes.0.is_empty() {
54        return Related::Exact;
55    }
56    for pref in prefixes.0.iter() {
57        let mut is_first = true;
58        for &p in pref {
59            if p == key {
60                if is_first {
61                    return Related::Exact;
62                } else {
63                    return Related::Ancestor;
64                }
65            }
66            is_first = false
67        }
68    }
69    Related::No
70}
71
72impl<'env, T: rand::Rng> MutTxn<'env, T> {
73    // Climb up the tree (using revtree).
74    fn filename_of_inode(&self, inode: Inode, working_copy: &Path) -> Option<PathBuf> {
75        let mut components = Vec::new();
76        let mut current = inode;
77        loop {
78            match self.get_revtree(current) {
79                Some(v) => {
80                    components.push(v.basename.to_owned());
81                    current = v.parent_inode.clone();
82                    if current == ROOT_INODE {
83                        break;
84                    }
85                }
86                None => {
87                    debug!("filename_of_inode: not in tree");
88                    return None;
89                }
90            }
91        }
92        let mut working_copy = working_copy.to_path_buf();
93        for c in components.iter().rev() {
94            working_copy.push(c.as_small_str().as_str());
95        }
96        Some(working_copy)
97    }
98
99    /// Collect all the children of key `key` into `files`.
100    fn collect_children(
101        &mut self,
102        branch: &Branch,
103        path: &Path,
104        key: Key<PatchId>,
105        inode: Inode,
106        base_path: &mut PathBuf,
107        prefixes: &Prefixes,
108        files: &mut HashMap<PathBuf, HashMap<Key<PatchId>, OutputItem>>,
109    ) -> Result<()> {
110        debug!("collect_children {:?}", base_path);
111        let e = Edge::zero(EdgeFlags::FOLDER_EDGE);
112        for (_, b) in self.iter_nodes(&branch, Some((key, Some(&e))))
113            .take_while(|&(k, b)| {
114                k == key
115                    && b.flag
116                        <= EdgeFlags::FOLDER_EDGE | EdgeFlags::PSEUDO_EDGE | EdgeFlags::EPSILON_EDGE
117            }) {
118            debug!("b={:?}", b);
119            let cont_b = self.get_contents(b.dest).unwrap();
120            let (_, b_key) = self.iter_nodes(&branch, Some((b.dest, Some(&e))))
121                .next()
122                .unwrap();
123            let b_inode = self.get_revinodes(b_key.dest);
124
125            // This is supposed to be a small string, so we can do
126            // as_slice.
127            if cont_b.as_slice().len() < 2 {
128                error!("cont_b {:?} b.dest {:?}", cont_b, b.dest);
129                return Err(ErrorKind::WrongFileHeader(b.dest).into());
130            }
131            let (perms, basename) = cont_b.as_slice().split_at(2);
132
133            let perms = FileMetadata::from_contents(perms);
134            let basename = std::str::from_utf8(basename).unwrap();
135            debug!("filename: {:?} {:?}", perms, basename);
136            let name = path.join(basename);
137            let related = is_related(&prefixes, b_key.dest);
138            debug!("related {:?} = {:?}", base_path, related);
139            if related != Related::No {
140                let v = files.entry(name).or_insert(HashMap::new());
141                if v.get(&b.dest).is_none() {
142                    let is_zombie = {
143                        let e = Edge::zero(
144                            EdgeFlags::FOLDER_EDGE | EdgeFlags::PARENT_EDGE
145                                | EdgeFlags::DELETED_EDGE,
146                        );
147                        self.iter_nodes(&branch, Some((b_key.dest, Some(&e))))
148                            .take_while(|&(k, b)| k == b_key.dest && b.flag == e.flag)
149                            .next()
150                            .is_some()
151                    };
152                    debug!("is_zombie = {:?}", is_zombie);
153                    v.insert(
154                        b.dest,
155                        OutputItem {
156                            parent: inode,
157                            meta: perms,
158                            key: b_key.dest,
159                            inode: b_inode,
160                            is_zombie,
161                            related,
162                        },
163                    );
164                }
165            }
166        }
167        Ok(())
168    }
169
170    /// Collect names of files with conflicts
171    ///
172    /// As conflicts have an internal representation, it can be determined
173    /// exactly which files contain conflicts.
174    pub fn list_conflict_files(
175        &mut self,
176        branch_name: &str,
177        prefixes: &[&str],
178    ) -> Result<Vec<PathBuf>> {
179        let mut files = HashMap::new();
180        let mut next_files = HashMap::new();
181        let branch = self.open_branch(branch_name)?;
182        let mut base_path = PathBuf::new();
183        let prefixes = prefixes.to_prefixes(self, &branch);
184        self.collect_children(
185            &branch,
186            "".as_ref(),
187            ROOT_KEY,
188            ROOT_INODE,
189            &mut base_path,
190            &prefixes,
191            &mut files,
192        )?;
193
194        let mut ret = vec![];
195        let mut forward = Vec::new();
196        while !files.is_empty() {
197            next_files.clear();
198            for (a, b) in files.drain() {
199                for (_, output_item) in b {
200                    // (_, meta, inode_key, inode, is_zombie)
201                    // Only bother with existing files
202                    if let Some(inode) = output_item.inode {
203                        if output_item.is_zombie {
204                            ret.push(a.clone())
205                        }
206                        if output_item.meta.is_dir() {
207                            self.collect_children(
208                                &branch,
209                                &a,
210                                output_item.key,
211                                inode,
212                                &mut base_path,
213                                &prefixes,
214                                &mut next_files,
215                            )?;
216                        } else {
217                            let mut graph = self.retrieve(&branch, output_item.key);
218                            let mut buf = std::io::sink();
219                            if self.output_file(&branch, &mut buf, &mut graph, &mut forward)? {
220                                ret.push(a.clone())
221                            }
222                        }
223                    }
224                }
225            }
226            std::mem::swap(&mut files, &mut next_files);
227        }
228        Ok(ret)
229    }
230
231    fn make_conflicting_name(&self, name: &mut PathBuf, name_key: Key<PatchId>) {
232        let basename = {
233            let basename = name.file_name().unwrap().to_string_lossy();
234            format!("{}.{}", basename, &name_key.patch.to_base58())
235        };
236        name.set_file_name(&basename);
237    }
238
239    fn output_alive_files(
240        &mut self,
241        branch: &mut Branch,
242        prefixes: &Prefixes,
243        working_copy: &Path,
244    ) -> Result<()> {
245        debug!("working copy {:?}", working_copy);
246        let mut files = HashMap::new();
247        let mut next_files = HashMap::new();
248        let mut base_path = PathBuf::new();
249        self.collect_children(
250            branch,
251            "".as_ref(),
252            ROOT_KEY,
253            ROOT_INODE,
254            &mut base_path,
255            prefixes,
256            &mut files,
257        )?;
258
259        let mut done = HashSet::new();
260
261        while !files.is_empty() {
262            debug!("files {:?}", files);
263            next_files.clear();
264            for (a, b) in files.drain() {
265                let b_len = b.len();
266                for (name_key, output_item) in b {
267                    // (parent_inode, meta, inode_key, inode, is_zombie)
268                    /*let has_several_names = {
269                        let e = Edge::zero(EdgeFlags::PARENT_EDGE | EdgeFlags::FOLDER_EDGE);
270                        let mut it = self.iter_nodes(branch, Some((inode_key, Some(&e))))
271                            .take_while(|&(k, v)| {
272                                k == inode_key && v.flag|EdgeFlags::PSEUDO_EDGE == e.flag|EdgeFlags::PSEUDO_EDGE
273                            });
274                        it.next();
275                        it.next().is_some()
276                    };*/
277                    if !done.insert(output_item.key) {
278                        debug!("already done {:?}", output_item.key);
279                        continue;
280                    }
281
282                    let mut name = if b_len > 1
283                    /*|| has_several_names*/
284                    {
285                        // debug!("b_len = {:?}, has_several_names {:?}", b_len, has_several_names);
286                        let mut name = a.clone();
287                        self.make_conflicting_name(&mut name, name_key);
288                        Cow::Owned(name)
289                    } else {
290                        Cow::Borrowed(&a)
291                    };
292                    let file_name = name.file_name().unwrap().to_string_lossy();
293                    base_path.push(Path::new(file_name.as_ref()));
294                    let file_id = OwnedFileId {
295                        parent_inode: output_item.parent,
296                        basename: SmallString::from_str(&file_name),
297                    };
298                    let working_copy_name = working_copy.join(name.as_ref());
299
300                    let status = if output_item.is_zombie {
301                        FileStatus::Zombie
302                    } else {
303                        FileStatus::Ok
304                    };
305
306                    let inode = if let Some(inode) = output_item.inode {
307                        // If the file already exists, find its
308                        // current name and rename it if that name
309                        // is different.
310                        if let Some(ref current_name) = self.filename_of_inode(inode, "".as_ref()) {
311                            if current_name != name.as_ref() {
312                                let current_name = working_copy.join(current_name);
313                                debug!("renaming {:?} to {:?}", current_name, working_copy_name);
314                                let parent = self.get_revtree(inode).unwrap().to_owned();
315                                self.del_revtree(inode, None)?;
316                                self.del_tree(&parent.as_file_id(), None)?;
317
318                                debug!("file_id: {:?}", file_id);
319                                if let Some(p) = working_copy_name.parent() {
320                                    std::fs::create_dir_all(p)?
321                                }
322                                if let Err(e) = std::fs::rename(&current_name, &working_copy_name) {
323                                    error!(
324                                        "while renaming {:?} to {:?}: {:?}",
325                                        current_name, working_copy_name, e
326                                    )
327                                }
328                            }
329                        }
330                        self.put_tree(&file_id.as_file_id(), inode)?;
331                        self.put_revtree(inode, &file_id.as_file_id())?;
332                        // If the file had been marked for deletion, remove that mark.
333                        if let Some(header) = self.get_inodes(inode) {
334                            debug!("header {:?}", header);
335                            let mut header = header.to_owned();
336                            header.status = status;
337                            self.replace_inodes(inode, header)?;
338                        } else {
339                            let header = FileHeader {
340                                key: output_item.key,
341                                metadata: output_item.meta,
342                                status,
343                            };
344                            debug!("no header {:?}", header);
345                            self.replace_inodes(inode, header)?;
346                            self.replace_revinodes(output_item.key, inode)?;
347                        }
348                        inode
349                    } else {
350                        // Else, create new inode.
351                        let inode = self.create_new_inode();
352                        let file_header = FileHeader {
353                            key: output_item.key,
354                            metadata: output_item.meta,
355                            status,
356                        };
357                        self.replace_inodes(inode, file_header)?;
358                        self.replace_revinodes(output_item.key, inode)?;
359                        debug!("file_id: {:?}", file_id);
360                        self.put_tree(&file_id.as_file_id(), inode)?;
361                        self.put_revtree(inode, &file_id.as_file_id())?;
362                        inode
363                    };
364                    if output_item.meta.is_dir() {
365                        // This is a directory, register it in inodes/trees.
366                        std::fs::create_dir_all(&working_copy_name)?;
367                        if let Related::Exact = output_item.related {
368                            self.collect_children(
369                                branch,
370                                &name,
371                                output_item.key,
372                                inode,
373                                &mut base_path,
374                                &Prefixes(Vec::new()),
375                                &mut next_files,
376                            )?
377                        } else {
378                            self.collect_children(
379                                branch,
380                                &name,
381                                output_item.key,
382                                inode,
383                                &mut base_path,
384                                prefixes,
385                                &mut next_files,
386                            )?
387                        }
388                    } else {
389                        // Output file.
390                        debug!(
391                            "creating file {:?}, key {:?} {:?}",
392                            &name, output_item.key, working_copy_name
393                        );
394                        let mut f = std::fs::File::create(&working_copy_name).unwrap();
395                        debug!("done");
396                        let mut l = self.retrieve(branch, output_item.key);
397                        let mut forward = Vec::new();
398                        self.output_file(branch, &mut f, &mut l, &mut forward)?;
399                        self.remove_redundant_edges(branch, &forward)?
400                    }
401                    base_path.pop();
402                    set_permissions(&working_copy_name, output_item.meta.permissions())?
403                }
404            }
405            std::mem::swap(&mut files, &mut next_files);
406        }
407        Ok(())
408    }
409
410    fn output_repository_assuming_no_pending_patch(
411        &mut self,
412        prefixes: &Prefixes,
413        branch: &mut Branch,
414        working_copy: &Path,
415        pending_patch_id: PatchId,
416    ) -> Result<()> {
417        debug!(
418            "inodes: {:?}",
419            self.iter_inodes(None)
420                .map(|(u, v)| (u.to_owned(), v.to_owned()))
421                .collect::<Vec<_>>()
422        );
423        // Now, garbage collect dead inodes.
424        let dead: Vec<_> = self.iter_tree(None)
425            .filter_map(|(k, v)| {
426                debug!("{:?} {:?}", k, v);
427                if let Some(key) = self.get_inodes(v) {
428                    if key.key.patch == pending_patch_id || self.is_alive_or_zombie(branch, key.key)
429                    {
430                        // Don't delete.
431                        None
432                    } else {
433                        Some((k.to_owned(), v, self.filename_of_inode(v, working_copy)))
434                    }
435                } else {
436                    debug!("not in inodes");
437                    Some((k.to_owned(), v, None))
438                }
439            })
440            .collect();
441        debug!("dead: {:?}", dead);
442
443        // Now, "kill the deads"
444        for (ref parent, inode, ref name) in dead {
445            self.remove_inode_rec(inode)?;
446            debug!("removed");
447            if let Some(ref name) = *name {
448                debug!("deleting {:?}", name);
449                if let Ok(meta) = fs::metadata(name) {
450                    if let Err(e) = if meta.is_dir() {
451                        fs::remove_dir_all(name)
452                    } else {
453                        fs::remove_file(name)
454                    } {
455                        error!("while deleting {:?}: {:?}", name, e);
456                    }
457                }
458            } else {
459                self.del_tree(&parent.as_file_id(), Some(inode))?;
460                self.del_revtree(inode, Some(&parent.as_file_id()))?;
461            }
462        }
463        debug!("done deleting dead files");
464        // Then output alive files. This has to be done *after*
465        // removing files, because we a file removed might have the
466        // same name as a file added without there being a conflict
467        // (depending on the relation between the two patches).
468        self.output_alive_files(branch, prefixes, working_copy)?;
469        debug!("done raw_output_repository");
470        Ok(())
471    }
472
473    fn remove_inode_rec(&mut self, inode: Inode) -> Result<()> {
474        // Remove the inode from inodes/revinodes.
475        let mut to_kill = vec![inode];
476        while let Some(inode) = to_kill.pop() {
477            debug!("kill dead {:?}", inode.to_hex());
478            let header = self.get_inodes(inode).map(|x| x.to_owned());
479            if let Some(header) = header {
480                self.del_inodes(inode, None)?;
481                self.del_revinodes(header.key, None)?;
482                let mut kills = Vec::new();
483                // Remove the inode from tree/revtree.
484                for (k, v) in self.iter_revtree(Some((inode, None)))
485                    .take_while(|&(k, _)| k == inode)
486                {
487                    kills.push((k.clone(), v.to_owned()))
488                }
489                for &(k, ref v) in kills.iter() {
490                    self.del_tree(&v.as_file_id(), Some(k))?;
491                    self.del_revtree(k, Some(&v.as_file_id()))?;
492                }
493                // If the dead is a directory, remove its descendants.
494                let inode_fileid = OwnedFileId {
495                    parent_inode: inode.clone(),
496                    basename: SmallString::from_str(""),
497                };
498                to_kill.extend(
499                    self.iter_tree(Some((&inode_fileid.as_file_id(), None)))
500                        .take_while(|&(ref k, _)| k.parent_inode == inode)
501                        .map(|(_, v)| v.to_owned()),
502                )
503            }
504        }
505        Ok(())
506    }
507
508    pub fn output_repository(
509        &mut self,
510        branch: &mut Branch,
511        working_copy: &Path,
512        prefixes: &Prefixes,
513        pending: &Patch,
514        local_pending: &HashSet<InodeUpdate>,
515    ) -> Result<()> {
516        debug!("begin output repository");
517
518        debug!("applying pending patch");
519        let tempdir = tempdir::TempDir::new("pijul")?;
520        let hash = pending.save(tempdir.path(), None)?;
521        let internal =
522            self.apply_local_patch(branch, working_copy, &hash, pending, local_pending, true)?;
523
524        debug!("applied");
525
526        // let prefixes = prefixes.to_prefixes(&self, &branch);
527        debug!("prefixes {:?}", prefixes);
528        self.output_repository_assuming_no_pending_patch(
529            &prefixes,
530            branch,
531            working_copy,
532            internal,
533        )?;
534
535        debug!("unrecording pending patch");
536        self.unrecord(branch, internal, pending)?;
537        Ok(())
538    }
539
540    pub fn output_repository_no_pending(
541        &mut self,
542        branch: &mut Branch,
543        working_copy: &Path,
544        prefixes: &Prefixes,
545    ) -> Result<()> {
546        debug!("begin output repository {:?}", prefixes);
547
548        // let prefixes = prefixes.iter().flat_map(|pref| self.prefix_keys(&branch, pref)).collect::<Vec<_>>();
549        debug!("prefixes {:?}", prefixes);
550        self.output_repository_assuming_no_pending_patch(
551            &prefixes,
552            branch,
553            working_copy,
554            ROOT_PATCH_ID,
555        )?;
556        Ok(())
557    }
558
559    pub(crate) fn output_partials(&mut self, branch_name: &str, prefixes: &Prefixes) -> Result<()> {
560        for p in prefixes.0.iter() {
561            self.put_partials(branch_name, p[0])?;
562        }
563        Ok(())
564    }
565}
566
567#[derive(Debug)]
568pub struct Prefixes(Vec<Vec<Key<PatchId>>>);
569
570impl Prefixes {
571    pub fn empty() -> Self {
572        Prefixes(Vec::new())
573    }
574}
575
576pub trait ToPrefixes {
577    fn to_prefixes<T>(&self, txn: &MutTxn<T>, branch: &Branch) -> Prefixes;
578}
579
580impl<'a> ToPrefixes for &'a [&'a str] {
581    fn to_prefixes<T>(&self, txn: &MutTxn<T>, branch: &Branch) -> Prefixes {
582        Prefixes(
583            self.iter()
584                .flat_map(|pref| txn.prefix_keys(&branch, pref))
585                .collect(),
586        )
587    }
588}
589
590impl<'a> ToPrefixes for &'a [Inode] {
591    fn to_prefixes<T>(&self, txn: &MutTxn<T>, _: &Branch) -> Prefixes {
592        Prefixes(
593            self.iter()
594                .map(|pref| {
595                    let mut result = Vec::new();
596                    let mut current = *pref;
597                    loop {
598                        if current == ROOT_INODE {
599                            result.push(ROOT_KEY);
600                            break;
601                        }
602                        result.push(txn.get_inodes(current).unwrap().key);
603                        match txn.get_revtree(current) {
604                            Some(v) => current = v.parent_inode.clone(),
605                            None => break,
606                        }
607                    }
608                    result
609                })
610                .collect(),
611        )
612    }
613}