From ea33fe8b21d2b02f902b131aba0d14389f2f8715 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 22 Oct 2009 22:14:02 +0200 Subject: Index: Is now diffable and appears to properly implement diffing against other items as well as the working tree Diff.Diffable: added callback allowing superclasses to preprocess diff arguments Diff.Diff: added eq, ne and hash methods, string methods would be nice --- TODO | 9 ++++++ lib/git/diff.py | 37 +++++++++++++++++------ lib/git/errors.py | 6 ++-- lib/git/index.py | 72 ++++++++++++++++++++++++++++++++++++++++++--- lib/git/objects/__init__.py | 1 + test/git/test_diff.py | 13 +++++--- test/git/test_index.py | 35 +++++++++++++++++++++- 7 files changed, 152 insertions(+), 21 deletions(-) diff --git a/TODO b/TODO index db77df67..3e743e65 100644 --- a/TODO +++ b/TODO @@ -58,6 +58,15 @@ Index creating several tree objects, so in the end it might be slower. Hmm, probably its okay to use the command unless we go c(++) + +Head.reset +---------- +* Should better be an instance method. Problem was that there is no class specifying + the HEAD - in a way reset would always effect the active branch. + Probably it would be okay to have a special type called SymbolicReference + which represents items like HEAD. These could naturally carry the reset + instance method. + Remote ------ * 'push' method needs a test, a true test repository is required though, a fork diff --git a/lib/git/diff.py b/lib/git/diff.py index 9b884502..03e6709c 100644 --- a/lib/git/diff.py +++ b/lib/git/diff.py @@ -18,13 +18,18 @@ class Diffable(object): """ __slots__ = tuple() - # subclasses provide additional arguments to the git-diff comamnd by supplynig - # them in this tuple - _diff_args = tuple() - - # Temporary standin for Index type until we have a real index type + # standin indicating you want to diff against the index class Index(object): pass + + def _process_diff_args(self, args): + """ + Returns + possibly altered version of the given args list. + Method is called right before git command execution. + Subclasses can use it to alter the behaviour of the superclass + """ + return args def diff(self, other=Index, paths=None, create_patch=False, **kwargs): """ @@ -60,13 +65,13 @@ class Diffable(object): On a bare repository, 'other' needs to be provided as Index or as as Tree/Commit, or a git command error will occour """ - args = list(self._diff_args[:]) + args = list() args.append( "--abbrev=40" ) # we need full shas args.append( "--full-index" ) # get full index paths, not only filenames if create_patch: args.append("-p") - args.append("-M") # check for renames + args.append("-M") # check for renames else: args.append("--raw") @@ -87,7 +92,7 @@ class Diffable(object): # END paths handling kwargs['as_process'] = True - proc = self.repo.git.diff(*args, **kwargs) + proc = self.repo.git.diff(*self._process_diff_args(args), **kwargs) diff_method = Diff._index_from_raw_format if create_patch: @@ -96,7 +101,7 @@ class Diffable(object): status = proc.wait() if status != 0: - raise GitCommandError("git-diff", status, proc.stderr ) + raise GitCommandError(("git diff",)+tuple(args), status, proc.stderr.read()) return index @@ -207,6 +212,20 @@ class Diff(object): self.diff = diff + + def __eq__(self, other): + for name in self.__slots__: + if getattr(self, name) != getattr(other, name): + return False + # END for each name + return True + + def __ne__(self, other): + return not ( self == other ) + + def __hash__(self): + return hash(tuple(getattr(self,n) for n in self.__slots__)) + @property def renamed(self): """ diff --git a/lib/git/errors.py b/lib/git/errors.py index 18c58073..cde2798a 100644 --- a/lib/git/errors.py +++ b/lib/git/errors.py @@ -25,8 +25,8 @@ class GitCommandError(Exception): self.stderr = stderr self.status = status self.command = command - + def __str__(self): - return repr("'%s' returned exit status %d: %r" % - (' '.join(self.command), self.status, str(self.stderr))) + return ("'%s' returned exit status %i: %s" % + (' '.join(str(i) for i in self.command), self.status, self.stderr)) diff --git a/lib/git/index.py b/lib/git/index.py index 4eabab15..4217c9a2 100644 --- a/lib/git/index.py +++ b/lib/git/index.py @@ -14,9 +14,11 @@ import objects import tempfile import os import stat -from git.objects import Blob, Tree +import git.diff as diff + +from git.objects import Blob, Tree, Object from git.utils import SHA1Writer, LazyMixin, ConcurrentWriteOperation -from git.diff import Diffable + class _TemporaryFileSwap(object): """ @@ -140,7 +142,7 @@ class IndexEntry(tuple): return IndexEntry((time, time, 0, 0, blob.mode, 0, 0, blob.size, blob.id, 0, blob.path)) -class Index(LazyMixin): +class Index(LazyMixin, diff.Diffable): """ Implements an Index that can be manipulated using a native implementation in order to save git command function calls wherever possible. @@ -154,7 +156,7 @@ class Index(LazyMixin): The index contains an entries dict whose keys are tuples of type IndexEntry to facilitate access. """ - __slots__ = ( "repo", "version", "entries", "_extension_data" ) + __slots__ = ( "repo", "version", "entries", "_extension_data", "_is_default_index" ) _VERSION = 2 # latest version we support S_IFGITLINK = 0160000 @@ -168,10 +170,13 @@ class Index(LazyMixin): self.repo = repo self.version = self._VERSION self._extension_data = '' + self._is_default_index = True if stream is not None: + self._is_default_index = False self._read_from_stream(stream) # END read from stream immediatly + def _set_cache_(self, attr): if attr == "entries": # read the current index @@ -187,6 +192,18 @@ class Index(LazyMixin): def _index_path(self): return os.path.join(self.repo.path, "index") + + @property + def path(self): + """ + Returns + Path to the index file we are representing or None if we are + a loose index that was read from a stream. + """ + if self._is_default_index: + return self._index_path() + return None + @classmethod def _read_entry(cls, stream): """Return: One entry of the given stream""" @@ -535,4 +552,51 @@ class Index(LazyMixin): # END write tree handling return Tree(self.repo, tree_sha, 0, '') + + def _process_diff_args(self, args): + try: + args.pop(args.index(self)) + except IndexError: + pass + # END remove self + return args + + def diff(self, other=diff.Diffable.Index, paths=None, create_patch=False, **kwargs): + """ + Diff this index against the working copy or a Tree or Commit object + + For a documentation of the parameters and return values, see + Diffable.diff + + Note + Will only work with indices that represent the default git index as + they have not been initialized with a stream. + """ + if not self._is_default_index: + raise AssertionError( "Cannot diff custom indices as they do not represent the default git index" ) + + # index against index is always empty + if other is self.Index: + return diff.DiffIndex() + + # index against anything but None is a reverse diff with the respective + # item. Handle existing -R flags properly. Transform strings to the object + # so that we can call diff on it + if isinstance(other, basestring): + other = Object.new(self.repo, other) + # END object conversion + + if isinstance(other, Object): + # invert the existing R flag + cur_val = kwargs.get('R', False) + kwargs['R'] = not cur_val + return other.diff(self.Index, paths, create_patch, **kwargs) + # END diff against other item handlin + + # if other is not None here, something is wrong + if other is not None: + raise ValueError( "other must be None, Diffable.Index, a Tree or Commit, was %r" % other ) + + # diff against working copy - can be handled by superclass natively + return super(Index, self).diff(other, paths, create_patch, **kwargs) diff --git a/lib/git/objects/__init__.py b/lib/git/objects/__init__.py index 39e650b7..192750e3 100644 --- a/lib/git/objects/__init__.py +++ b/lib/git/objects/__init__.py @@ -2,6 +2,7 @@ Import all submodules main classes into the package space """ import inspect +from base import * from tag import * from blob import * from tree import * diff --git a/test/git/test_diff.py b/test/git/test_diff.py index d7505987..ead231e5 100644 --- a/test/git/test_diff.py +++ b/test/git/test_diff.py @@ -59,6 +59,14 @@ class TestDiff(TestBase): assertion_map.setdefault(key, 0) assertion_map[key] = assertion_map[key]+len(list(diff_index.iter_change_type(ct))) # END for each changetype + + # check entries + diff_set = set() + diff_set.add(diff_index[0]) + diff_set.add(diff_index[0]) + assert len(diff_set) == 1 + assert diff_index[0] == diff_index[0] + assert not (diff_index[0] != diff_index[0]) # END diff index checking # END for each patch option # END for each path option @@ -71,7 +79,4 @@ class TestDiff(TestBase): for key,value in assertion_map.items(): assert value, "Did not find diff for %s" % key # END for each iteration type - - def test_diff_index_working_tree(self): - self.fail("""Find a good way to diff an index against the working tree -which is not possible with the current interface""") + diff --git a/test/git/test_index.py b/test/git/test_index.py index 5c643a67..257acf10 100644 --- a/test/git/test_index.py +++ b/test/git/test_index.py @@ -102,12 +102,45 @@ class TestTree(TestBase): assert num_blobs == len(three_way_index.entries) @with_rw_repo('0.1.6') - def test_from_index(self, rw_repo): + def test_from_index_and_diff(self, rw_repo): # default Index instance points to our index index = Index(rw_repo) + assert index.path is not None assert len(index.entries) # write the file back index.write() # could sha it, or check stats + + # test diff + # resetting the head will leave the index in a different state, and the + # diff will yield a few changes + cur_head_commit = rw_repo.head.commit + ref = rw_repo.head.reset(rw_repo, 'HEAD~6', index=True, working_tree=False) + + # diff against same index is 0 + diff = index.diff() + assert len(diff) == 0 + + # against HEAD as string, must be the same as it matches index + diff = index.diff('HEAD') + assert len(diff) == 0 + + # against previous head, there must be a difference + diff = index.diff(cur_head_commit) + assert len(diff) + + # we reverse the result + adiff = index.diff(str(cur_head_commit), R=True) + odiff = index.diff(cur_head_commit, R=False) # now its not reversed anymore + assert adiff != odiff + assert odiff == diff # both unreversed diffs against HEAD + + # against working copy - its still at cur_commit + wdiff = index.diff(None) + assert wdiff != adiff + assert wdiff != odiff + + # against something unusual + self.failUnlessRaises(ValueError, index.diff, int) -- cgit v1.2.1