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
use {
    git2::{self, Repository, Status},
    std::{
        collections::HashMap,
        path::{Path, PathBuf},
    },
};

const INTERESTING: Status = Status::from_bits_truncate(
    Status::WT_NEW.bits() | Status::CONFLICTED.bits() | Status::WT_MODIFIED.bits(),
);

// if I add nothing, I'll remove this useless struct
// and only use git2.Status
#[derive(Debug, Clone, Copy)]
pub struct LineGitStatus {
    pub status: Status,
}

impl LineGitStatus {
    pub fn from(repo: &Repository, relative_path: &Path) -> Option<LineGitStatus> {
        repo.status_file(&relative_path)
            .ok()
            .map(|status| LineGitStatus { status })
    }
    pub fn is_interesting(self) -> bool {
        self.status.intersects(INTERESTING)
    }
}

pub struct LineStatusComputer {
    interesting_statuses: HashMap<PathBuf, Status>,
}
impl LineStatusComputer {
    pub fn from(repo: Repository) -> Self {
        let repo_path = repo.path().parent().unwrap().to_path_buf();
        let mut interesting_statuses = HashMap::new();
        if let Ok(statuses) = &repo.statuses(None) {
            for entry in statuses.iter() {
                let status = entry.status();
                if status.intersects(INTERESTING) {
                    if let Some(path) = entry.path() {
                        let path = repo_path.join(path);
                        interesting_statuses.insert(path, status);
                    }
                }
            }
        } else {
            debug!("get statuses failed");
        }
        Self {
            interesting_statuses,
        }
    }
    pub fn line_status(&self, path: &Path) -> Option<LineGitStatus> {
        self.interesting_statuses
            .get(path)
            .map(|&status| LineGitStatus { status })
    }
    pub fn is_interesting(&self, path: &Path) -> bool {
        self.interesting_statuses.contains_key(path)
    }
}

///
#[derive(Debug, Clone)]
pub struct TreeGitStatus {
    pub current_branch_name: Option<String>,
    pub insertions: usize,
    pub deletions: usize,
}

impl TreeGitStatus {
    pub fn from(repo: &Repository) -> Option<Self> {
        let current_branch_name = repo
            .head()
            .ok()
            .and_then(|head| head.shorthand().map(String::from));
        let stats = match repo.diff_index_to_workdir(None, None) {
            Ok(diff) => {
                match diff.stats() {
                    Ok(stats) => stats,
                    Err(e) => {
                        debug!("get stats failed : {:?}", e);
                        return None;
                    }
                }
            }
            Err(e) => {
                debug!("get diff failed : {:?}", e);
                return None;
            }
        };
        Some(Self {
            current_branch_name,
            insertions: stats.insertions(),
            deletions: stats.deletions(),
        })
    }
}