use std::io::{Read, Write};
use std::path::PathBuf;
use std::fs::File;
use git2::{DiffFormat, DiffOptions, Oid, Repository, Tree};
use anyhow::{Context, Result};
use lazy_static::lazy_static;
use dotenv_codegen::dotenv;
use thiserror::Error;
use clap::Parser;
use crate::commit::ChatError;
pub trait FilePath {
fn is_empty(&self) -> Result<bool> {
self.read().map(|s| s.is_empty())
}
fn write(&self, msg: String) -> Result<()>;
fn read(&self) -> Result<String>;
}
impl FilePath for PathBuf {
fn write(&self, msg: String) -> Result<()> {
let mut file = File::create(self)?;
file.write_all(msg.as_bytes())?;
Ok(())
}
fn read(&self) -> Result<String> {
let mut file = File::open(self)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
Ok(contents)
}
}
pub trait Utf8String {
fn to_utf8(&self) -> String;
}
impl Utf8String for Vec<u8> {
fn to_utf8(&self) -> String {
String::from_utf8(self.to_vec()).unwrap_or_default()
}
}
impl Utf8String for [u8] {
fn to_utf8(&self) -> String {
String::from_utf8(self.to_vec()).unwrap_or_default()
}
}
pub trait PatchDiff {
fn to_patch(&self, max_token_count: usize) -> Result<String>;
}
impl PatchDiff for git2::Diff<'_> {
fn to_patch(&self, max_token_count: usize) -> Result<String> {
let mut acc = Vec::new();
let mut length = 0;
#[rustfmt::skip]
self.print(DiffFormat::Patch, |_, _, line| {
let content = line.content();
acc.extend_from_slice(content);
let str = content.to_utf8();
length += str.len();
length <= max_token_count
}).ok();
Ok(acc.to_utf8())
}
}
pub trait PatchRepository {
fn to_patch(&self, tree: Option<Tree<'_>>, max_token_count: usize) -> Result<String>;
fn to_diff(&self, tree: Option<Tree<'_>>) -> Result<git2::Diff<'_>>;
}
impl PatchRepository for Repository {
fn to_patch(&self, tree: Option<Tree<'_>>, max_token_count: usize) -> Result<String> {
self.to_diff(tree)?.to_patch(max_token_count)
}
fn to_diff(&self, tree: Option<Tree<'_>>) -> Result<git2::Diff<'_>> {
let mut opts = DiffOptions::new();
opts
.enable_fast_untracked_dirs(true)
.ignore_whitespace_change(true)
.recurse_untracked_dirs(false)
.recurse_ignored_dirs(false)
.ignore_whitespace_eol(true)
.ignore_blank_lines(true)
.include_untracked(false)
.ignore_whitespace(true)
.indent_heuristic(false)
.ignore_submodules(true)
.include_ignored(false)
.interhunk_lines(0)
.context_lines(0)
.patience(true)
.minimal(true);
self
.diff_tree_to_index(tree.as_ref(), None, Some(&mut opts))
.context("Failed to get diff")
}
}
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
pub struct Args {
pub commit_msg_file: PathBuf,
#[clap(required = false)]
pub commit_type: Option<String>,
#[clap(required = false)]
pub sha1: Option<Oid>
}
#[derive(Error, Debug)]
pub enum HookError {
#[error("Failed to open repository")]
OpenRepository,
#[error("Failed to get patch")]
GetPatch,
#[error("Empty diff output")]
EmptyDiffOutput,
#[error("Failed to write commit message")]
WriteCommitMessage,
#[error(transparent)]
Anyhow(#[from] anyhow::Error),
#[error(transparent)]
Chat(#[from] ChatError)
}
lazy_static! {
pub static ref MAX_DIFF_TOKENS: usize = dotenv!("MAX_DIFF_TOKENS").parse::<usize>().unwrap();
}