Skip to content

Commit f21d82c

Browse files
authored
Merge pull request #648 from tenderlove/ruby-singleton-diff
Move Rugged::Tree.diff to Ruby
2 parents e6560af + 67369c1 commit f21d82c

File tree

3 files changed

+186
-161
lines changed

3 files changed

+186
-161
lines changed

ext/rugged/rugged_tree.c

Lines changed: 28 additions & 161 deletions
Original file line numberDiff line numberDiff line change
@@ -354,183 +354,49 @@ static VALUE rb_git_tree_path(VALUE self, VALUE rb_path)
354354
return rb_entry;
355355
}
356356

357-
/*
358-
* call-seq:
359-
* Tree.diff(repo, tree, diffable[, options]) -> diff
360-
*
361-
* Returns a diff between the `tree` and the diffable object that was given.
362-
* +diffable+ can either be a +Rugged::Commit+, a +Rugged::Tree+, a +Rugged::Index+,
363-
* or +nil+.
364-
*
365-
* The +tree+ object will be used as the "old file" side of the diff, while the
366-
* parent tree or the +diffable+ object will be used for the "new file" side.
367-
*
368-
* If +tree+ or +diffable+ are nil, they will be treated as an empty tree. Passing
369-
* both as `nil` will raise an exception.
370-
*
371-
* The following options can be passed in the +options+ Hash:
372-
*
373-
* :paths ::
374-
* An array of paths / fnmatch patterns to constrain the diff to a specific
375-
* set of files. Also see +:disable_pathspec_match+.
376-
*
377-
* :max_size ::
378-
* An integer specifying the maximum byte size of a file before a it will
379-
* be treated as binary. The default value is 512MB.
380-
*
381-
* :context_lines ::
382-
* The number of unchanged lines that define the boundary of a hunk (and
383-
* to display before and after the actual changes). The default is 3.
384-
*
385-
* :interhunk_lines ::
386-
* The maximum number of unchanged lines between hunk boundaries before the hunks
387-
* will be merged into a one. The default is 0.
388-
*
389-
* :old_prefix ::
390-
* The virtual "directory" to prefix to old filenames in hunk headers.
391-
* The default is "a".
392-
*
393-
* :new_prefix ::
394-
* The virtual "directory" to prefix to new filenames in hunk headers.
395-
* The default is "b".
396-
*
397-
* :reverse ::
398-
* If true, the sides of the diff will be reversed.
399-
*
400-
* :force_text ::
401-
* If true, all files will be treated as text, disabling binary attributes & detection.
402-
*
403-
* :ignore_whitespace ::
404-
* If true, all whitespace will be ignored.
405-
*
406-
* :ignore_whitespace_change ::
407-
* If true, changes in amount of whitespace will be ignored.
408-
*
409-
* :ignore_whitespace_eol ::
410-
* If true, whitespace at end of line will be ignored.
411-
*
412-
* :ignore_submodules ::
413-
* if true, submodules will be excluded from the diff completely.
414-
*
415-
* :patience ::
416-
* If true, the "patience diff" algorithm will be used (currenlty unimplemented).
417-
*
418-
* :include_ignored ::
419-
* If true, ignored files will be included in the diff.
420-
*
421-
* :include_untracked ::
422-
* If true, untracked files will be included in the diff.
423-
*
424-
* :include_unmodified ::
425-
* If true, unmodified files will be included in the diff.
426-
*
427-
* :recurse_untracked_dirs ::
428-
* Even if +:include_untracked+ is true, untracked directories will only be
429-
* marked with a single entry in the diff. If this flag is set to true,
430-
* all files under ignored directories will be included in the diff, too.
431-
*
432-
* :disable_pathspec_match ::
433-
* If true, the given +:paths+ will be applied as exact matches, instead of
434-
* as fnmatch patterns.
435-
*
436-
* :deltas_are_icase ::
437-
* If true, filename comparisons will be made with case-insensitivity.
438-
*
439-
* :include_untracked_content ::
440-
* if true, untracked content will be contained in the the diff patch text.
441-
*
442-
* :skip_binary_check ::
443-
* If true, diff deltas will be generated without spending time on binary
444-
* detection. This is useful to improve performance in cases where the actual
445-
* file content difference is not needed.
446-
*
447-
* :include_typechange ::
448-
* If true, type changes for files will not be interpreted as deletion of
449-
* the "old file" and addition of the "new file", but will generate
450-
* typechange records.
451-
*
452-
* :include_typechange_trees ::
453-
* Even if +:include_typechange+ is true, blob -> tree changes will still
454-
* usually be handled as a deletion of the blob. If this flag is set to true,
455-
* blob -> tree changes will be marked as typechanges.
456-
*
457-
* :ignore_filemode ::
458-
* If true, file mode changes will be ignored.
459-
*
460-
* :recurse_ignored_dirs ::
461-
* Even if +:include_ignored+ is true, ignored directories will only be
462-
* marked with a single entry in the diff. If this flag is set to true,
463-
* all files under ignored directories will be included in the diff, too.
464-
*
465-
* Examples:
466-
*
467-
* # Emulating `git diff <treeish>`
468-
* tree = Rugged::Tree.lookup(repo, "d70d245ed97ed2aa596dd1af6536e4bfdb047b69")
469-
* diff = tree.diff(repo.index)
470-
* diff.merge!(tree.diff)
471-
*
472-
* # Tree-to-Tree Diff
473-
* tree = Rugged::Tree.lookup(repo, "d70d245ed97ed2aa596dd1af6536e4bfdb047b69")
474-
* other_tree = Rugged::Tree.lookup(repo, "7a9e0b02e63179929fed24f0a3e0f19168114d10")
475-
* diff = tree.diff(other_tree)
476-
*/
477-
static VALUE rb_git_tree_diff_(int argc, VALUE *argv, VALUE self)
357+
static VALUE rb_git_diff_tree_to_index(VALUE self, VALUE rb_repo, VALUE rb_self, VALUE rb_other, VALUE rb_options)
478358
{
479359
git_tree *tree = NULL;
480360
git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
481361
git_repository *repo = NULL;
482362
git_diff *diff = NULL;
483-
VALUE rb_self, rb_repo, rb_other, rb_options;
363+
git_index *index;
484364
int error;
485365

486-
rb_scan_args(argc, argv, "22", &rb_repo, &rb_self, &rb_other, &rb_options);
487366
Data_Get_Struct(rb_repo, git_repository, repo);
488-
rugged_parse_diff_options(&opts, rb_options);
367+
Data_Get_Struct(rb_other, git_index, index);
489368

490-
if (!NIL_P(rb_self)) {
491-
if (!rb_obj_is_kind_of(rb_self, rb_cRuggedTree))
492-
rb_raise(rb_eTypeError,
493-
"At least a Rugged::Tree object is required for diffing");
369+
rugged_parse_diff_options(&opts, rb_options);
494370

371+
if (RTEST(rb_self)) {
495372
Data_Get_Struct(rb_self, git_tree, tree);
496373
}
497374

498-
if (NIL_P(rb_other)) {
499-
if (tree == NULL) {
500-
xfree(opts.pathspec.strings);
501-
rb_raise(rb_eTypeError, "Need 'old' or 'new' for diffing");
502-
}
375+
error = git_diff_tree_to_index(&diff, repo, tree, index, &opts);
503376

504-
error = git_diff_tree_to_tree(&diff, repo, tree, NULL, &opts);
505-
} else {
506-
if (TYPE(rb_other) == T_STRING)
507-
rb_other = rugged_object_rev_parse(rb_repo, rb_other, 1);
377+
xfree(opts.pathspec.strings);
378+
rugged_exception_check(error);
508379

509-
if (rb_obj_is_kind_of(rb_other, rb_cRuggedCommit)) {
510-
git_tree *other_tree;
511-
git_commit *commit;
380+
return rugged_diff_new(rb_cRuggedDiff, rb_repo, diff);
381+
}
512382

513-
Data_Get_Struct(rb_other, git_commit, commit);
514-
error = git_commit_tree(&other_tree, commit);
383+
static VALUE rb_git_diff_tree_to_tree(VALUE self, VALUE rb_repo, VALUE rb_tree, VALUE rb_other_tree, VALUE rb_options) {
384+
git_tree *tree = NULL;
385+
git_tree *other_tree = NULL;
386+
git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
387+
git_repository *repo = NULL;
388+
git_diff *diff = NULL;
389+
int error;
515390

516-
if (!error) {
517-
error = git_diff_tree_to_tree(&diff, repo, tree, other_tree, &opts);
518-
git_tree_free(other_tree);
519-
}
520-
} else if (rb_obj_is_kind_of(rb_other, rb_cRuggedTree)) {
521-
git_tree *other_tree;
522-
523-
Data_Get_Struct(rb_other, git_tree, other_tree);
524-
error = git_diff_tree_to_tree(&diff, repo, tree, other_tree, &opts);
525-
} else if (rb_obj_is_kind_of(rb_other, rb_cRuggedIndex)) {
526-
git_index *index;
527-
Data_Get_Struct(rb_other, git_index, index);
528-
error = git_diff_tree_to_index(&diff, repo, tree, index, &opts);
529-
} else {
530-
xfree(opts.pathspec.strings);
531-
rb_raise(rb_eTypeError, "A Rugged::Commit, Rugged::Tree or Rugged::Index instance is required");
532-
}
533-
}
391+
Data_Get_Struct(rb_repo, git_repository, repo);
392+
Data_Get_Struct(rb_tree, git_tree, tree);
393+
394+
if(RTEST(rb_other_tree))
395+
Data_Get_Struct(rb_other_tree, git_tree, other_tree);
396+
397+
rugged_parse_diff_options(&opts, rb_options);
398+
399+
error = git_diff_tree_to_tree(&diff, repo, tree, other_tree, &opts);
534400

535401
xfree(opts.pathspec.strings);
536402
rugged_exception_check(error);
@@ -1036,7 +902,8 @@ void Init_rugged_tree(void)
1036902
rb_define_method(rb_cRuggedTree, "update", rb_git_tree_update, 1);
1037903
rb_define_singleton_method(rb_cRuggedTree, "empty", rb_git_tree_empty, 1);
1038904

1039-
rb_define_singleton_method(rb_cRuggedTree, "diff", rb_git_tree_diff_, -1);
905+
rb_define_private_method(rb_singleton_class(rb_cRuggedTree), "diff_tree_to_index", rb_git_diff_tree_to_index, 4);
906+
rb_define_private_method(rb_singleton_class(rb_cRuggedTree), "diff_tree_to_tree", rb_git_diff_tree_to_tree, 4);
1040907

1041908
rb_cRuggedTreeBuilder = rb_define_class_under(rb_cRuggedTree, "Builder", rb_cObject);
1042909
rb_define_singleton_method(rb_cRuggedTreeBuilder, "new", rb_git_treebuilder_new, -1);

lib/rugged/tree.rb

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,155 @@
11
module Rugged
22
class Tree
3+
##
4+
# call-seq:
5+
# Tree.diff(repo, tree, diffable[, options]) -> diff
6+
#
7+
# Returns a diff between the `tree` and the diffable object that was given.
8+
# +diffable+ can either be a +Rugged::Commit+, a +Rugged::Tree+, a +Rugged::Index+,
9+
# or +nil+.
10+
#
11+
# The +tree+ object will be used as the "old file" side of the diff, while the
12+
# parent tree or the +diffable+ object will be used for the "new file" side.
13+
#
14+
# If +tree+ or +diffable+ are nil, they will be treated as an empty tree. Passing
15+
# both as `nil` will raise an exception.
16+
#
17+
# The following options can be passed in the +options+ Hash:
18+
#
19+
# :paths ::
20+
# An array of paths / fnmatch patterns to constrain the diff to a specific
21+
# set of files. Also see +:disable_pathspec_match+.
22+
#
23+
# :max_size ::
24+
# An integer specifying the maximum byte size of a file before a it will
25+
# be treated as binary. The default value is 512MB.
26+
#
27+
# :context_lines ::
28+
# The number of unchanged lines that define the boundary of a hunk (and
29+
# to display before and after the actual changes). The default is 3.
30+
#
31+
# :interhunk_lines ::
32+
# The maximum number of unchanged lines between hunk boundaries before the hunks
33+
# will be merged into a one. The default is 0.
34+
#
35+
# :old_prefix ::
36+
# The virtual "directory" to prefix to old filenames in hunk headers.
37+
# The default is "a".
38+
#
39+
# :new_prefix ::
40+
# The virtual "directory" to prefix to new filenames in hunk headers.
41+
# The default is "b".
42+
#
43+
# :reverse ::
44+
# If true, the sides of the diff will be reversed.
45+
#
46+
# :force_text ::
47+
# If true, all files will be treated as text, disabling binary attributes & detection.
48+
#
49+
# :ignore_whitespace ::
50+
# If true, all whitespace will be ignored.
51+
#
52+
# :ignore_whitespace_change ::
53+
# If true, changes in amount of whitespace will be ignored.
54+
#
55+
# :ignore_whitespace_eol ::
56+
# If true, whitespace at end of line will be ignored.
57+
#
58+
# :ignore_submodules ::
59+
# if true, submodules will be excluded from the diff completely.
60+
#
61+
# :patience ::
62+
# If true, the "patience diff" algorithm will be used (currenlty unimplemented).
63+
#
64+
# :include_ignored ::
65+
# If true, ignored files will be included in the diff.
66+
#
67+
# :include_untracked ::
68+
# If true, untracked files will be included in the diff.
69+
#
70+
# :include_unmodified ::
71+
# If true, unmodified files will be included in the diff.
72+
#
73+
# :recurse_untracked_dirs ::
74+
# Even if +:include_untracked+ is true, untracked directories will only be
75+
# marked with a single entry in the diff. If this flag is set to true,
76+
# all files under ignored directories will be included in the diff, too.
77+
#
78+
# :disable_pathspec_match ::
79+
# If true, the given +:paths+ will be applied as exact matches, instead of
80+
# as fnmatch patterns.
81+
#
82+
# :deltas_are_icase ::
83+
# If true, filename comparisons will be made with case-insensitivity.
84+
#
85+
# :include_untracked_content ::
86+
# if true, untracked content will be contained in the the diff patch text.
87+
#
88+
# :skip_binary_check ::
89+
# If true, diff deltas will be generated without spending time on binary
90+
# detection. This is useful to improve performance in cases where the actual
91+
# file content difference is not needed.
92+
#
93+
# :include_typechange ::
94+
# If true, type changes for files will not be interpreted as deletion of
95+
# the "old file" and addition of the "new file", but will generate
96+
# typechange records.
97+
#
98+
# :include_typechange_trees ::
99+
# Even if +:include_typechange+ is true, blob -> tree changes will still
100+
# usually be handled as a deletion of the blob. If this flag is set to true,
101+
# blob -> tree changes will be marked as typechanges.
102+
#
103+
# :ignore_filemode ::
104+
# If true, file mode changes will be ignored.
105+
#
106+
# :recurse_ignored_dirs ::
107+
# Even if +:include_ignored+ is true, ignored directories will only be
108+
# marked with a single entry in the diff. If this flag is set to true,
109+
# all files under ignored directories will be included in the diff, too.
110+
#
111+
# Examples:
112+
#
113+
# # Emulating `git diff <treeish>`
114+
# tree = Rugged::Tree.lookup(repo, "d70d245ed97ed2aa596dd1af6536e4bfdb047b69")
115+
# diff = tree.diff(repo.index)
116+
# diff.merge!(tree.diff)
117+
#
118+
# # Tree-to-Tree Diff
119+
# tree = Rugged::Tree.lookup(repo, "d70d245ed97ed2aa596dd1af6536e4bfdb047b69")
120+
# other_tree = Rugged::Tree.lookup(repo, "7a9e0b02e63179929fed24f0a3e0f19168114d10")
121+
# diff = tree.diff(other_tree)
122+
#
123+
124+
def self.diff(repo, tree, other_tree = nil, options = {})
125+
if tree && !tree.is_a?(Rugged::Tree)
126+
raise TypeError, "At least a Rugged::Tree object is required for diffing"
127+
end
128+
129+
if other_tree.nil?
130+
if tree.nil?
131+
raise TypeError, "Need 'old' or 'new' for diffing"
132+
else
133+
diff_tree_to_tree repo, tree, nil, options
134+
end
135+
else
136+
if other_tree.is_a?(::String)
137+
other_tree = Rugged::Object.rev_parse repo, other_tree
138+
end
139+
140+
case other_tree
141+
when Rugged::Commit
142+
diff_tree_to_tree repo, tree, other_tree.tree, options
143+
when Rugged::Tree
144+
diff_tree_to_tree repo, tree, other_tree, options
145+
when Rugged::Index
146+
diff_tree_to_index repo, tree, other_tree, options
147+
else
148+
raise TypeError, "A Rugged::Commit, Rugged::Tree or Rugged::Index instance is required"
149+
end
150+
end
151+
end
152+
3153
include Enumerable
4154

5155
attr_reader :owner

test/diff_test.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1187,4 +1187,12 @@ def test_other_is_wrong_type
11871187
end
11881188
assert_equal "A Rugged::Commit, Rugged::Tree or Rugged::Index instance is required", ex.message
11891189
end
1190+
1191+
def test_other_tree_is_an_index_but_tree_is_nil
1192+
repo = FixtureRepo.from_libgit2("diff")
1193+
1194+
diff = Rugged::Tree.diff repo, nil, repo.index
1195+
assert_equal 2, diff.size
1196+
assert_equal 2, diff.deltas.size
1197+
end
11901198
end

0 commit comments

Comments
 (0)