summaryrefslogtreecommitdiff
path: root/src/git.rs
blob: 76f9a4e794b345cdcf825f537d65bfe06adaa15d (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
use crate::commit::models::Commit;
use chrono::{DateTime, Local};
use git2::{BranchType, Error, Repository, Time};

use std::fs::remove_dir_all;

/// Return the temporary folder of the git repository
fn get_tmp_dir(repo_name: &String) -> String {
    format!("/tmp/{}", &repo_name)
}

/// Clone a repository `repo` in a temporary folder. It uses only GitHub at
/// this moment.
pub fn clone_repo(repo_name: &String) -> Result<Repository, Error> {
    let url: &str =
        &format!("https://github.com/{}", &repo_name).to_owned()[..];
    let tmp_dir: String = get_tmp_dir(&repo_name);

    let cloned = Repository::clone(url, tmp_dir)?;

    Ok(cloned)
}

/// Check if a `branch` exists inside the repository and then set the head of
/// `repo` to that branch
pub fn get_branch(repo: &Repository, branch: &str) -> Result<(), Error> {
    if let Err(e) = repo.find_branch(&branch, BranchType::Local) {
        return Err(e);
    }

    repo.set_head(&format!("refs/heads/{}", branch).to_owned()[..])
}

/// Get a `git2::Commit` and returns a date in RFC2822 format
pub fn get_date(commit: &git2::Commit) -> String {
    let t: Time = commit.time();
    let (offset, sign) = match t.offset_minutes() {
        n if n < 0 => (-n, '-'),
        n => (n, '+'),
    };
    let (hours, minutes) = (offset / 60, offset % 60);
    let ts =
        time::Timespec::new(t.seconds() + (t.offset_minutes() as i64) * 60, 0);
    let time = time::at(ts);

    let result = format!(
        "{} {}{:02}{:02}",
        time.strftime("%a, %e %b %Y %T").unwrap(),
        sign,
        hours,
        minutes
    );

    result
}

/// Get a `git2::Commit` and returns a valid `Commit` to upload to the database
fn get_commit(gcommit: &git2::Commit, repo_name: &String) -> Commit {
    let hash = gcommit.id().to_string();
    let tree = match gcommit.parent_id(0) {
        Ok(parent) => Some(parent.to_string()),
        Err(_) => None,
    };

    let mut text = "".to_string();
    for line in String::from_utf8_lossy(gcommit.message_bytes()).lines() {
        text += line;
        text += "\n";
    }

    // Remove the last "\n"
    let _ = text.pop();

    let date: DateTime<Local> =
        match DateTime::parse_from_rfc2822(&get_date(&gcommit)) {
            Ok(date) => date.into(),
            Err(e) => {
                // This is a real problem!
                // TODO: manage this error
                panic!(e)
            }
        };
    let author_email = gcommit.author().email().unwrap().to_string();
    let author_name = gcommit.author().name().unwrap().to_string();
    let committer_email = gcommit.committer().email().unwrap().to_string();
    let committer_name = gcommit.committer().name().unwrap().to_string();

    Commit {
        hash,
        tree,
        text,
        date,
        author_email,
        author_name,
        committer_email,
        committer_name,
        repository_url: repo_name.clone(),
    }
}

/// Get all commits from a Git repository. Returns an array of `Commit`.
/// First, clone the repo into a temporary folder.
/// Then, open the repository.
/// Then, get commits
/// Finally, remove the temporary folder
pub fn repo_commits(repo_name: &String) -> Result<Vec<Commit>, Error> {
    // Try to clone the repo. If it returns an error, it's useless to go ahead:
    // raises an error.
    let repo = match clone_repo(&repo_name) {
        Ok(r) => r,
        Err(e) => {
            return Err(e);
        }
    };

    if let Err(e) = get_branch(&repo, "main") {
        return Err(e);
    }

    let mut revwalk = repo.revwalk()?;
    let head = repo.head().unwrap().target().unwrap();

    if let Err(e) = revwalk.push(head) {
        return Err(e);
    }

    let mut commits: Vec<Commit> = vec![];
    for commit in revwalk {
        let hash = repo.find_commit(commit?)?;
        commits.push(get_commit(&hash, &repo_name));
    }

    if let Err(_) = remove_dir_all(get_tmp_dir(&repo_name)) {
        return Err(git2::Error::new(
            git2::ErrorCode::GenericError,
            git2::ErrorClass::Os,
            "Temporary clone not deleted",
        ));
    }

    Ok(commits)
}