diff options
-rw-r--r-- | CHANGES | 7 | ||||
-rw-r--r-- | lib/git/diff.py | 65 | ||||
-rw-r--r-- | test/git/test_diff.py | 19 |
3 files changed, 79 insertions, 12 deletions
@@ -80,6 +80,13 @@ Diff * Members a a_commit and b_commit renamed to a_blob and b_blob - they are populated with Blob objects if possible * Members a_path and b_path removed as this information is kept in the blobs +* Diffs are now returned as DiffIndex allowing to more quickly find the kind of + diffs you are interested in + +Diffing +------- +* Commit and Tree objects now support diffing natively with a common interface to + compare agains other Commits or Trees, against the working tree or against the index. Blob ---- diff --git a/lib/git/diff.py b/lib/git/diff.py index 6a6a097c..1774597a 100644 --- a/lib/git/diff.py +++ b/lib/git/diff.py @@ -22,6 +22,10 @@ class Diffable(object): # them in this tuple _diff_args = tuple() + # Temporary standin for Index type until we have a real index type + class Index(object): + pass + def diff(self, other=None, paths=None, create_patch=False, **kwargs): """ Creates diffs between two items being trees, trees and index or an @@ -30,6 +34,7 @@ class Diffable(object): ``other`` Is the item to compare us with. If None, we will be compared to the working tree. + If Index ( type ), it will be compared against the index ``paths`` is a list of paths or a single path to limit the diff to. @@ -63,8 +68,10 @@ class Diffable(object): if paths is not None and not isinstance(paths, (tuple,list)): paths = [ paths ] - if other is not None: + if other is not None and other is not self.Index: args.insert(0, other) + if other is self.Index: + args.insert(0, "--cached") args.insert(0,self) @@ -90,7 +97,33 @@ class DiffIndex(list): The class improves the diff handling convenience """ + # change type invariant identifying possible ways a blob can have changed + # A = Added + # D = Deleted + # R = Renamed + # NOTE: 'Modified' mode is impllied as it wouldn't be listed as a diff otherwise + change_type = ("A", "D", "R") + + def iter_change_type(self, change_type): + """ + Return + iterator yieling Diff instances that match the given change_type + + ``change_type`` + Member of DiffIndex.change_type + """ + if change_type not in self.change_type: + raise ValueError( "Invalid change type: %s" % change_type ) + + for diff in self: + if change_type == "A" and diff.new_file: + yield diff + elif change_type == "D" and diff.deleted_file: + yield diff + elif change_type == "R" and diff.renamed: + yield diff + # END for each diff class Diff(object): @@ -132,7 +165,7 @@ class Diff(object): """, re.VERBOSE | re.MULTILINE) re_is_null_hexsha = re.compile( r'^0{40}$' ) __slots__ = ("a_blob", "b_blob", "a_mode", "b_mode", "new_file", "deleted_file", - "rename_from", "rename_to", "renamed", "diff") + "rename_from", "rename_to", "diff") def __init__(self, repo, a_path, b_path, a_blob_id, b_blob_id, a_mode, b_mode, new_file, deleted_file, rename_from, @@ -148,17 +181,29 @@ class Diff(object): self.a_mode = a_mode self.b_mode = b_mode + if self.a_mode: self.a_mode = blob.Blob._mode_str_to_int( self.a_mode ) if self.b_mode: self.b_mode = blob.Blob._mode_str_to_int( self.b_mode ) + self.new_file = new_file self.deleted_file = deleted_file - self.rename_from = rename_from - self.rename_to = rename_to - self.renamed = rename_from != rename_to + + # be clear and use None instead of empty strings + self.rename_from = rename_from or None + self.rename_to = rename_to or None + self.diff = diff + @property + def renamed(self): + """ + Returns: + True if the blob of our diff has been renamed + """ + return self.rename_from != self.rename_to + @classmethod def _index_from_patch_format(cls, repo, stream): """ @@ -210,20 +255,22 @@ class Diff(object): if not line.startswith(":"): continue # END its not a valid diff line - old_mode, new_mode, a_blob_id, b_blob_id, modification_id, path = line[1:].split() + old_mode, new_mode, a_blob_id, b_blob_id, change_type, path = line[1:].split() a_path = path b_path = path deleted_file = False new_file = False - if modification_id == 'D': + + # NOTE: We cannot conclude from the existance of a blob to change type + # as diffs with the working do not have blobs yet + if change_type == 'D': b_path = None deleted_file = True - elif modification_id == 'A': + elif change_type == 'A': a_path = None new_file = True # END add/remove handling - diff = Diff(repo, a_path, b_path, a_blob_id, b_blob_id, old_mode, new_mode, new_file, deleted_file, None, None, '') index.append(diff) diff --git a/test/git/test_diff.py b/test/git/test_diff.py index c9604faf..deae7cfc 100644 --- a/test/git/test_diff.py +++ b/test/git/test_diff.py @@ -42,22 +42,35 @@ class TestDiff(TestCase): def test_diff_interface(self): # test a few variations of the main diff routine + assertion_map = dict() for i, commit in enumerate(self.repo.iter_commits('0.1.6', max_count=10)): diff_item = commit if i%2 == 0: diff_item = commit.tree # END use tree every second item - for other in (None, commit.parents[0]): + for other in (None, commit.Index, commit.parents[0]): for paths in (None, "CHANGES", ("CHANGES", "lib")): for create_patch in range(2): diff_index = diff_item.diff(other, paths, create_patch) assert isinstance(diff_index, DiffIndex) - # TODO: test diff index + if diff_index: + for ct in DiffIndex.change_type: + key = 'ct_%s'%ct + assertion_map.setdefault(key, 0) + assertion_map[key] = assertion_map[key]+len(list(diff_index.iter_change_type(ct))) + # END for each changetype + # END diff index checking # END for each patch option # END for each path option # END for each other side # END for each commit - self.fail( "TODO: Test full diff interface on commits, trees, index, patch and non-patch" ) + # assert we could always find at least one instance of the members we + # can iterate in the diff index - if not this indicates its not working correctly + # or our test does not span the whole range of possibilities + for key,value in assertion_map.items(): + assert value, "Did not find diff for %s" % key + # END for each iteration type + |