gitu 0.41.0

A git client inspired by Magit
Documentation
use super::{Action, OpTrait, confirm};
use crate::{
    Res,
    app::{App, State},
    config::ConfirmDiscardOption,
    git::diff::{DiffType, PatchMode},
    item_data::{ItemData, Ref},
    term::Term,
};
use std::{path::PathBuf, process::Command, rc::Rc};

pub(crate) struct Discard;
impl OpTrait for Discard {
    fn get_action(&self, target: &ItemData) -> Option<Action> {
        let action = match target {
            ItemData::Reference {
                kind: Ref::Head(branch),
                ..
            } => discard_branch(branch.clone()),
            ItemData::Untracked(file) => clean_file(file.clone()),
            ItemData::Delta { diff, file_i } => {
                let patch = diff.format_file_patch(*file_i);
                match diff.diff_type {
                    DiffType::WorkdirToIndex => reverse_worktree(patch),
                    DiffType::IndexToTree => reverse_index_and_worktree(patch),
                    DiffType::TreeToTree => reverse_index_and_worktree(patch),
                }
            }
            ItemData::Hunk {
                diff,
                file_i,
                hunk_i,
            } => {
                let patch = diff.format_hunk_patch(*file_i, *hunk_i);
                match diff.diff_type {
                    DiffType::WorkdirToIndex => reverse_worktree(patch),
                    DiffType::IndexToTree => reverse_index_and_worktree(patch),
                    DiffType::TreeToTree => reverse_index_and_worktree(patch),
                }
            }
            ItemData::HunkLine {
                diff,
                file_i,
                hunk_i,
                line_i,
                ..
            } => {
                let patch = diff.format_line_patch(
                    *file_i,
                    *hunk_i,
                    *line_i..(line_i + 1),
                    PatchMode::Reverse,
                );

                match diff.diff_type {
                    DiffType::WorkdirToIndex => reverse_worktree(patch),
                    DiffType::IndexToTree => reverse_index_and_worktree(patch),
                    DiffType::TreeToTree => reverse_index_and_worktree(patch),
                }
            }
            _ => return None,
        };

        Some(action)
    }

    fn is_target_op(&self) -> bool {
        true
    }

    fn display(&self, _state: &State) -> String {
        "Discard".into()
    }
}

fn discard_branch(branch: String) -> Action {
    Rc::new(move |app, term| {
        confirm(app, term, "Really discard? (y or n)")?;
        super::branch::delete(app, term, &branch)
    })
}

fn clean_file(file: PathBuf) -> Action {
    Rc::new(move |app, term| {
        confirm_discard(app, term)?;

        let mut cmd = Command::new("git");
        cmd.args(["clean", "--force"]);
        cmd.arg(&file);

        app.close_menu();
        app.run_cmd(term, &[], cmd)
    })
}

fn reverse_worktree(patch: String) -> Action {
    let patch_bytes = patch.into_bytes();

    Rc::new(move |app, term| {
        confirm_discard(app, term)?;

        let mut cmd = Command::new("git");
        cmd.args(["apply", "--reverse", "--recount"]);

        app.close_menu();
        app.run_cmd(term, &patch_bytes, cmd)
    })
}

fn reverse_index_and_worktree(patch: String) -> Action {
    let patch_bytes = patch.into_bytes();

    Rc::new(move |app, term| {
        confirm_discard(app, term)?;

        let mut cmd = Command::new("git");
        cmd.args(["apply", "--reverse", "--index", "--recount"]);

        app.close_menu();
        app.run_cmd(term, &patch_bytes, cmd)
    })
}

fn confirm_discard(app: &mut App, term: &mut Term) -> Res<()> {
    if app.state.config.general.confirm_discard <= ConfirmDiscardOption::File {
        confirm(app, term, "Really discard? (y or n)")?;
    }
    Ok(())
}