Skip to content

Commit 9cbd2fb

Browse files
committed
Bind the rest of the diff API
1 parent b64029a commit 9cbd2fb

File tree

7 files changed

+1027
-33
lines changed

7 files changed

+1027
-33
lines changed

examples/log.rs

Lines changed: 69 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,18 @@
1212
* <http://creativecommons.org/publicdomain/zero/1.0/>.
1313
*/
1414

15+
#![feature(macro_rules)]
1516
#![deny(warnings)]
1617

1718
extern crate "rustc-serialize" as rustc_serialize;
1819
extern crate docopt;
1920
extern crate git2;
2021
extern crate time;
2122

23+
use std::str;
2224
use docopt::Docopt;
23-
use git2::{Repository, Signature, Commit, ObjectType, Time};
25+
use git2::{Repository, Signature, Commit, ObjectType, Time, DiffOptions};
26+
use git2::{Pathspec, Diff, Error, DiffFormat};
2427

2528
#[deriving(RustcDecodable)]
2629
struct Args {
@@ -44,10 +47,7 @@ struct Args {
4447
flag_patch: bool,
4548
}
4649

47-
fn run(args: &Args) -> Result<(), git2::Error> {
48-
assert!(args.arg_spec.len() == 0, "unimplemented diff specs");
49-
assert!(!args.flag_patch, "unimplemented patch show");
50-
50+
fn run(args: &Args) -> Result<(), Error> {
5151
let path = args.flag_git_dir.as_ref().map(|s| s.as_slice()).unwrap_or(".");
5252
let repo = try!(Repository::open(&Path::new(path)));
5353
let mut revwalk = try!(repo.revwalk());
@@ -82,29 +82,73 @@ fn run(args: &Args) -> Result<(), git2::Error> {
8282
try!(revwalk.hide(from));
8383
}
8484
}
85-
8685
if args.arg_commit.len() == 0 {
8786
try!(revwalk.push_head());
8887
}
8988

89+
// Prepare our diff options and pathspec matcher
90+
let (mut diffopts, mut diffopts2) = (DiffOptions::new(), DiffOptions::new());
91+
for spec in args.arg_spec.iter() {
92+
diffopts.pathspec(spec);
93+
diffopts2.pathspec(spec);
94+
}
95+
let ps = try!(Pathspec::new(args.arg_spec.iter()));
96+
9097
// Filter our revwalk based on the CLI parameters
91-
let mut revwalk = revwalk.map(|id| {
92-
repo.find_commit(id).unwrap()
93-
}).filter(|commit| {
94-
let len = commit.parents().len();
95-
len >= args.min_parents() && match args.max_parents() {
96-
Some(n) => len < n, None => true
98+
macro_rules! filter_try {
99+
($e:expr) => (match $e { Ok(t) => t, Err(e) => return Some(Err(e)) })
100+
}
101+
let mut revwalk = revwalk.filter_map(|id| {
102+
let commit = filter_try!(repo.find_commit(id));
103+
let parents = commit.parents().len();
104+
if parents < args.min_parents() { return None }
105+
if let Some(n) = args.max_parents() {
106+
if parents >= n { return None }
97107
}
98-
}).filter(|commit| {
99-
sig_matches(commit.author(), &args.flag_author) &&
100-
sig_matches(commit.committer(), &args.flag_committer)
101-
}).filter(|commit| {
102-
log_message_matches(commit.message(), &args.flag_grep)
108+
if args.arg_spec.len() > 0 {
109+
match commit.parents().len() {
110+
0 => {
111+
let tree = filter_try!(commit.tree());
112+
let flags = git2::PATHSPEC_NO_MATCH_ERROR;
113+
if ps.match_tree(&tree, flags).is_err() { return None }
114+
}
115+
_ => {
116+
let m = commit.parents().all(|parent| {
117+
match_with_parent(&repo, &commit, &parent, &mut diffopts)
118+
.unwrap_or(false)
119+
});
120+
if !m { return None }
121+
}
122+
}
123+
}
124+
if !sig_matches(commit.author(), &args.flag_author) { return None }
125+
if !sig_matches(commit.committer(), &args.flag_committer) { return None }
126+
if !log_message_matches(commit.message(), &args.flag_grep) { return None }
127+
Some(Ok(commit))
103128
}).skip(args.flag_skip.unwrap_or(0)).take(args.flag_max_count.unwrap_or(-1));
104129

105130
// print!
106131
for commit in revwalk {
132+
let commit = try!(commit);
107133
print_commit(&commit);
134+
if !args.flag_patch || commit.parents().len() > 1 { continue }
135+
let a = if commit.parents().len() == 1 {
136+
let parent = try!(commit.parent(0));
137+
Some(try!(parent.tree()))
138+
} else {
139+
None
140+
};
141+
let b = try!(commit.tree());
142+
let diff = try!(Diff::tree_to_tree(&repo, a.as_ref(), Some(&b),
143+
Some(&mut diffopts2)));
144+
try!(diff.print(DiffFormat::Patch, |_delta, _hunk, line| {
145+
match line.origin() {
146+
' ' | '+' | '-' => print!("{}", line.origin()),
147+
_ => {}
148+
}
149+
print!("{}", str::from_utf8(line.content()).unwrap());
150+
true
151+
}));
108152
}
109153

110154
Ok(())
@@ -167,6 +211,14 @@ fn print_time(time: &Time, prefix: &str) {
167211

168212
}
169213

214+
fn match_with_parent(repo: &Repository, commit: &Commit, parent: &Commit,
215+
opts: &mut DiffOptions) -> Result<bool, Error> {
216+
let a = try!(parent.tree());
217+
let b = try!(commit.tree());
218+
let diff = try!(Diff::tree_to_tree(repo, Some(&a), Some(&b), Some(opts)));
219+
Ok(diff.deltas().len() > 0)
220+
}
221+
170222
impl Args {
171223
fn min_parents(&self) -> uint {
172224
if self.flag_no_min_parents { return 0 }

libgit2-sys/lib.rs

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ pub use git_status_opt_t::*;
3232
pub use git_status_show_t::*;
3333
pub use git_delta_t::*;
3434
pub use git_sort::*;
35+
pub use git_diff_format_t::*;
36+
pub use git_diff_stats_format_t::*;
3537

3638
use libc::{c_int, c_char, c_uint, size_t, c_uchar, c_void, c_ushort};
3739

@@ -66,6 +68,7 @@ pub enum git_status_list {}
6668
pub enum git_pathspec {}
6769
pub enum git_pathspec_match_list {}
6870
pub enum git_diff {}
71+
pub enum git_diff_stats {}
6972

7073
#[repr(C)]
7174
pub struct git_revspec {
@@ -704,6 +707,117 @@ pub const GIT_PATHSPEC_NO_MATCH_ERROR: u32 = 1 << 3;
704707
pub const GIT_PATHSPEC_FIND_FAILURES: u32 = 1 << 4;
705708
pub const GIT_PATHSPEC_FAILURES_ONLY: u32 = 1 << 5;
706709

710+
pub type git_diff_file_cb = extern fn(*const git_diff_delta, f32, *mut c_void)
711+
-> c_int;
712+
pub type git_diff_hunk_cb = extern fn(*const git_diff_delta,
713+
*const git_diff_hunk,
714+
*mut c_void) -> c_int;
715+
pub type git_diff_line_cb = extern fn(*const git_diff_delta,
716+
*const git_diff_hunk,
717+
*const git_diff_line,
718+
*mut c_void) -> c_int;
719+
720+
#[repr(C)]
721+
pub struct git_diff_hunk {
722+
pub old_start: c_int,
723+
pub old_lines: c_int,
724+
pub new_start: c_int,
725+
pub new_lines: c_int,
726+
pub header_len: size_t,
727+
pub header: [u8, ..128],
728+
}
729+
730+
pub type git_diff_line_t = u8;
731+
pub const GIT_DIFF_LINE_CONTEXT: u8 = ' ' as u8;
732+
pub const GIT_DIFF_LINE_ADDITION: u8 = '+' as u8;
733+
pub const GIT_DIFF_LINE_DELETION: u8 = '-' as u8;
734+
pub const GIT_DIFF_LINE_CONTEXT_EOFNL: u8 = '=' as u8;
735+
pub const GIT_DIFF_LINE_ADD_EOFNL: u8 = '>' as u8;
736+
pub const GIT_DIFF_LINE_DEL_EOFNL: u8 = '<' as u8;
737+
pub const GIT_DIFF_LINE_FILE_HDR: u8 = 'F' as u8;
738+
pub const GIT_DIFF_LINE_HUNK_HDR: u8 = 'H' as u8;
739+
pub const GIT_DIFF_LINE_LINE_BINARY: u8 = 'B' as u8;
740+
741+
#[repr(C)]
742+
pub struct git_diff_line {
743+
pub origin: u8,
744+
pub old_lineno: c_int,
745+
pub new_lineno: c_int,
746+
pub num_lines: c_int,
747+
pub content_len: size_t,
748+
pub content_offset: git_off_t,
749+
pub content: *const u8,
750+
}
751+
752+
#[repr(C)]
753+
pub struct git_diff_options {
754+
pub version: c_uint,
755+
pub flags: u32,
756+
pub ignore_submodules: git_submodule_ignore_t,
757+
pub pathspec: git_strarray,
758+
pub notify_cb: git_diff_notify_cb,
759+
pub notify_payload: *mut c_void,
760+
pub context_lines: u32,
761+
pub interhunk_lines: u32,
762+
pub id_abbrev: u16,
763+
pub max_size: git_off_t,
764+
pub old_prefix: *const c_char,
765+
pub new_prefix: *const c_char,
766+
}
767+
768+
#[repr(C)]
769+
pub enum git_diff_format_t {
770+
GIT_DIFF_FORMAT_PATCH = 1,
771+
GIT_DIFF_FORMAT_PATCH_HEADER = 2,
772+
GIT_DIFF_FORMAT_RAW = 3,
773+
GIT_DIFF_FORMAT_NAME_ONLY = 4,
774+
GIT_DIFF_FORMAT_NAME_STATUS = 5,
775+
}
776+
777+
#[repr(C)]
778+
pub enum git_diff_stats_format_t {
779+
GIT_DIFF_STATS_NONE = 0,
780+
GIT_DIFF_STATS_FULL = 1 << 0,
781+
GIT_DIFF_STATS_SHORT = 1 << 1,
782+
GIT_DIFF_STATS_NUMBER = 1 << 2,
783+
GIT_DIFF_STATS_INCLUDE_SUMMARY = 1 << 3,
784+
}
785+
786+
pub type git_diff_notify_cb = extern fn(*const git_diff,
787+
*const git_diff_delta,
788+
*const c_char,
789+
*mut c_void) -> c_int;
790+
791+
pub type git_diff_options_t = u32;
792+
pub const GIT_DIFF_NORMAL: u32 = 0;
793+
pub const GIT_DIFF_REVERSE: u32 = 1 << 0;
794+
pub const GIT_DIFF_INCLUDE_IGNORED: u32 = 1 << 1;
795+
pub const GIT_DIFF_RECURSE_IGNORED_DIRS: u32 = 1 << 2;
796+
pub const GIT_DIFF_INCLUDE_UNTRACKED: u32 = 1 << 3;
797+
pub const GIT_DIFF_RECURSE_UNTRACKED_DIRS: u32 = 1 << 4;
798+
pub const GIT_DIFF_INCLUDE_UNMODIFIED: u32 = 1 << 5;
799+
pub const GIT_DIFF_INCLUDE_TYPECHANGE: u32 = 1 << 6;
800+
pub const GIT_DIFF_INCLUDE_TYPECHANGE_TREES: u32 = 1 << 7;
801+
pub const GIT_DIFF_IGNORE_FILEMODE: u32 = 1 << 8;
802+
pub const GIT_DIFF_IGNORE_SUBMODULES: u32 = 1 << 9;
803+
pub const GIT_DIFF_IGNORE_CASE: u32 = 1 << 10;
804+
pub const GIT_DIFF_DISABLE_PATHSPEC_MATCH: u32 = 1 << 12;
805+
pub const GIT_DIFF_SKIP_BINARY_CHECK: u32 = 1 << 13;
806+
pub const GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS: u32 = 1 << 14;
807+
pub const GIT_DIFF_UPDATE_INDEX: u32 = 1 << 15;
808+
pub const GIT_DIFF_INCLUDE_UNREADABLE: u32 = 1 << 16;
809+
pub const GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED: u32 = 1 << 17;
810+
pub const GIT_DIFF_FORCE_TEXT: u32 = 1 << 20;
811+
pub const GIT_DIFF_FORCE_BINARY: u32 = 1 << 21;
812+
pub const GIT_DIFF_IGNORE_WHITESPACE: u32 = 1 << 22;
813+
pub const GIT_DIFF_IGNORE_WHITESPACE_CHANGE: u32 = 1 << 23;
814+
pub const GIT_DIFF_IGNORE_WHITESPACE_EOL: u32 = 1 << 24;
815+
pub const GIT_DIFF_SHOW_UNTRACKED_CONTENT: u32 = 1 << 25;
816+
pub const GIT_DIFF_SHOW_UNMODIFIED: u32 = 1 << 26;
817+
pub const GIT_DIFF_PATIENCE: u32 = 1 << 28;
818+
pub const GIT_DIFF_MINIMAL: u32 = 1 << 29;
819+
pub const GIT_DIFF_SHOW_BINARY: u32 = 1 << 30;
820+
707821
/// Initialize openssl for the libgit2 library
708822
#[cfg(unix)]
709823
pub fn openssl_init() {
@@ -1630,6 +1744,92 @@ extern {
16301744
path: *const c_char) -> c_int;
16311745
pub fn git_pathspec_new(out: *mut *mut git_pathspec,
16321746
pathspec: *const git_strarray) -> c_int;
1747+
1748+
// diff
1749+
pub fn git_diff_blob_to_buffer(old_blob: *const git_blob,
1750+
old_as_path: *const c_char,
1751+
buffer: *const c_char,
1752+
buffer_len: size_t,
1753+
buffer_as_path: *const c_char,
1754+
options: *const git_diff_options,
1755+
file_cb: git_diff_file_cb,
1756+
hunk_cb: git_diff_hunk_cb,
1757+
line_cb: git_diff_line_cb,
1758+
payload: *mut c_void) -> c_int;
1759+
pub fn git_diff_blobs(old_blob: *const git_blob,
1760+
old_as_path: *const c_char,
1761+
new_blob: *const git_blob,
1762+
new_as_path: *const c_char,
1763+
options: *const git_diff_options,
1764+
file_cb: git_diff_file_cb,
1765+
hunk_cb: git_diff_hunk_cb,
1766+
line_cb: git_diff_line_cb,
1767+
payload: *mut c_void) -> c_int;
1768+
pub fn git_diff_buffers(old_buffer: *const c_void,
1769+
old_len: size_t,
1770+
old_as_path: *const c_char,
1771+
new_buffer: *const c_void,
1772+
new_len: size_t,
1773+
new_as_path: *const c_char,
1774+
options: *const git_diff_options,
1775+
file_cb: git_diff_file_cb,
1776+
hunk_cb: git_diff_hunk_cb,
1777+
line_cb: git_diff_line_cb,
1778+
payload: *mut c_void) -> c_int;
1779+
pub fn git_diff_foreach(diff: *mut git_diff,
1780+
file_cb: git_diff_file_cb,
1781+
hunk_cb: git_diff_hunk_cb,
1782+
line_cb: git_diff_line_cb,
1783+
payload: *mut c_void) -> c_int;
1784+
pub fn git_diff_free(diff: *mut git_diff);
1785+
pub fn git_diff_get_delta(diff: *const git_diff,
1786+
idx: size_t) -> *const git_diff_delta;
1787+
pub fn git_diff_get_stats(out: *mut *mut git_diff_stats,
1788+
diff: *mut git_diff) -> c_int;
1789+
pub fn git_diff_index_to_workdir(diff: *mut *mut git_diff,
1790+
repo: *mut git_repository,
1791+
index: *mut git_index,
1792+
opts: *const git_diff_options) -> c_int;
1793+
pub fn git_diff_init_options(opts: *mut git_diff_options,
1794+
version: c_uint) -> c_int;
1795+
pub fn git_diff_is_sorted_icase(diff: *const git_diff) -> c_int;
1796+
pub fn git_diff_merge(onto: *mut git_diff,
1797+
from: *const git_diff) -> c_int;
1798+
pub fn git_diff_num_deltas(diff: *const git_diff) -> size_t;
1799+
pub fn git_diff_num_deltas_of_type(diff: *const git_diff,
1800+
delta: git_delta_t) -> size_t;
1801+
pub fn git_diff_print(diff: *mut git_diff,
1802+
format: git_diff_format_t,
1803+
print_cb: git_diff_line_cb,
1804+
payload: *mut c_void) -> c_int;
1805+
pub fn git_diff_stats_deletions(stats: *const git_diff_stats) -> size_t;
1806+
pub fn git_diff_stats_files_changed(stats: *const git_diff_stats) -> size_t;
1807+
pub fn git_diff_stats_free(stats: *mut git_diff_stats);
1808+
pub fn git_diff_stats_insertions(stats: *const git_diff_stats) -> size_t;
1809+
pub fn git_diff_stats_to_buf(out: *mut git_buf,
1810+
stats: *const git_diff_stats,
1811+
format: git_diff_stats_format_t,
1812+
width: size_t) -> c_int;
1813+
pub fn git_diff_status_char(status: git_delta_t) -> c_char;
1814+
pub fn git_diff_tree_to_index(diff: *mut *mut git_diff,
1815+
repo: *mut git_repository,
1816+
old_tree: *mut git_tree,
1817+
index: *mut git_index,
1818+
opts: *const git_diff_options) -> c_int;
1819+
pub fn git_diff_tree_to_tree(diff: *mut *mut git_diff,
1820+
repo: *mut git_repository,
1821+
old_tree: *mut git_tree,
1822+
new_tree: *mut git_tree,
1823+
opts: *const git_diff_options) -> c_int;
1824+
pub fn git_diff_tree_to_workdir(diff: *mut *mut git_diff,
1825+
repo: *mut git_repository,
1826+
old_tree: *mut git_tree,
1827+
opts: *const git_diff_options) -> c_int;
1828+
pub fn git_diff_tree_to_workdir_with_index(diff: *mut *mut git_diff,
1829+
repo: *mut git_repository,
1830+
old_tree: *mut git_tree,
1831+
opts: *const git_diff_options)
1832+
-> c_int;
16331833
}
16341834

16351835
#[test]

0 commit comments

Comments
 (0)