diff options
Diffstat (limited to 'extra/git2/examples/diff.rs')
-rw-r--r-- | extra/git2/examples/diff.rs | 368 |
1 files changed, 368 insertions, 0 deletions
diff --git a/extra/git2/examples/diff.rs b/extra/git2/examples/diff.rs new file mode 100644 index 000000000..62f165db3 --- /dev/null +++ b/extra/git2/examples/diff.rs @@ -0,0 +1,368 @@ +/* + * libgit2 "diff" example - shows how to use the diff API + * + * Written by the libgit2 contributors + * + * To the extent possible under law, the author(s) have dedicated all copyright + * and related and neighboring rights to this software to the public domain + * worldwide. This software is distributed without any warranty. + * + * You should have received a copy of the CC0 Public Domain Dedication along + * with this software. If not, see + * <http://creativecommons.org/publicdomain/zero/1.0/>. + */ + +#![deny(warnings)] + +use git2::{Blob, Diff, DiffOptions, Error, Object, ObjectType, Oid, Repository}; +use git2::{DiffDelta, DiffFindOptions, DiffFormat, DiffHunk, DiffLine}; +use std::str; +use structopt::StructOpt; + +#[derive(StructOpt)] +#[allow(non_snake_case)] +struct Args { + #[structopt(name = "from_oid")] + arg_from_oid: Option<String>, + #[structopt(name = "to_oid")] + arg_to_oid: Option<String>, + #[structopt(name = "blobs", long)] + /// treat from_oid and to_oid as blob ids + flag_blobs: bool, + #[structopt(name = "patch", short, long)] + /// show output in patch format + flag_patch: bool, + #[structopt(name = "cached", long)] + /// use staged changes as diff + flag_cached: bool, + #[structopt(name = "nocached", long)] + /// do not use staged changes + flag_nocached: bool, + #[structopt(name = "name-only", long)] + /// show only names of changed files + flag_name_only: bool, + #[structopt(name = "name-status", long)] + /// show only names and status changes + flag_name_status: bool, + #[structopt(name = "raw", long)] + /// generate the raw format + flag_raw: bool, + #[structopt(name = "format", long)] + /// specify format for stat summary + flag_format: Option<String>, + #[structopt(name = "color", long)] + /// use color output + flag_color: bool, + #[structopt(name = "no-color", long)] + /// never use color output + flag_no_color: bool, + #[structopt(short = "R")] + /// swap two inputs + flag_R: bool, + #[structopt(name = "text", short = "a", long)] + /// treat all files as text + flag_text: bool, + #[structopt(name = "ignore-space-at-eol", long)] + /// ignore changes in whitespace at EOL + flag_ignore_space_at_eol: bool, + #[structopt(name = "ignore-space-change", short = "b", long)] + /// ignore changes in amount of whitespace + flag_ignore_space_change: bool, + #[structopt(name = "ignore-all-space", short = "w", long)] + /// ignore whitespace when comparing lines + flag_ignore_all_space: bool, + #[structopt(name = "ignored", long)] + /// show untracked files + flag_ignored: bool, + #[structopt(name = "untracked", long)] + /// generate diff using the patience algorithm + flag_untracked: bool, + #[structopt(name = "patience", long)] + /// show ignored files as well + flag_patience: bool, + #[structopt(name = "minimal", long)] + /// spend extra time to find smallest diff + flag_minimal: bool, + #[structopt(name = "stat", long)] + /// generate a diffstat + flag_stat: bool, + #[structopt(name = "numstat", long)] + /// similar to --stat, but more machine friendly + flag_numstat: bool, + #[structopt(name = "shortstat", long)] + /// only output last line of --stat + flag_shortstat: bool, + #[structopt(name = "summary", long)] + /// output condensed summary of header info + flag_summary: bool, + #[structopt(name = "find-renames", short = "M", long)] + /// set threshold for finding renames (default 50) + flag_find_renames: Option<u16>, + #[structopt(name = "find-copies", short = "C", long)] + /// set threshold for finding copies (default 50) + flag_find_copies: Option<u16>, + #[structopt(name = "find-copies-harder", long)] + /// inspect unmodified files for sources of copies + flag_find_copies_harder: bool, + #[structopt(name = "break_rewrites", short = "B", long)] + /// break complete rewrite changes into pairs + flag_break_rewrites: bool, + #[structopt(name = "unified", short = "U", long)] + /// lints of context to show + flag_unified: Option<u32>, + #[structopt(name = "inter-hunk-context", long)] + /// maximum lines of change between hunks + flag_inter_hunk_context: Option<u32>, + #[structopt(name = "abbrev", long)] + /// length to abbreviate commits to + flag_abbrev: Option<u16>, + #[structopt(name = "src-prefix", long)] + /// show given source prefix instead of 'a/' + flag_src_prefix: Option<String>, + #[structopt(name = "dst-prefix", long)] + /// show given destination prefix instead of 'b/' + flag_dst_prefix: Option<String>, + #[structopt(name = "path", long = "git-dir")] + /// path to git repository to use + flag_git_dir: Option<String>, +} + +const RESET: &str = "\u{1b}[m"; +const BOLD: &str = "\u{1b}[1m"; +const RED: &str = "\u{1b}[31m"; +const GREEN: &str = "\u{1b}[32m"; +const CYAN: &str = "\u{1b}[36m"; + +#[derive(PartialEq, Eq, Copy, Clone)] +enum Cache { + Normal, + Only, + None, +} + +fn line_color(line: &DiffLine) -> Option<&'static str> { + match line.origin() { + '+' => Some(GREEN), + '-' => Some(RED), + '>' => Some(GREEN), + '<' => Some(RED), + 'F' => Some(BOLD), + 'H' => Some(CYAN), + _ => None, + } +} + +fn print_diff_line( + _delta: DiffDelta, + _hunk: Option<DiffHunk>, + line: DiffLine, + args: &Args, +) -> bool { + if args.color() { + print!("{}", RESET); + if let Some(color) = line_color(&line) { + print!("{}", color); + } + } + match line.origin() { + '+' | '-' | ' ' => print!("{}", line.origin()), + _ => {} + } + print!("{}", str::from_utf8(line.content()).unwrap()); + true +} + +fn run(args: &Args) -> Result<(), Error> { + let path = args.flag_git_dir.as_ref().map(|s| &s[..]).unwrap_or("."); + let repo = Repository::open(path)?; + + // Prepare our diff options based on the arguments given + let mut opts = DiffOptions::new(); + opts.reverse(args.flag_R) + .force_text(args.flag_text) + .ignore_whitespace_eol(args.flag_ignore_space_at_eol) + .ignore_whitespace_change(args.flag_ignore_space_change) + .ignore_whitespace(args.flag_ignore_all_space) + .include_ignored(args.flag_ignored) + .include_untracked(args.flag_untracked) + .patience(args.flag_patience) + .minimal(args.flag_minimal); + if let Some(amt) = args.flag_unified { + opts.context_lines(amt); + } + if let Some(amt) = args.flag_inter_hunk_context { + opts.interhunk_lines(amt); + } + if let Some(amt) = args.flag_abbrev { + opts.id_abbrev(amt); + } + if let Some(ref s) = args.flag_src_prefix { + opts.old_prefix(&s); + } + if let Some(ref s) = args.flag_dst_prefix { + opts.new_prefix(&s); + } + if let Some("diff-index") = args.flag_format.as_ref().map(|s| &s[..]) { + opts.id_abbrev(40); + } + + if args.flag_blobs { + let b1 = resolve_blob(&repo, args.arg_from_oid.as_ref())?; + let b2 = resolve_blob(&repo, args.arg_to_oid.as_ref())?; + repo.diff_blobs( + b1.as_ref(), + None, + b2.as_ref(), + None, + Some(&mut opts), + None, + None, + None, + Some(&mut |d, h, l| print_diff_line(d, h, l, args)), + )?; + if args.color() { + print!("{}", RESET); + } + return Ok(()); + } + + // Prepare the diff to inspect + let t1 = tree_to_treeish(&repo, args.arg_from_oid.as_ref())?; + let t2 = tree_to_treeish(&repo, args.arg_to_oid.as_ref())?; + let head = tree_to_treeish(&repo, Some(&"HEAD".to_string()))?.unwrap(); + let mut diff = match (t1, t2, args.cache()) { + (Some(t1), Some(t2), _) => { + repo.diff_tree_to_tree(t1.as_tree(), t2.as_tree(), Some(&mut opts))? + } + (t1, None, Cache::None) => { + let t1 = t1.unwrap_or(head); + repo.diff_tree_to_workdir(t1.as_tree(), Some(&mut opts))? + } + (t1, None, Cache::Only) => { + let t1 = t1.unwrap_or(head); + repo.diff_tree_to_index(t1.as_tree(), None, Some(&mut opts))? + } + (Some(t1), None, _) => { + repo.diff_tree_to_workdir_with_index(t1.as_tree(), Some(&mut opts))? + } + (None, None, _) => repo.diff_index_to_workdir(None, Some(&mut opts))?, + (None, Some(_), _) => unreachable!(), + }; + + // Apply rename and copy detection if requested + if args.flag_break_rewrites + || args.flag_find_copies_harder + || args.flag_find_renames.is_some() + || args.flag_find_copies.is_some() + { + let mut opts = DiffFindOptions::new(); + if let Some(t) = args.flag_find_renames { + opts.rename_threshold(t); + opts.renames(true); + } + if let Some(t) = args.flag_find_copies { + opts.copy_threshold(t); + opts.copies(true); + } + opts.copies_from_unmodified(args.flag_find_copies_harder) + .rewrites(args.flag_break_rewrites); + diff.find_similar(Some(&mut opts))?; + } + + // Generate simple output + let stats = args.flag_stat | args.flag_numstat | args.flag_shortstat | args.flag_summary; + if stats { + print_stats(&diff, args)?; + } + if args.flag_patch || !stats { + diff.print(args.diff_format(), |d, h, l| print_diff_line(d, h, l, args))?; + if args.color() { + print!("{}", RESET); + } + } + + Ok(()) +} + +fn print_stats(diff: &Diff, args: &Args) -> Result<(), Error> { + let stats = diff.stats()?; + let mut format = git2::DiffStatsFormat::NONE; + if args.flag_stat { + format |= git2::DiffStatsFormat::FULL; + } + if args.flag_shortstat { + format |= git2::DiffStatsFormat::SHORT; + } + if args.flag_numstat { + format |= git2::DiffStatsFormat::NUMBER; + } + if args.flag_summary { + format |= git2::DiffStatsFormat::INCLUDE_SUMMARY; + } + let buf = stats.to_buf(format, 80)?; + print!("{}", str::from_utf8(&*buf).unwrap()); + Ok(()) +} + +fn tree_to_treeish<'a>( + repo: &'a Repository, + arg: Option<&String>, +) -> Result<Option<Object<'a>>, Error> { + let arg = match arg { + Some(s) => s, + None => return Ok(None), + }; + let obj = repo.revparse_single(arg)?; + let tree = obj.peel(ObjectType::Tree)?; + Ok(Some(tree)) +} + +fn resolve_blob<'a>(repo: &'a Repository, arg: Option<&String>) -> Result<Option<Blob<'a>>, Error> { + let arg = match arg { + Some(s) => Oid::from_str(s)?, + None => return Ok(None), + }; + repo.find_blob(arg).map(|b| Some(b)) +} + +impl Args { + fn cache(&self) -> Cache { + if self.flag_cached { + Cache::Only + } else if self.flag_nocached { + Cache::None + } else { + Cache::Normal + } + } + fn color(&self) -> bool { + self.flag_color && !self.flag_no_color + } + fn diff_format(&self) -> DiffFormat { + if self.flag_patch { + DiffFormat::Patch + } else if self.flag_name_only { + DiffFormat::NameOnly + } else if self.flag_name_status { + DiffFormat::NameStatus + } else if self.flag_raw { + DiffFormat::Raw + } else { + match self.flag_format.as_ref().map(|s| &s[..]) { + Some("name") => DiffFormat::NameOnly, + Some("name-status") => DiffFormat::NameStatus, + Some("raw") => DiffFormat::Raw, + Some("diff-index") => DiffFormat::Raw, + _ => DiffFormat::Patch, + } + } + } +} + +fn main() { + let args = Args::from_args(); + match run(&args) { + Ok(()) => {} + Err(e) => println!("error: {}", e), + } +} |