aboutsummaryrefslogtreecommitdiff
path: root/src/git/log.rs
blob: c8f4aa37ccf2200e6e2d8c7b84573101f5a87920 (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
//! libgit2 log parsing

use crate::git::{tree::FileNode, Repo};
use git2::Oid;
use std::collections::{BTreeMap, BTreeSet};

/// A file-commit referenced graph thing
///
/// git is _weird_!  It's essentially just a glorified key-value store
/// and it shows.  There's no utilities to figure out how thing are
/// related, and all the actual graph things in git are sugar on top
/// of this store.
///
/// In order to make sense of anything in a repo we need to quite
/// heavily parse the log.  This type here is the result of this
/// parsing: you can ask it smart questions like "when did this file
/// change" and it will tell you (sort of).
#[derive(Debug, Default)]
pub(crate) struct CommitGraph {
    /// The correct order of commits in the log
    order: Vec<String>,
    /// List of all files, and the commits in which they were touched
    file_refs: BTreeMap<String, Vec<String>>,
    /// Map of commit IDs to metadata
    commit_refs: BTreeMap<String, CommitNode>,
}

#[derive(Debug)]
pub(crate) struct CommitNode {
    id: String,
    author: String,
    message: String,
    touches: BTreeSet<String>,
    time: i64,
}

fn build_diff_log(repo: &Repo, log: Vec<(String, Vec<FileNode>)>) -> Vec<CommitNode> {
    todo!()
}

/// Walk through all commits from a given ref and build a commit graph
pub(crate) fn create_commit_log(rev: String, repo: &Repo) -> CommitGraph {
    let mut walker = repo.get_inner().revwalk().unwrap();
    walker.push(Oid::from_str(rev.as_str()).unwrap()).unwrap();
    let mut commits = walker
        .into_iter()
        .map(|oid| {
            let oid = oid.unwrap();
            repo.get_inner().find_commit(oid).unwrap()
        })
        .collect::<Vec<_>>();
    commits.reverse();

    let mut initial: Vec<(_, _)> = commits
        .into_iter()
        .map(|commit| {
            let id = format!("{}", commit.id());
            (id.clone(), repo.get_tree(id.as_str()))
        })
        .collect();

    // split off rest of the diffs and dissolve the len(1) vec
    let log = initial.split_off(1);
    let previous = initial.remove(0).1;

    let mut order = vec![];
    let (commit_refs, file_refs) = log.into_iter().fold(
        (BTreeMap::new(), BTreeMap::new()),
        |(mut cm, mut fm), (cid, current)| {
            let commit_id = format!("{}", cid);

            let d = repo
                .get_inner()
                .diff_tree_to_tree(Some(&previous), Some(&current), None)
                .unwrap();

            // Store the commit to preserve order
            order.push(commit_id.clone());

            // For each file, store this commit as one that touched it
            let touches = d.deltas().fold(BTreeSet::new(), |mut set, delta| {
                let file_id = format!("{}", delta.new_file().id());
                fm.entry(file_id.clone())
                    .or_insert(vec![])
                    .push(commit_id.clone());
                set.insert(file_id);
                set
            });

            // From the commit, build a metadata object
            let commit_u = repo
                .get_inner()
                .find_commit(Oid::from_str(cid.as_str()).unwrap())
                .unwrap();
            let author_u = commit_u.author();
            let commit = CommitNode {
                id: commit_id,
                message: commit_u.message().unwrap().to_owned(),
                author: format!("{} {}", author_u.name().unwrap(), author_u.email().unwrap()),
                touches,
                time: author_u.when().seconds(),
            };

            // Insert the metadata object
            cm.insert(cid.clone(), commit);

            // We pass both the modified maps into the next commit
            (cm, fm)
        },
    );

    CommitGraph {
        order,
        file_refs,
        commit_refs,
    }
}