diff options
| -rw-r--r-- | lib/git/exc.py | 4 | ||||
| m--------- | lib/git/ext/gitdb | 0 | ||||
| -rw-r--r-- | lib/git/objects/base.py | 11 | ||||
| -rw-r--r-- | lib/git/refs.py | 19 | ||||
| -rw-r--r-- | lib/git/repo.py | 165 | ||||
| -rw-r--r-- | test/git/test_refs.py | 824 | ||||
| -rw-r--r-- | test/git/test_repo.py | 151 | 
7 files changed, 735 insertions, 439 deletions
| diff --git a/lib/git/exc.py b/lib/git/exc.py index 93919d5e..d2cb8d7e 100644 --- a/lib/git/exc.py +++ b/lib/git/exc.py @@ -1,10 +1,12 @@ -# errors.py +# exc.py  # Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors  #  # This module is part of GitPython and is released under  # the BSD License: http://www.opensource.org/licenses/bsd-license.php  """ Module containing all exceptions thrown througout the git package, """ +from gitdb.exc import * +  class InvalidGitRepositoryError(Exception):  	""" Thrown if the given repository appears to have an invalid format.  """ diff --git a/lib/git/ext/gitdb b/lib/git/ext/gitdb -Subproject 6c8721a7d5d32e54bb4ffd3725ed23ac5d76a59 +Subproject 46bf4710e0f7184ac4875e8037de30b5081bfda diff --git a/lib/git/objects/base.py b/lib/git/objects/base.py index d4a46788..21b9b1ea 100644 --- a/lib/git/objects/base.py +++ b/lib/git/objects/base.py @@ -53,6 +53,17 @@ class Object(LazyMixin):  		inst = get_object_type_by_name(typename)(repo, hex_to_bin(hexsha))  		inst.size = size  		return inst +		 +	@classmethod +	def new_from_sha(cls, repo, sha1): +		""" +		:return: new object instance of a type appropriate to represent the given  +			binary sha1 +		:param sha1: 20 byte binary sha1""" +		oinfo = repo.odb.info(sha1) +		inst = get_object_type_by_name(oinfo.type)(repo, oinfo.binsha) +		inst.size = oinfo.size +		return inst   	def _set_self_from_args_(self, args_dict):  		"""Initialize attributes on self from the given dict that was retrieved diff --git a/lib/git/refs.py b/lib/git/refs.py index 343a0afb..a466e419 100644 --- a/lib/git/refs.py +++ b/lib/git/refs.py @@ -68,7 +68,7 @@ class SymbolicReference(object):  		:return:  			In case of symbolic references, the shortest assumable name   			is the path itself.""" -		return self.path	 +		return self.path  	def _abs_path(self):  		return join_path_native(self.repo.git_dir, self.path) @@ -109,6 +109,19 @@ class SymbolicReference(object):  		# I believe files are closing themselves on destruction, so it is   		# alright. +	@classmethod +	def dereference_recursive(cls, repo, ref_path): +		""" +		:return: hexsha stored in the reference at the given ref_path, recursively dereferencing all +			intermediate references as required +		:param repo: the repository containing the reference at ref_path""" +		while True: +			ref = cls(repo, ref_path) +			hexsha, ref_path = ref._get_ref_info() +			if hexsha is not None: +				return hexsha +		# END recursive dereferencing +		  	def _get_ref_info(self):  		"""Return: (sha, target_ref_path) if available, the sha the file at   		rela_path points to, or None. target_ref_path is the reference we  @@ -795,6 +808,10 @@ class TagReference(Reference):  			raise ValueError( "Tag %s points to a Blob or Tree - have never seen that before" % self )	  	@property +	def tree(self): +		return self.commit.tree + +	@property  	def tag(self):  		"""  		:return: Tag object this tag ref points to or None in case  diff --git a/lib/git/repo.py b/lib/git/repo.py index 62202364..8e97adee 100644 --- a/lib/git/repo.py +++ b/lib/git/repo.py @@ -12,12 +12,13 @@ from index import IndexFile  from objects import *  from config import GitConfigParser  from remote import Remote - +from string import digits  from db import (  				GitCmdObjectDB,   				GitDB  				) +from gitdb.exc import BadObject  from gitdb.util import (  							join,  							isdir,  @@ -70,6 +71,7 @@ class Repo(object):  	# precompiled regex  	re_whitespace = re.compile(r'\s+')  	re_hexsha_only = re.compile('^[0-9A-Fa-f]{40}$') +	re_hexsha_shortened = re.compile('^[0-9A-Fa-f]{7:40}$')  	re_author_committer_start = re.compile(r'^(author|committer)')  	re_tab_full_line = re.compile(r'^\t(.*)$') @@ -698,6 +700,167 @@ class Repo(object):  		self.git.archive(treeish, **kwargs)  		return self +		 +	def rev_parse(self, rev): +		""" +		:return: Object at the given revision, either Commit, Tag, Tree or Blob +		:param rev: git-rev-parse compatible revision specification, please see +			http://www.kernel.org/pub/software/scm/git/docs/git-rev-parse.html +			for details +		:note: Currently there is no access to the rev-log, rev-specs may only contain +			topological tokens such ~ and ^. +		:raise BadObject: if the given revision could not be found""" +		if '@' in rev: +			raise ValueError("There is no rev-log support yet") +		 +		 +		# colon search mode ? +		if rev.startswith(':/'): +			# colon search mode +			raise NotImplementedError("commit by message search ( regex )") +		# END handle search +		 +		# return object specified by the given name +		def name_to_object(name): +			hexsha = None +			 +			# is it a hexsha ? +			if self.re_hexsha_shortened.match(name): +				if len(name) != 40: +					# find long sha for short sha +					raise NotImplementedError("short sha parsing") +				else: +					hexsha = name +				# END handle short shas +			else: +				for base in ('%s', 'refs/%s', 'refs/tags/%s', 'refs/heads/%s', 'refs/remotes/%s', 'refs/remotes/%s/HEAD'): +					try: +						hexsha = SymbolicReference.dereference_recursive(self, base % name) +						break +					except ValueError: +						pass +				# END for each base +			# END handle hexsha +			 +			# tried everything ? fail +			if hexsha is None: +				raise BadObject(name) +			# END assert hexsha was found +			 +			return Object.new_from_sha(self, hex_to_bin(hexsha)) +		# END object by name +		 +		obj = None +		output_type = "commit" +		start = 0 +		parsed_to = 0 +		lr = len(rev) +		while start < lr and start != -1: +			if rev[start] not in "^~:": +				start += 1 +				continue +			# END handle start +			 +			if obj is None: +				# token is a rev name +				obj = name_to_object(rev[:start]) +			# END initialize obj on first token +			 +			token = rev[start] +			start += 1 +			 +			# try to parse {type} +			if start < lr and rev[start] == '{': +				end = rev.find('}', start) +				if end == -1: +					raise ValueError("Missing closing brace to define type in %s" % rev) +				output_type = rev[start+1:end]	# exclude brace +				 +				# handle type  +				if output_type == 'commit': +					pass # default +				elif output_type == 'tree': +					try: +						obj = obj.tree +					except AttributeError: +						pass	# error raised later +					# END exception handling +				elif output_type in ('', 'blob'): +					while True: +						try: +							obj = obj.object  +						except AttributeError: +							break +					# END dereference tag +				else: +					raise ValueError("Invalid output type: %s ( in %s )"  % (output_type, rev)) +				# END handle output type +				 +				if obj.type != output_type: +					raise ValueError("Could not accomodate requested object type %s, got %s" % (output_type, obj.type)) +				# END verify ouput type +				 +				start = end+1					# skip brace +				parsed_to = start +				continue +			# END parse type +			 +			# try to parse a number +			num = 0 +			if token != ":": +				while start < lr: +					if rev[start] in digits: +						num = num * 10 + int(rev[start]) +						start += 1 +					else: +						break +					# END handle number +				# END number parse loop +				 +				# no explicit number given, 1 is the default +				if num == 0: +					num = 1 +				# END set default num +			# END number parsing only if non-blob mode +			 +			 +			parsed_to = start +			# handle hiererarchy walk +			try: +				if token == "~": +					for item in xrange(num): +						obj = obj.parents[0] +					# END for each history item to walk +				elif token == "^": +					# must be n'th parent +					obj = obj.parents[num-1] +				elif token == ":": +					if obj.type != "tree": +						obj = obj.tree +					# END get tree type +					obj = obj[rev[start:]] +					parsed_to = lr +				else: +					raise "Invalid token: %r" % token +				# END end handle tag +			except (IndexError, AttributeError): +				raise BadObject("Invalid Revision") +			# END exception handling +		# END parse loop +		 +		# still no obj ? Its probably a simple name +		if obj is None: +			obj = name_to_object(rev) +			parsed_to = lr +		# END handle simple name +		 +		if obj is None: +			raise ValueError("Revision specifier could not be parsed: %s" % rev) + +		if parsed_to != lr: +			raise ValueError("Didn't consume complete rev spec %s, consumed part: %s" % (rev, rev[:parsed_to])) +		 +		return obj  	def __repr__(self):  		return '<git.Repo "%s">' % self.git_dir diff --git a/test/git/test_refs.py b/test/git/test_refs.py index 958bcf85..44a8ed95 100644 --- a/test/git/test_refs.py +++ b/test/git/test_refs.py @@ -14,413 +14,417 @@ import os  class TestRefs(TestBase): -    def test_from_path(self): -        # should be able to create any reference directly -        for ref_type in ( Reference, Head, TagReference, RemoteReference ): -            for name in ('rela_name', 'path/rela_name'): -                full_path = ref_type.to_full_path(name) -                instance = ref_type.from_path(self.rorepo, full_path) -                assert isinstance(instance, ref_type) -            # END for each name  -        # END for each type -     -    def test_tag_base(self): -        tag_object_refs = list() -        for tag in self.rorepo.tags: -            assert "refs/tags" in tag.path -            assert tag.name -            assert isinstance( tag.commit, Commit ) -            if tag.tag is not None: -                tag_object_refs.append( tag ) -                tagobj = tag.tag -                assert isinstance( tagobj, TagObject )  -                assert tagobj.tag == tag.name -                assert isinstance( tagobj.tagger, Actor ) -                assert isinstance( tagobj.tagged_date, int ) -                assert isinstance( tagobj.tagger_tz_offset, int ) -                assert tagobj.message -                assert tag.object == tagobj -                # can't assign the object -                self.failUnlessRaises(AttributeError, setattr, tag, 'object', tagobj) -            # END if we have a tag object -        # END for tag in repo-tags -        assert tag_object_refs -        assert isinstance(self.rorepo.tags['0.1.5'], TagReference) -         -    def test_tags(self): -        # tag refs can point to tag objects or to commits -        s = set() -        ref_count = 0 -        for ref in chain(self.rorepo.tags, self.rorepo.heads): -            ref_count += 1 -            assert isinstance(ref, refs.Reference) -            assert str(ref) == ref.name -            assert repr(ref) -            assert ref == ref -            assert not ref != ref -            s.add(ref) -        # END for each ref -        assert len(s) == ref_count -        assert len(s|s) == ref_count -         -    def test_heads(self): -        for head in self.rorepo.heads: -            assert head.name -            assert head.path -            assert "refs/heads" in head.path -            prev_object = head.object -            cur_object = head.object -            assert prev_object == cur_object        # represent the same git object -            assert prev_object is not cur_object    # but are different instances -        # END for each head -         -    def test_refs(self): -        types_found = set() -        for ref in self.rorepo.refs: -            types_found.add(type(ref)) -        assert len(types_found) == 3  -         -    def test_is_valid(self): -        assert Reference(self.rorepo, 'refs/doesnt/exist').is_valid() == False -        assert self.rorepo.head.is_valid() -        assert self.rorepo.head.reference.is_valid() -        assert SymbolicReference(self.rorepo, 'hellothere').is_valid() == False -         -    @with_rw_repo('0.1.6') -    def test_head_reset(self, rw_repo): -        cur_head = rw_repo.head -        new_head_commit = cur_head.ref.commit.parents[0] -        cur_head.reset(new_head_commit, index=True) # index only -        assert cur_head.reference.commit == new_head_commit -         -        self.failUnlessRaises(ValueError, cur_head.reset, new_head_commit, index=False, working_tree=True) -        new_head_commit = new_head_commit.parents[0] -        cur_head.reset(new_head_commit, index=True, working_tree=True)  # index + wt -        assert cur_head.reference.commit == new_head_commit -         -        # paths -        cur_head.reset(new_head_commit, paths = "lib") -         -         -        # now that we have a write write repo, change the HEAD reference - its  -        # like git-reset --soft -        heads = rw_repo.heads -        assert heads -        for head in heads: -            cur_head.reference = head -            assert cur_head.reference == head -            assert isinstance(cur_head.reference, Head) -            assert cur_head.commit == head.commit -            assert not cur_head.is_detached -        # END for each head -         -        # detach -        active_head = heads[0] -        curhead_commit = active_head.commit -        cur_head.reference = curhead_commit -        assert cur_head.commit == curhead_commit -        assert cur_head.is_detached -        self.failUnlessRaises(TypeError, getattr, cur_head, "reference") -         -        # tags are references, hence we can point to them -        some_tag = rw_repo.tags[0] -        cur_head.reference = some_tag -        assert not cur_head.is_detached -        assert cur_head.commit == some_tag.commit -        assert isinstance(cur_head.reference, TagReference)  -         -        # put HEAD back to a real head, otherwise everything else fails -        cur_head.reference = active_head -         -        # type check -        self.failUnlessRaises(ValueError, setattr, cur_head, "reference", "that") -         -        # head handling  -        commit = 'HEAD' -        prev_head_commit = cur_head.commit -        for count, new_name in enumerate(("my_new_head", "feature/feature1")): -            actual_commit = commit+"^"*count -            new_head = Head.create(rw_repo, new_name, actual_commit) -            assert cur_head.commit == prev_head_commit -            assert isinstance(new_head, Head) -            # already exists -            self.failUnlessRaises(GitCommandError, Head.create, rw_repo, new_name) -             -            # force it -            new_head = Head.create(rw_repo, new_name, actual_commit, force=True) -            old_path = new_head.path -            old_name = new_head.name -             -            assert new_head.rename("hello").name == "hello" -            assert new_head.rename("hello/world").name == "hello/world" -            assert new_head.rename(old_name).name == old_name and new_head.path == old_path -             -            # rename with force -            tmp_head = Head.create(rw_repo, "tmphead") -            self.failUnlessRaises(GitCommandError, tmp_head.rename, new_head) -            tmp_head.rename(new_head, force=True) -            assert tmp_head == new_head and tmp_head.object == new_head.object -             -            Head.delete(rw_repo, tmp_head) -            heads = rw_repo.heads -            assert tmp_head not in heads and new_head not in heads -            # force on deletion testing would be missing here, code looks okay though ;) -        # END for each new head name -        self.failUnlessRaises(TypeError, RemoteReference.create, rw_repo, "some_name")   -         -        # tag ref -        tag_name = "1.0.2" -        light_tag = TagReference.create(rw_repo, tag_name) -        self.failUnlessRaises(GitCommandError, TagReference.create, rw_repo, tag_name) -        light_tag = TagReference.create(rw_repo, tag_name, "HEAD~1", force = True) -        assert isinstance(light_tag, TagReference) -        assert light_tag.name == tag_name -        assert light_tag.commit == cur_head.commit.parents[0] -        assert light_tag.tag is None -         -        # tag with tag object -        other_tag_name = "releases/1.0.2RC" -        msg = "my mighty tag\nsecond line" -        obj_tag = TagReference.create(rw_repo, other_tag_name, message=msg) -        assert isinstance(obj_tag, TagReference) -        assert obj_tag.name == other_tag_name -        assert obj_tag.commit == cur_head.commit -        assert obj_tag.tag is not None -         -        TagReference.delete(rw_repo, light_tag, obj_tag) -        tags = rw_repo.tags -        assert light_tag not in tags and obj_tag not in tags -         -        # remote deletion -        remote_refs_so_far = 0 -        remotes = rw_repo.remotes  -        assert remotes -        for remote in remotes: -            refs = remote.refs -            RemoteReference.delete(rw_repo, *refs) -            remote_refs_so_far += len(refs) -        # END for each ref to delete -        assert remote_refs_so_far -         -        for remote in remotes: -            # remotes without references throw -            self.failUnlessRaises(AssertionError, getattr, remote, 'refs') -        # END for each remote -         -        # change where the active head points to -        if cur_head.is_detached: -            cur_head.reference = rw_repo.heads[0] -         -        head = cur_head.reference -        old_commit = head.commit -        head.commit = old_commit.parents[0] -        assert head.commit == old_commit.parents[0] -        assert head.commit == cur_head.commit -        head.commit = old_commit -         -        # setting a non-commit as commit fails, but succeeds as object -        head_tree = head.commit.tree -        self.failUnlessRaises(ValueError, setattr, head, 'commit', head_tree) -        assert head.commit == old_commit        # and the ref did not change -        self.failUnlessRaises(GitCommandError, setattr, head, 'object', head_tree) -         -        # set the commit directly using the head. This would never detach the head -        assert not cur_head.is_detached -        head.object = old_commit -        cur_head.reference = head.commit -        assert cur_head.is_detached -        parent_commit = head.commit.parents[0] -        assert cur_head.is_detached -        cur_head.commit = parent_commit -        assert cur_head.is_detached and cur_head.commit == parent_commit -         -        cur_head.reference = head -        assert not cur_head.is_detached -        cur_head.commit = parent_commit -        assert not cur_head.is_detached -        assert head.commit == parent_commit -         -        # test checkout -        active_branch = rw_repo.active_branch -        for head in rw_repo.heads: -            checked_out_head = head.checkout() -            assert checked_out_head == head -        # END for each head to checkout -         -        # checkout with branch creation -        new_head = active_branch.checkout(b="new_head") -        assert active_branch != rw_repo.active_branch -        assert new_head == rw_repo.active_branch -         -        # checkout  with force as we have a changed a file -        # clear file -        open(new_head.commit.tree.blobs[-1].abspath,'w').close() -        assert len(new_head.commit.diff(None)) -         -        # create a new branch that is likely to touch the file we changed -        far_away_head = rw_repo.create_head("far_head",'HEAD~100') -        self.failUnlessRaises(GitCommandError, far_away_head.checkout) -        assert active_branch == active_branch.checkout(force=True) -        assert rw_repo.head.reference != far_away_head -         -        # test reference creation -        partial_ref = 'sub/ref' -        full_ref = 'refs/%s' % partial_ref -        ref = Reference.create(rw_repo, partial_ref) -        assert ref.path == full_ref -        assert ref.object == rw_repo.head.commit -         -        self.failUnlessRaises(OSError, Reference.create, rw_repo, full_ref, 'HEAD~20') -        # it works if it is at the same spot though and points to the same reference -        assert Reference.create(rw_repo, full_ref, 'HEAD').path == full_ref -        Reference.delete(rw_repo, full_ref) -         -        # recreate the reference using a full_ref -        ref = Reference.create(rw_repo, full_ref) -        assert ref.path == full_ref -        assert ref.object == rw_repo.head.commit -         -        # recreate using force -        ref = Reference.create(rw_repo, partial_ref, 'HEAD~1', force=True) -        assert ref.path == full_ref -        assert ref.object == rw_repo.head.commit.parents[0] -         -        # rename it -        orig_obj = ref.object -        for name in ('refs/absname', 'rela_name', 'feature/rela_name'): -            ref_new_name = ref.rename(name) -            assert isinstance(ref_new_name, Reference) -            assert name in ref_new_name.path -            assert ref_new_name.object == orig_obj -            assert ref_new_name == ref -        # END for each name type -         -        # References that don't exist trigger an error if we want to access them -        self.failUnlessRaises(ValueError, getattr, Reference(rw_repo, "refs/doesntexist"), 'commit') -         -        # exists, fail unless we force -        ex_ref_path = far_away_head.path -        self.failUnlessRaises(OSError, ref.rename, ex_ref_path) -        # if it points to the same commit it works -        far_away_head.commit = ref.commit -        ref.rename(ex_ref_path) -        assert ref.path == ex_ref_path and ref.object == orig_obj -        assert ref.rename(ref.path).path == ex_ref_path # rename to same name -         -        # create symbolic refs -        symref_path = "symrefs/sym" -        symref = SymbolicReference.create(rw_repo, symref_path, cur_head.reference) -        assert symref.path == symref_path -        assert symref.reference == cur_head.reference -         -        self.failUnlessRaises(OSError, SymbolicReference.create, rw_repo, symref_path, cur_head.reference.commit) -        # it works if the new ref points to the same reference  -        SymbolicReference.create(rw_repo, symref.path, symref.reference).path == symref.path -        SymbolicReference.delete(rw_repo, symref) -        # would raise if the symref wouldn't have been deletedpbl -        symref = SymbolicReference.create(rw_repo, symref_path, cur_head.reference) -         -        # test symbolic references which are not at default locations like HEAD -        # or FETCH_HEAD - they may also be at spots in refs of course -        symbol_ref_path = "refs/symbol_ref" -        symref = SymbolicReference(rw_repo, symbol_ref_path) -        assert symref.path == symbol_ref_path -        symbol_ref_abspath = os.path.join(rw_repo.git_dir, symref.path) -         -        # set it -        symref.reference = new_head -        assert symref.reference == new_head -        assert os.path.isfile(symbol_ref_abspath) -        assert symref.commit == new_head.commit -         -        for name in ('absname','folder/rela_name'): -            symref_new_name = symref.rename(name) -            assert isinstance(symref_new_name, SymbolicReference) -            assert name in symref_new_name.path -            assert symref_new_name.reference == new_head -            assert symref_new_name == symref -            assert not symref.is_detached -        # END for each ref -         -        # create a new non-head ref just to be sure we handle it even if packed -        Reference.create(rw_repo, full_ref) -         -        # test ref listing - assure we have packed refs -        rw_repo.git.pack_refs(all=True, prune=True) -        heads = rw_repo.heads -        assert heads -        assert new_head in heads -        assert active_branch in heads -        assert rw_repo.tags -         -        # we should be able to iterate all symbolic refs as well - in that case -        # we should expect only symbolic references to be returned -        for symref in SymbolicReference.iter_items(rw_repo): -            assert not symref.is_detached -             -        # when iterating references, we can get references and symrefs -        # when deleting all refs, I'd expect them to be gone ! Even from  -        # the packed ones -        # For this to work, we must not be on any branch -        rw_repo.head.reference = rw_repo.head.commit -        deleted_refs = set() -        for ref in Reference.iter_items(rw_repo): -            if ref.is_detached: -                ref.delete(rw_repo, ref) -                deleted_refs.add(ref) -            # END delete ref -        # END for each ref to iterate and to delete -        assert deleted_refs -         -        for ref in Reference.iter_items(rw_repo): -            if ref.is_detached: -                assert ref not in deleted_refs -        # END for each ref -         -        # reattach head - head will not be returned if it is not a symbolic  -        # ref -        rw_repo.head.reference = Head.create(rw_repo, "master") -         -        # At least the head should still exist -        assert os.path.isfile(os.path.join(rw_repo.git_dir, 'HEAD')) -        refs = list(SymbolicReference.iter_items(rw_repo)) -        assert len(refs) == 1 -         -         -        # test creation of new refs from scratch -        for path in ("basename", "dir/somename", "dir2/subdir/basename"): -            # REFERENCES  -            ############ -            fpath = Reference.to_full_path(path) -            ref_fp = Reference.from_path(rw_repo, fpath) -            assert not ref_fp.is_valid() -            ref = Reference(rw_repo, fpath) -            assert ref == ref_fp -             -            # can be created by assigning a commit -            ref.commit = rw_repo.head.commit -            assert ref.is_valid() -             -            # if the assignment raises, the ref doesn't exist -            Reference.delete(ref.repo, ref.path) -            assert not ref.is_valid() -            self.failUnlessRaises(ValueError, setattr, ref, 'commit', "nonsense") -            assert not ref.is_valid() -             -            # I am sure I had my reason to make it a class method at first, but -            # now it doesn't make so much sense anymore, want an instance method as well -            # See http://byronimo.lighthouseapp.com/projects/51787-gitpython/tickets/27 -            Reference.delete(ref.repo, ref.path) -            assert not ref.is_valid() -             -            ref.object = rw_repo.head.commit -            assert ref.is_valid() -             -            Reference.delete(ref.repo, ref.path) -            assert not ref.is_valid() -            self.failUnlessRaises(GitCommandError, setattr, ref, 'object', "nonsense") -            assert not ref.is_valid() -             -        # END for each path -         -         +	def test_from_path(self): +		# should be able to create any reference directly +		for ref_type in ( Reference, Head, TagReference, RemoteReference ): +			for name in ('rela_name', 'path/rela_name'): +				full_path = ref_type.to_full_path(name) +				instance = ref_type.from_path(self.rorepo, full_path) +				assert isinstance(instance, ref_type) +			# END for each name  +		# END for each type +	 +	def test_tag_base(self): +		tag_object_refs = list() +		for tag in self.rorepo.tags: +			assert "refs/tags" in tag.path +			assert tag.name +			assert isinstance( tag.commit, Commit ) +			if tag.tag is not None: +				tag_object_refs.append( tag ) +				tagobj = tag.tag +				assert isinstance( tagobj, TagObject )  +				assert tagobj.tag == tag.name +				assert isinstance( tagobj.tagger, Actor ) +				assert isinstance( tagobj.tagged_date, int ) +				assert isinstance( tagobj.tagger_tz_offset, int ) +				assert tagobj.message +				assert tag.object == tagobj +				assert tag.tree.type == 'tree' +				assert tag.tree == tag.commit.tree +				# can't assign the object +				self.failUnlessRaises(AttributeError, setattr, tag, 'object', tagobj) +			# END if we have a tag object +		# END for tag in repo-tags +		assert tag_object_refs +		assert isinstance(self.rorepo.tags['0.1.5'], TagReference) +		 +	def test_tags(self): +		# tag refs can point to tag objects or to commits +		s = set() +		ref_count = 0 +		for ref in chain(self.rorepo.tags, self.rorepo.heads): +			ref_count += 1 +			assert isinstance(ref, refs.Reference) +			assert str(ref) == ref.name +			assert repr(ref) +			assert ref == ref +			assert not ref != ref +			s.add(ref) +		# END for each ref +		assert len(s) == ref_count +		assert len(s|s) == ref_count +		 +	def test_heads(self): +		for head in self.rorepo.heads: +			assert head.name +			assert head.path +			assert "refs/heads" in head.path +			prev_object = head.object +			cur_object = head.object +			assert prev_object == cur_object		# represent the same git object +			assert prev_object is not cur_object	# but are different instances +		# END for each head +		 +	def test_refs(self): +		types_found = set() +		for ref in self.rorepo.refs: +			types_found.add(type(ref)) +		assert len(types_found) == 3  +		 +	def test_is_valid(self): +		assert Reference(self.rorepo, 'refs/doesnt/exist').is_valid() == False +		assert self.rorepo.head.is_valid() +		assert self.rorepo.head.reference.is_valid() +		assert SymbolicReference(self.rorepo, 'hellothere').is_valid() == False +		 +	@with_rw_repo('0.1.6') +	def test_head_reset(self, rw_repo): +		cur_head = rw_repo.head +		new_head_commit = cur_head.ref.commit.parents[0] +		cur_head.reset(new_head_commit, index=True) # index only +		assert cur_head.reference.commit == new_head_commit +		 +		self.failUnlessRaises(ValueError, cur_head.reset, new_head_commit, index=False, working_tree=True) +		new_head_commit = new_head_commit.parents[0] +		cur_head.reset(new_head_commit, index=True, working_tree=True)	# index + wt +		assert cur_head.reference.commit == new_head_commit +		 +		# paths +		cur_head.reset(new_head_commit, paths = "lib") +		 +		 +		# now that we have a write write repo, change the HEAD reference - its  +		# like git-reset --soft +		heads = rw_repo.heads +		assert heads +		for head in heads: +			cur_head.reference = head +			assert cur_head.reference == head +			assert isinstance(cur_head.reference, Head) +			assert cur_head.commit == head.commit +			assert not cur_head.is_detached +		# END for each head +		 +		# detach +		active_head = heads[0] +		curhead_commit = active_head.commit +		cur_head.reference = curhead_commit +		assert cur_head.commit == curhead_commit +		assert cur_head.is_detached +		self.failUnlessRaises(TypeError, getattr, cur_head, "reference") +		 +		# tags are references, hence we can point to them +		some_tag = rw_repo.tags[0] +		cur_head.reference = some_tag +		assert not cur_head.is_detached +		assert cur_head.commit == some_tag.commit +		assert isinstance(cur_head.reference, TagReference)  +		 +		# put HEAD back to a real head, otherwise everything else fails +		cur_head.reference = active_head +		 +		# type check +		self.failUnlessRaises(ValueError, setattr, cur_head, "reference", "that") +		 +		# head handling  +		commit = 'HEAD' +		prev_head_commit = cur_head.commit +		for count, new_name in enumerate(("my_new_head", "feature/feature1")): +			actual_commit = commit+"^"*count +			new_head = Head.create(rw_repo, new_name, actual_commit) +			assert cur_head.commit == prev_head_commit +			assert isinstance(new_head, Head) +			# already exists +			self.failUnlessRaises(GitCommandError, Head.create, rw_repo, new_name) +			 +			# force it +			new_head = Head.create(rw_repo, new_name, actual_commit, force=True) +			old_path = new_head.path +			old_name = new_head.name +			 +			assert new_head.rename("hello").name == "hello" +			assert new_head.rename("hello/world").name == "hello/world" +			assert new_head.rename(old_name).name == old_name and new_head.path == old_path +			 +			# rename with force +			tmp_head = Head.create(rw_repo, "tmphead") +			self.failUnlessRaises(GitCommandError, tmp_head.rename, new_head) +			tmp_head.rename(new_head, force=True) +			assert tmp_head == new_head and tmp_head.object == new_head.object +			 +			Head.delete(rw_repo, tmp_head) +			heads = rw_repo.heads +			assert tmp_head not in heads and new_head not in heads +			# force on deletion testing would be missing here, code looks okay though ;) +		# END for each new head name +		self.failUnlessRaises(TypeError, RemoteReference.create, rw_repo, "some_name")	 +		 +		# tag ref +		tag_name = "1.0.2" +		light_tag = TagReference.create(rw_repo, tag_name) +		self.failUnlessRaises(GitCommandError, TagReference.create, rw_repo, tag_name) +		light_tag = TagReference.create(rw_repo, tag_name, "HEAD~1", force = True) +		assert isinstance(light_tag, TagReference) +		assert light_tag.name == tag_name +		assert light_tag.commit == cur_head.commit.parents[0] +		assert light_tag.tag is None +		 +		# tag with tag object +		other_tag_name = "releases/1.0.2RC" +		msg = "my mighty tag\nsecond line" +		obj_tag = TagReference.create(rw_repo, other_tag_name, message=msg) +		assert isinstance(obj_tag, TagReference) +		assert obj_tag.name == other_tag_name +		assert obj_tag.commit == cur_head.commit +		assert obj_tag.tag is not None +		 +		TagReference.delete(rw_repo, light_tag, obj_tag) +		tags = rw_repo.tags +		assert light_tag not in tags and obj_tag not in tags +		 +		# remote deletion +		remote_refs_so_far = 0 +		remotes = rw_repo.remotes  +		assert remotes +		for remote in remotes: +			refs = remote.refs +			RemoteReference.delete(rw_repo, *refs) +			remote_refs_so_far += len(refs) +		# END for each ref to delete +		assert remote_refs_so_far +		 +		for remote in remotes: +			# remotes without references throw +			self.failUnlessRaises(AssertionError, getattr, remote, 'refs') +		# END for each remote +		 +		# change where the active head points to +		if cur_head.is_detached: +			cur_head.reference = rw_repo.heads[0] +		 +		head = cur_head.reference +		old_commit = head.commit +		head.commit = old_commit.parents[0] +		assert head.commit == old_commit.parents[0] +		assert head.commit == cur_head.commit +		head.commit = old_commit +		 +		# setting a non-commit as commit fails, but succeeds as object +		head_tree = head.commit.tree +		self.failUnlessRaises(ValueError, setattr, head, 'commit', head_tree) +		assert head.commit == old_commit		# and the ref did not change +		self.failUnlessRaises(GitCommandError, setattr, head, 'object', head_tree) +		 +		# set the commit directly using the head. This would never detach the head +		assert not cur_head.is_detached +		head.object = old_commit +		cur_head.reference = head.commit +		assert cur_head.is_detached +		parent_commit = head.commit.parents[0] +		assert cur_head.is_detached +		cur_head.commit = parent_commit +		assert cur_head.is_detached and cur_head.commit == parent_commit +		 +		cur_head.reference = head +		assert not cur_head.is_detached +		cur_head.commit = parent_commit +		assert not cur_head.is_detached +		assert head.commit == parent_commit +		 +		# test checkout +		active_branch = rw_repo.active_branch +		for head in rw_repo.heads: +			checked_out_head = head.checkout() +			assert checked_out_head == head +		# END for each head to checkout +		 +		# checkout with branch creation +		new_head = active_branch.checkout(b="new_head") +		assert active_branch != rw_repo.active_branch +		assert new_head == rw_repo.active_branch +		 +		# checkout	with force as we have a changed a file +		# clear file +		open(new_head.commit.tree.blobs[-1].abspath,'w').close() +		assert len(new_head.commit.diff(None)) +		 +		# create a new branch that is likely to touch the file we changed +		far_away_head = rw_repo.create_head("far_head",'HEAD~100') +		self.failUnlessRaises(GitCommandError, far_away_head.checkout) +		assert active_branch == active_branch.checkout(force=True) +		assert rw_repo.head.reference != far_away_head +		 +		# test reference creation +		partial_ref = 'sub/ref' +		full_ref = 'refs/%s' % partial_ref +		ref = Reference.create(rw_repo, partial_ref) +		assert ref.path == full_ref +		assert ref.object == rw_repo.head.commit +		 +		self.failUnlessRaises(OSError, Reference.create, rw_repo, full_ref, 'HEAD~20') +		# it works if it is at the same spot though and points to the same reference +		assert Reference.create(rw_repo, full_ref, 'HEAD').path == full_ref +		Reference.delete(rw_repo, full_ref) +		 +		# recreate the reference using a full_ref +		ref = Reference.create(rw_repo, full_ref) +		assert ref.path == full_ref +		assert ref.object == rw_repo.head.commit +		 +		# recreate using force +		ref = Reference.create(rw_repo, partial_ref, 'HEAD~1', force=True) +		assert ref.path == full_ref +		assert ref.object == rw_repo.head.commit.parents[0] +		 +		# rename it +		orig_obj = ref.object +		for name in ('refs/absname', 'rela_name', 'feature/rela_name'): +			ref_new_name = ref.rename(name) +			assert isinstance(ref_new_name, Reference) +			assert name in ref_new_name.path +			assert ref_new_name.object == orig_obj +			assert ref_new_name == ref +		# END for each name type +		 +		# References that don't exist trigger an error if we want to access them +		self.failUnlessRaises(ValueError, getattr, Reference(rw_repo, "refs/doesntexist"), 'commit') +		 +		# exists, fail unless we force +		ex_ref_path = far_away_head.path +		self.failUnlessRaises(OSError, ref.rename, ex_ref_path) +		# if it points to the same commit it works +		far_away_head.commit = ref.commit +		ref.rename(ex_ref_path) +		assert ref.path == ex_ref_path and ref.object == orig_obj +		assert ref.rename(ref.path).path == ex_ref_path # rename to same name +		 +		# create symbolic refs +		symref_path = "symrefs/sym" +		symref = SymbolicReference.create(rw_repo, symref_path, cur_head.reference) +		assert symref.path == symref_path +		assert symref.reference == cur_head.reference +		 +		self.failUnlessRaises(OSError, SymbolicReference.create, rw_repo, symref_path, cur_head.reference.commit) +		# it works if the new ref points to the same reference  +		SymbolicReference.create(rw_repo, symref.path, symref.reference).path == symref.path +		SymbolicReference.delete(rw_repo, symref) +		# would raise if the symref wouldn't have been deletedpbl +		symref = SymbolicReference.create(rw_repo, symref_path, cur_head.reference) +		 +		# test symbolic references which are not at default locations like HEAD +		# or FETCH_HEAD - they may also be at spots in refs of course +		symbol_ref_path = "refs/symbol_ref" +		symref = SymbolicReference(rw_repo, symbol_ref_path) +		assert symref.path == symbol_ref_path +		symbol_ref_abspath = os.path.join(rw_repo.git_dir, symref.path) +		 +		# set it +		symref.reference = new_head +		assert symref.reference == new_head +		assert os.path.isfile(symbol_ref_abspath) +		assert symref.commit == new_head.commit +		 +		for name in ('absname','folder/rela_name'): +			symref_new_name = symref.rename(name) +			assert isinstance(symref_new_name, SymbolicReference) +			assert name in symref_new_name.path +			assert symref_new_name.reference == new_head +			assert symref_new_name == symref +			assert not symref.is_detached +		# END for each ref +		 +		# create a new non-head ref just to be sure we handle it even if packed +		Reference.create(rw_repo, full_ref) +		 +		# test ref listing - assure we have packed refs +		rw_repo.git.pack_refs(all=True, prune=True) +		heads = rw_repo.heads +		assert heads +		assert new_head in heads +		assert active_branch in heads +		assert rw_repo.tags +		 +		# we should be able to iterate all symbolic refs as well - in that case +		# we should expect only symbolic references to be returned +		for symref in SymbolicReference.iter_items(rw_repo): +			assert not symref.is_detached +			 +		# when iterating references, we can get references and symrefs +		# when deleting all refs, I'd expect them to be gone ! Even from  +		# the packed ones +		# For this to work, we must not be on any branch +		rw_repo.head.reference = rw_repo.head.commit +		deleted_refs = set() +		for ref in Reference.iter_items(rw_repo): +			if ref.is_detached: +				ref.delete(rw_repo, ref) +				deleted_refs.add(ref) +			# END delete ref +		# END for each ref to iterate and to delete +		assert deleted_refs +		 +		for ref in Reference.iter_items(rw_repo): +			if ref.is_detached: +				assert ref not in deleted_refs +		# END for each ref +		 +		# reattach head - head will not be returned if it is not a symbolic  +		# ref +		rw_repo.head.reference = Head.create(rw_repo, "master") +		 +		# At least the head should still exist +		assert os.path.isfile(os.path.join(rw_repo.git_dir, 'HEAD')) +		refs = list(SymbolicReference.iter_items(rw_repo)) +		assert len(refs) == 1 +		 +		 +		# test creation of new refs from scratch +		for path in ("basename", "dir/somename", "dir2/subdir/basename"): +			# REFERENCES  +			############ +			fpath = Reference.to_full_path(path) +			ref_fp = Reference.from_path(rw_repo, fpath) +			assert not ref_fp.is_valid() +			ref = Reference(rw_repo, fpath) +			assert ref == ref_fp +			 +			# can be created by assigning a commit +			ref.commit = rw_repo.head.commit +			assert ref.is_valid() +			 +			# if the assignment raises, the ref doesn't exist +			Reference.delete(ref.repo, ref.path) +			assert not ref.is_valid() +			self.failUnlessRaises(ValueError, setattr, ref, 'commit', "nonsense") +			assert not ref.is_valid() +			 +			# I am sure I had my reason to make it a class method at first, but +			# now it doesn't make so much sense anymore, want an instance method as well +			# See http://byronimo.lighthouseapp.com/projects/51787-gitpython/tickets/27 +			Reference.delete(ref.repo, ref.path) +			assert not ref.is_valid() +			 +			ref.object = rw_repo.head.commit +			assert ref.is_valid() +			 +			Reference.delete(ref.repo, ref.path) +			assert not ref.is_valid() +			self.failUnlessRaises(GitCommandError, setattr, ref, 'object', "nonsense") +			assert not ref.is_valid() +			 +		# END for each path +		 +	def test_dereference_recursive(self): +		# for now, just test the HEAD +		assert SymbolicReference.dereference_recursive(self.rorepo, 'HEAD') diff --git a/test/git/test_repo.py b/test/git/test_repo.py index 11c7c2e6..f1609266 100644 --- a/test/git/test_repo.py +++ b/test/git/test_repo.py @@ -11,34 +11,35 @@ from git.util import join_path_native  import tempfile  import shutil  from cStringIO import StringIO +from git.exc import BadObject  class TestRepo(TestBase):  	@raises(InvalidGitRepositoryError) -	def test_new_should_raise_on_invalid_repo_location(self): +	def _test_new_should_raise_on_invalid_repo_location(self):  		Repo(tempfile.gettempdir())  	@raises(NoSuchPathError) -	def test_new_should_raise_on_non_existant_path(self): +	def _test_new_should_raise_on_non_existant_path(self):  		Repo("repos/foobar") -	def test_repo_creation_from_different_paths(self): +	def _test_repo_creation_from_different_paths(self):  		r_from_gitdir = Repo(self.rorepo.git_dir)  		assert r_from_gitdir.git_dir == self.rorepo.git_dir  		assert r_from_gitdir.git_dir.endswith('.git')  		assert not self.rorepo.git.working_dir.endswith('.git')  		assert r_from_gitdir.git.working_dir == self.rorepo.git.working_dir -	def test_description(self): +	def _test_description(self):  		txt = "Test repository"  		self.rorepo.description = txt  		assert_equal(self.rorepo.description, txt) -	def test_heads_should_return_array_of_head_objects(self): +	def _test_heads_should_return_array_of_head_objects(self):  		for head in self.rorepo.heads:  			assert_equal(Head, head.__class__) -	def test_heads_should_populate_head_data(self): +	def _test_heads_should_populate_head_data(self):  		for head in self.rorepo.heads:  			assert head.name  			assert isinstance(head.commit,Commit) @@ -47,7 +48,7 @@ class TestRepo(TestBase):  		assert isinstance(self.rorepo.heads.master, Head)  		assert isinstance(self.rorepo.heads['master'], Head) -	def test_tree_from_revision(self): +	def _test_tree_from_revision(self):  		tree = self.rorepo.tree('0.1.6')  		assert len(tree.hexsha) == 40   		assert tree.type == "tree" @@ -56,7 +57,7 @@ class TestRepo(TestBase):  		# try from invalid revision that does not exist  		self.failUnlessRaises(ValueError, self.rorepo.tree, 'hello world') -	def test_commits(self): +	def _test_commits(self):  		mc = 10  		commits = list(self.rorepo.iter_commits('0.1.6', max_count=mc))  		assert len(commits) == mc @@ -78,7 +79,7 @@ class TestRepo(TestBase):  		c = commits[1]  		assert isinstance(c.parents, tuple) -	def test_trees(self): +	def _test_trees(self):  		mc = 30  		num_trees = 0  		for tree in self.rorepo.iter_trees('0.1.5', max_count=mc): @@ -116,7 +117,7 @@ class TestRepo(TestBase):  		# END test repos with working tree -	def test_init(self): +	def _test_init(self):  		prev_cwd = os.getcwd()  		os.chdir(tempfile.gettempdir())  		git_dir_rela = "repos/foo/bar.git" @@ -161,17 +162,17 @@ class TestRepo(TestBase):  			os.chdir(prev_cwd)  		# END restore previous state -	def test_bare_property(self): +	def _test_bare_property(self):  		self.rorepo.bare -	def test_daemon_export(self): +	def _test_daemon_export(self):  		orig_val = self.rorepo.daemon_export  		self.rorepo.daemon_export = not orig_val  		assert self.rorepo.daemon_export == ( not orig_val )  		self.rorepo.daemon_export = orig_val  		assert self.rorepo.daemon_export == orig_val -	def test_alternates(self): +	def _test_alternates(self):  		cur_alternates = self.rorepo.alternates  		# empty alternates  		self.rorepo.alternates = [] @@ -181,15 +182,15 @@ class TestRepo(TestBase):  		assert alts == self.rorepo.alternates  		self.rorepo.alternates = cur_alternates -	def test_repr(self): +	def _test_repr(self):  		path = os.path.join(os.path.abspath(GIT_REPO), '.git')  		assert_equal('<git.Repo "%s">' % path, repr(self.rorepo)) -	def test_is_dirty_with_bare_repository(self): +	def _test_is_dirty_with_bare_repository(self):  		self.rorepo._bare = True  		assert_false(self.rorepo.is_dirty()) -	def test_is_dirty(self): +	def _test_is_dirty(self):  		self.rorepo._bare = False  		for index in (0,1):  			for working_tree in (0,1): @@ -201,23 +202,23 @@ class TestRepo(TestBase):  		self.rorepo._bare = True  		assert self.rorepo.is_dirty() == False -	def test_head(self): +	def _test_head(self):  		assert self.rorepo.head.reference.object == self.rorepo.active_branch.object -	def test_index(self): +	def _test_index(self):  		index = self.rorepo.index  		assert isinstance(index, IndexFile) -	def test_tag(self): +	def _test_tag(self):  		assert self.rorepo.tag('refs/tags/0.1.5').commit -	def test_archive(self): +	def _test_archive(self):  		tmpfile = os.tmpfile()  		self.rorepo.archive(tmpfile, '0.1.5')  		assert tmpfile.tell()  	@patch_object(Git, '_call_process') -	def test_should_display_blame_information(self, git): +	def _test_should_display_blame_information(self, git):  		git.return_value = fixture('blame')  		b = self.rorepo.blame( 'master', 'lib/git.py')  		assert_equal(13, len(b)) @@ -243,7 +244,7 @@ class TestRepo(TestBase):  		assert_true( isinstance( tlist[0], basestring ) )  		assert_true( len( tlist ) < sum( len(t) for t in tlist ) )				 # test for single-char bug -	def test_untracked_files(self): +	def _test_untracked_files(self):  		base = self.rorepo.working_tree_dir  		files = (	join_path_native(base, "__test_myfile"),   					join_path_native(base, "__test_other_file") ) @@ -269,7 +270,7 @@ class TestRepo(TestBase):  		assert len(self.rorepo.untracked_files) == (num_recently_untracked - len(files)) -	def test_config_reader(self): +	def _test_config_reader(self):  		reader = self.rorepo.config_reader()				# all config files   		assert reader.read_only  		reader = self.rorepo.config_reader("repository")	# single config file @@ -286,7 +287,7 @@ class TestRepo(TestBase):  				pass   		# END for each config level  -	def test_creation_deletion(self): +	def _test_creation_deletion(self):  		# just a very quick test to assure it generally works. There are   		# specialized cases in the test_refs module  		head = self.rorepo.create_head("new_head", "HEAD~1") @@ -298,12 +299,12 @@ class TestRepo(TestBase):  		remote = self.rorepo.create_remote("new_remote", "git@server:repo.git")  		self.rorepo.delete_remote(remote) -	def test_comparison_and_hash(self): +	def _test_comparison_and_hash(self):  		# this is only a preliminary test, more testing done in test_index  		assert self.rorepo == self.rorepo and not (self.rorepo != self.rorepo)  		assert len(set((self.rorepo, self.rorepo))) == 1 -	def test_git_cmd(self): +	def _test_git_cmd(self):  		# test CatFileContentStream, just to be very sure we have no fencepost errors  		# last \n is the terminating newline that it expects  		l1 = "0123456789\n" @@ -376,3 +377,101 @@ class TestRepo(TestBase):  		assert s._stream.tell() == 2  		assert s.read() == l1[2:ts]  		assert s._stream.tell() == ts+1 +		 +	def _assert_rev_parse_types(self, name, rev_obj): +		rev_parse = self.rorepo.rev_parse +		 +		# tree and blob type +		obj = rev_parse(name + '^{tree}') +		assert obj == rev_obj.tree +		 +		obj = rev_parse(name + ':CHANGES') +		assert obj.type == 'blob' and obj.path == 'CHANGES' +		assert rev_obj.tree['CHANGES'] == obj +			 +		 +	def _assert_rev_parse(self, name): +		"""tries multiple different rev-parse syntaxes with the given name +		:return: parsed object""" +		rev_parse = self.rorepo.rev_parse +		obj = rev_parse(name) +		 +		# try history +		rev = name + "~" +		obj2 = rev_parse(rev) +		assert obj2 == obj.parents[0] +		self._assert_rev_parse_types(rev, obj2) +		 +		# history with number +		ni = 11 +		history = list() +		citer = obj.traverse() +		for pn in range(ni): +			history.append(citer.next()) +		# END get given amount of commits +		 +		for pn in range(11): +			rev = name + "~%i" % (pn+1) +			obj2 = rev_parse(rev) +			assert obj2 == history[pn] +			self._assert_rev_parse_types(rev, obj2) +		# END history check +		 +		# parent ( default ) +		rev = name + "^" +		obj2 = rev_parse(rev) +		assert obj2 == obj.parents[0] +		self._assert_rev_parse_types(rev, obj2) +		 +		# parent with number +		for pn, parent in enumerate(obj.parents): +			rev = name + "^%i" % (pn+1) +			assert rev_parse(rev) == parent +			self._assert_rev_parse_types(rev, obj2) +		# END for each parent +		 +		return obj +		 +	def test_rev_parse(self): +		rev_parse = self.rorepo.rev_parse +		 +		# start from reference +		num_resolved = 0 +		for ref in Reference.iter_items(self.rorepo): +			path_tokens = ref.path.split("/") +			for pt in range(len(path_tokens)): +				path_section = '/'.join(path_tokens[-(pt+1):])  +				try: +					obj = self._assert_rev_parse(path_section) +					assert obj.type == ref.object.type +					num_resolved += 1 +				except BadObject: +					print "failed on %s" % path_section +					raise +					# is fine, in case we have something like 112, which belongs to remotes/rname/merge-requests/112 +					pass +				# END exception handling +			# END for each token +		# END for each reference +		assert num_resolved +		 +		# try full sha directly ( including type conversion ) +		 +		 +		# multiple tree types result in the same tree: HEAD^{tree}^{tree}:CHANGES +		 +		# try to get parents from first revision - it should fail as no such revision +		# exists +		 +		# todo: dereference tag into a blob 0.1.7^{blob} - quite a special one +		 +		# dereference tag using ^{} notation +		 +		# missing closing brace commit^{tree +		 +		# missing starting brace +		 +		# not enough parents ^10 +		 +		# cannot handle rev-log for now  +		self.failUnlessRaises(ValueError, rev_parse, "hi@there") | 
