idgit 0.0.1

A TUI for git inspired by magit
Documentation
use crate::{file::File, Error, Result};
use bstr::BString;

#[allow(unused)]
use tracing::{debug, error, info, instrument, span, warn};

#[derive(Debug, Clone)]
pub enum Meta {
    Added(File),
    Deleted(File),
    Modified { old: File, new: File },
    Renamed { old: File, new: File },
    Copied { old: File, new: File },
    Ignored(File),
    Untracked(File),
    Typechange { old: File, new: File },
    Unreadable(File),
    Conflicted { old: File, new: File },
}

impl Meta {
    /// # Panics
    /// If the delta is of status [`git2::Delta::Unmodified`].
    pub(crate) fn from_git2(from: &git2::DiffDelta) -> Result<Self> {
        use git2::Delta;
        Ok(match from.status() {
            Delta::Added => Self::Added(Self::get_new_file_only(from)?),
            Delta::Deleted => Self::Deleted(Self::get_old_file_only(from)?),
            Delta::Modified => {
                let (old, new) = Self::get_both_files(from)?;
                Self::Modified { old, new }
            }
            Delta::Renamed => {
                let (old, new) = Self::get_both_files(from)?;
                Self::Renamed { old, new }
            }
            Delta::Copied => {
                let (old, new) = Self::get_both_files(from)?;
                Self::Copied { old, new }
            }
            Delta::Ignored => Self::Ignored(Self::get_new_file_only(from)?),
            Delta::Untracked => Self::Untracked(Self::get_new_file_only(from)?),
            Delta::Typechange => {
                let (old, new) = Self::get_both_files(from)?;
                Self::Typechange { old, new }
            }
            Delta::Unreadable => Self::Unreadable(Self::get_new_file_only(from)?),
            Delta::Unmodified => unreachable!("We don't include unmodified files"),
            Delta::Conflicted => {
                let (old, new) = Self::get_both_files(from)?;
                Self::Conflicted { old, new }
            }
        })
    }

    fn get_new_file_only(from: &git2::DiffDelta) -> Result<File> {
        assert_eq!(from.nfiles(), 1);
        let file = from.new_file();
        let path = file.path().ok_or_else(|| Error::MissingPath(file.id()))?;
        Ok(File::new(path))
    }

    fn get_old_file_only(from: &git2::DiffDelta) -> Result<File> {
        assert_eq!(from.nfiles(), 1);
        let file = from.old_file();
        let path = file.path().ok_or_else(|| Error::MissingPath(file.id()))?;
        Ok(File::new(path))
    }

    fn get_both_files(from: &git2::DiffDelta) -> Result<(File, File)> {
        assert_eq!(from.nfiles(), 2);

        let old_path = from
            .old_file()
            .path()
            .ok_or_else(|| Error::MissingPath(from.old_file().id()))?;

        let new_path = from
            .new_file()
            .path()
            .ok_or_else(|| Error::MissingPath(from.new_file().id()))?;

        Ok((File::new(old_path), File::new(new_path)))
    }
}

#[derive(Debug, Clone)]
pub struct Details {
    meta: Meta,
    lines: Vec<Line>,
}

impl Details {
    pub(crate) fn new(meta: Meta, lines: Vec<Line>) -> Self {
        Self { meta, lines }
    }
}

#[derive(Debug, Clone)]
pub struct Line {
    /// Line number in old file or None for added line
    old_lineno: Option<u32>,
    /// Line number in new file or None for deleted line
    new_lineno: Option<u32>,
    /// Number of newline characters in content
    num_lines: u32,
    /// Offset in the original file to the content
    content_offset: i64,
    content: BString,
    origin: git2::DiffLineType,
}

impl Line {
    pub(crate) fn from_git2(from: &git2::DiffLine) -> Self {
        Self {
            old_lineno: from.old_lineno(),
            new_lineno: from.new_lineno(),
            num_lines: from.num_lines(),
            content_offset: from.content_offset(),
            content: from.content().into(),
            origin: from.origin_value(),
        }
    }
}