|
| 1 | +use std::collections::HashMap; |
| 2 | +use std::path::{Path, PathBuf}; |
| 3 | + |
| 4 | +use git2::{DiffDelta, DiffHunk, DiffOptions, Repository}; |
| 5 | + |
| 6 | +use crate::{LineDiff, LineDiffs}; |
| 7 | + |
| 8 | +pub struct Git { |
| 9 | + repo: Repository, |
| 10 | + root: PathBuf, |
| 11 | +} |
| 12 | + |
| 13 | +impl std::fmt::Debug for Git { |
| 14 | + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 15 | + f.debug_struct("Git").field("root", &self.root).finish() |
| 16 | + } |
| 17 | +} |
| 18 | + |
| 19 | +impl Git { |
| 20 | + pub fn discover_from_path(file: &Path) -> Option<Self> { |
| 21 | + let repo = Repository::discover(file).ok()?; |
| 22 | + let root = repo.workdir()?.to_path_buf(); |
| 23 | + Some(Self { repo, root }) |
| 24 | + } |
| 25 | + |
| 26 | + // Modified from https://github.com/sharkdp/bat/blob/master/src/diff.rs |
| 27 | + pub fn line_diffs(&mut self, file: &Path) -> Option<LineDiffs> { |
| 28 | + let mut diff_options = DiffOptions::new(); |
| 29 | + let relative = file.strip_prefix(&self.root).ok()?; |
| 30 | + diff_options.pathspec(relative); |
| 31 | + diff_options.context_lines(0); |
| 32 | + |
| 33 | + let diff = self |
| 34 | + .repo |
| 35 | + .diff_index_to_workdir(None, Some(&mut diff_options)) |
| 36 | + .ok()?; |
| 37 | + |
| 38 | + let mut line_diffs: LineDiffs = HashMap::new(); |
| 39 | + |
| 40 | + let mut mark_section = |start: u32, end: u32, change: LineDiff| { |
| 41 | + for line in start..=end { |
| 42 | + line_diffs.insert(line as usize, change); |
| 43 | + } |
| 44 | + }; |
| 45 | + |
| 46 | + let hunk_cb = &mut |delta: DiffDelta, hunk: DiffHunk| { |
| 47 | + let relative_path = delta.new_file().path().unwrap_or_else(|| Path::new("")); |
| 48 | + if !file.ends_with(relative_path) { |
| 49 | + return false; |
| 50 | + } |
| 51 | + |
| 52 | + let old_lines = hunk.old_lines(); |
| 53 | + let new_start = hunk.new_start(); |
| 54 | + let new_lines = hunk.new_lines(); |
| 55 | + let new_end = new_start + new_lines.saturating_sub(1); |
| 56 | + |
| 57 | + if old_lines == 0 && new_lines > 0 { |
| 58 | + mark_section(new_start, new_end, LineDiff::Added); |
| 59 | + } else if new_lines == 0 && old_lines > 0 { |
| 60 | + if new_start == 0 { |
| 61 | + mark_section(1, 1, LineDiff::DeletedAbove); |
| 62 | + } else { |
| 63 | + mark_section(new_start, new_start, LineDiff::Deleted); |
| 64 | + } |
| 65 | + } else { |
| 66 | + // TODO: modified and added lines nearby render as big modified chunk |
| 67 | + mark_section(new_start, new_end, LineDiff::Modified); |
| 68 | + } |
| 69 | + |
| 70 | + true |
| 71 | + }; |
| 72 | + |
| 73 | + diff.foreach(&mut |_, _| true, None, Some(hunk_cb), None) |
| 74 | + .ok()?; |
| 75 | + |
| 76 | + Some(line_diffs) |
| 77 | + } |
| 78 | +} |
0 commit comments