aboutsummaryrefslogtreecommitdiff
path: root/apps/servers/octopus/supergit/src/files.rs
blob: 2a1b69a34562f71492f9f4163932bf01aa6d3227 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
use crate::{Branch, BranchIter, Commit, HashId};
use git2::{ObjectType, TreeWalkMode, TreeWalkResult};
use atomptr::AtomPtr;
use std::collections::BTreeMap;
use std::{path::PathBuf, sync::Arc};

/// A tree of files
pub struct FileTree {
    repo: Arc<git2::Repository>,
    tree: AtomPtr<BTreeMap<String, TreeEntry>>,
}

impl FileTree {
    /// Utility function to create a tree, and then parse it too
    pub(crate) fn new(repo: &Arc<git2::Repository>, commit: HashId) -> Arc<Self> {
        Arc::new(Self {
            repo: Arc::clone(repo),
            tree: AtomPtr::new(BTreeMap::new()),
        })
        .parse(commit)
    }

    /// Parse a tree from a specific commit
    pub(crate) fn parse(self: Arc<Self>, commit: HashId) -> Arc<Self> {
        let mut new_tree = BTreeMap::new();
        
        let tree = (&self.repo)
            .find_commit(commit.to_oid())
            .unwrap()
            .tree()
            .unwrap();

        tree.walk(TreeWalkMode::PreOrder, |what, entry| {
            let path_segs: Vec<_> = what.split("/").filter(|s| s != &"").collect();
            let path = if path_segs.len() == 0 {
                None
            } else {
                Some(path_segs)
            };

            println!("{:?} {}", path, entry.name().unwrap());
            TreeWalkResult::Ok
        })
        .unwrap();
        drop(tree);

        // Atomicly swap new tree into place
        self.tree.swap(new_tree);
        
        self
    }
}

/// An entry in a file tree
///
/// It's variants can either be a file (leaf), or a subtree, with it's
/// own path handles, and children.
pub enum TreeEntry {
    /// A single file
    File(File),
    /// A sub-tree
    Dir(Directory),
}

impl TreeEntry {
    /// Create a tree entry from a path and `git2::TreeEntry`
    fn generate(root: PathBuf, path_segments: Option<Vec<String>>, entry: git2::TreeEntry) -> Self {
        let path = path_segments.map_or("".into(), |p| path_segs_join(p));

        match entry.kind() {
            Some(ObjectType::Blob) => Self::File(File::new(root, path)),
            Some(ObjectType::Tree) => Self::Dir(Directory::new(root, path)),
            _ => unimplemented!(),
        }
    }
}

/// A file to have ever existed in a git repo
pub struct File {
    root: PathBuf,
    path: String,
}

impl File {
    pub(crate) fn new(root: PathBuf, path: String) -> Self {
        Self { root, path }
    }

    /// Get the history of a file from a branch iterator
    pub fn get_history(&self, branch: BranchIter) -> Vec<Commit> {
        todo!()
    }
}

/// A subdirectory in a file tree
///
/// A directory has a set of children, which can either be Files, or
/// other directories.  Many of the functions to retrieve metadata
/// (such as the last commit, count, etc) will be deferred to the
/// children of this directory.
pub struct Directory {
    root: PathBuf,
    path: String,
}

impl Directory {
    pub(crate) fn new(root: PathBuf, path: String) -> Self {
        Self { root, path }
    }
}

/// Take a vector of path segments, and turn it into a valid offset path
///
/// There are tests to make sure this function works properly.
/// Following are some example transformations.
///
/// * vec![] -> ""
/// * vec!["foo"] -> "foo"
/// * vec!["foo", "bar", "baz"] -> "foo/bar/baz"
fn path_segs_join(segments: Vec<String>) -> String {
    segments
        .into_iter()
        .fold(PathBuf::new(), |buf, seg| buf.join(seg))
        .as_path()
        .to_str()
        .unwrap()
        .to_owned()
}

#[test]
fn empty_path() {
    assert_eq!(path_segs_join(vec![]), String::from(""));
}

#[test]
fn one_path() {
    assert_eq!(path_segs_join(vec!["foo".into()]), String::from("foo"));
}

#[test]
fn nested_path() {
    assert_eq!(
        path_segs_join(vec!["foo".into(), "bar".into(), "baz".into()]),
        String::from("foo/bar/baz")
    );
}