summaryrefslogtreecommitdiffstats
path: root/extra/git2/examples/diff.rs
diff options
context:
space:
mode:
Diffstat (limited to 'extra/git2/examples/diff.rs')
-rw-r--r--extra/git2/examples/diff.rs368
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),
+ }
+}