diff options
-rw-r--r-- | git/db/cmd/base.py | 31 | ||||
-rw-r--r-- | git/refs/reference.py | 51 | ||||
-rw-r--r-- | git/refs/remote.py | 20 | ||||
-rw-r--r-- | git/refs/symbolic.py | 1 | ||||
-rw-r--r-- | git/test/db/cmd/test_base.py | 63 | ||||
-rw-r--r-- | git/test/refs/test_refs.py | 5 | ||||
-rw-r--r-- | git/test/test_remote.py | 4 |
7 files changed, 146 insertions, 29 deletions
diff --git a/git/db/cmd/base.py b/git/db/cmd/base.py index 95c587db..6a4a6544 100644 --- a/git/db/cmd/base.py +++ b/git/db/cmd/base.py @@ -432,20 +432,47 @@ class CmdFetchInfo(FetchInfo): ref_type = None if remote_local_ref == "FETCH_HEAD": ref_type = SymbolicReference - elif ref_type_name == "branch": + elif ref_type_name in ("remote-tracking", "branch"): + # note: remote-tracking is just the first part of the 'remote-tracking branch' token. + # We don't parse it correctly, but its enough to know what to do, and its new in git 1.7something ref_type = RemoteReference elif ref_type_name == "tag": ref_type = TagReference else: raise TypeError("Cannot handle reference type: %r" % ref_type_name) + #END handle ref type # create ref instance if ref_type is SymbolicReference: remote_local_ref = ref_type(repo, "FETCH_HEAD") else: - remote_local_ref = Reference.from_path(repo, join_path(ref_type._common_path_default, remote_local_ref.strip())) + # determine prefix. Tags are usually pulled into refs/tags, they may have subdirectories. + # It is not clear sometimes where exactly the item is, unless we have an absolute path as indicated + # by the 'ref/' prefix. Otherwise even a tag could be in refs/remotes, which is when it will have the + # 'tags/' subdirectory in its path. + # We don't want to test for actual existence, but try to figure everything out analytically. + ref_path = None + remote_local_ref = remote_local_ref.strip() + if remote_local_ref.startswith(Reference._common_path_default + "/"): + # always use actual type if we get absolute paths + # Will always be the case if something is fetched outside of refs/remotes (if its not a tag) + ref_path = remote_local_ref + if ref_type is not TagReference and not remote_local_ref.startswith(RemoteReference._common_path_default + "/"): + ref_type = Reference + #END downgrade remote reference + elif ref_type is TagReference and 'tags/' in remote_local_ref: + # even though its a tag, it is located in refs/remotes + ref_path = join_path(RemoteReference._common_path_default, remote_local_ref) + else: + ref_path = join_path(ref_type._common_path_default, remote_local_ref) + #END obtain refpath + + # even though the path could be within the git conventions, we make + # sure we respect whatever the user wanted, and disabled path checking + remote_local_ref = ref_type(repo, ref_path, check_path=False) # END create ref instance + note = ( note and note.strip() ) or '' # parse flags from control_character diff --git a/git/refs/reference.py b/git/refs/reference.py index 5cff74bb..d85b194d 100644 --- a/git/refs/reference.py +++ b/git/refs/reference.py @@ -11,6 +11,18 @@ from git.util import ( __all__ = ["Reference"] +#{ Utilities +def require_remote_ref_path(func): + """A decorator raising a TypeError if we are not a valid remote, based on the path""" + def wrapper(self, *args): + if not self.path.startswith(self._remote_common_path_default + "/"): + raise ValueError("ref path does not point to a remote reference: %s" % path) + return func(self, *args) + #END wrapper + wrapper.__name__ = func.__name__ + return wrapper +#}END utilites + class Reference(SymbolicReference, LazyMixin, Iterable): """Represents a named reference to any object. Subclasses may apply restrictions though, @@ -20,20 +32,24 @@ class Reference(SymbolicReference, LazyMixin, Iterable): _resolve_ref_on_create = True _common_path_default = "refs" - def __init__(self, repo, path): + def __init__(self, repo, path, check_path = True): """Initialize this instance :param repo: Our parent repository :param path: Path relative to the .git/ directory pointing to the ref in question, i.e. - refs/heads/master""" - if not path.startswith(self._common_path_default+'/'): - raise ValueError("Cannot instantiate %r from path %s, maybe use %s.to_full_path(name) to safely generate a valid full path from a name" % ( self.__class__.__name__, path, type(self).__name__)) + refs/heads/master + :param check_path: if False, you can provide any path. Otherwise the path must start with the + default path prefix of this type.""" + if check_path and not path.startswith(self._common_path_default+'/'): + raise ValueError("Cannot instantiate %r from path %s" % (self.__class__.__name__, path)) super(Reference, self).__init__(repo, path) def __str__(self): return self.name + + #{ Interface def set_object(self, object, logmsg = None): """Special version which checks if the head-log needs an update as well""" @@ -80,3 +96,30 @@ class Reference(SymbolicReference, LazyMixin, Iterable): """Equivalent to SymbolicReference.iter_items, but will return non-detached references as well.""" return cls._iter_items(repo, common_path) + + #}END interface + + + #{ Remote Interface + + @property + @require_remote_ref_path + def remote_name(self): + """ + :return: + Name of the remote we are a reference of, such as 'origin' for a reference + named 'origin/master'""" + tokens = self.path.split('/') + # /refs/remotes/<remote name>/<branch_name> + return tokens[2] + + @property + @require_remote_ref_path + def remote_head(self): + """:return: Name of the remote head itself, i.e. master. + :note: The returned name is usually not qualified enough to uniquely identify + a branch""" + tokens = self.path.split('/') + return '/'.join(tokens[3:]) + + #} END remote interface diff --git a/git/refs/remote.py b/git/refs/remote.py index f2dc72ee..d7bfc3e0 100644 --- a/git/refs/remote.py +++ b/git/refs/remote.py @@ -12,7 +12,7 @@ class RemoteReference(Head): """Represents a reference pointing to a remote head.""" __slots__ = tuple() - _common_path_default = "refs/remotes" + _common_path_default = Head._remote_common_path_default @classmethod @@ -24,24 +24,6 @@ class RemoteReference(Head): # END handle remote constraint return super(RemoteReference, cls).iter_items(repo, common_path) - @property - def remote_name(self): - """ - :return: - Name of the remote we are a reference of, such as 'origin' for a reference - named 'origin/master'""" - tokens = self.path.split('/') - # /refs/remotes/<remote name>/<branch_name> - return tokens[2] - - @property - def remote_head(self): - """:return: Name of the remote head itself, i.e. master. - :note: The returned name is usually not qualified enough to uniquely identify - a branch""" - tokens = self.path.split('/') - return '/'.join(tokens[3:]) - @classmethod def create(cls, *args, **kwargs): """Used to disable this method""" diff --git a/git/refs/symbolic.py b/git/refs/symbolic.py index ec31f30c..2c8faa91 100644 --- a/git/refs/symbolic.py +++ b/git/refs/symbolic.py @@ -36,6 +36,7 @@ class SymbolicReference(object): _resolve_ref_on_create = False _points_to_commits_only = True _common_path_default = "" + _remote_common_path_default = "refs/remotes" _id_attribute_ = "name" re_hexsha_only = re.compile('^[0-9A-Fa-f]{40}$') diff --git a/git/test/db/cmd/test_base.py b/git/test/db/cmd/test_base.py index 959be16b..cbb4a339 100644 --- a/git/test/db/cmd/test_base.py +++ b/git/test/db/cmd/test_base.py @@ -11,6 +11,8 @@ from git.exc import BadObject from git.db.complex import CmdCompatibilityGitDB from git.db.cmd.base import * +from git.refs import TagReference, Reference, RemoteReference + class TestBase(RepoBase): RepoCls = CmdCompatibilityGitDB @@ -28,5 +30,62 @@ class TestBase(RepoBase): self.failUnlessRaises(BadObject, gdb.partial_to_complete_sha_hex, invalid_rev) def test_fetch_info(self): - self.failUnlessRaises(ValueError, CmdFetchInfo._from_line, self.rorepo, "nonsense", '') - self.failUnlessRaises(ValueError, CmdFetchInfo._from_line, self.rorepo, "? [up to date] 0.1.7RC -> origin/0.1.7RC", '') + self.failUnlessRaises(ValueError, CmdCmdFetchInfo._from_line, self.rorepo, "nonsense", '') + self.failUnlessRaises(ValueError, CmdCmdFetchInfo._from_line, self.rorepo, "? [up to date] 0.1.7RC -> origin/0.1.7RC", '') + + + def test_fetch_info(self): + # assure we can handle remote-tracking branches + fetch_info_line_fmt = "c437ee5deb8d00cf02f03720693e4c802e99f390 not-for-merge %s '0.3' of git://github.com/gitpython-developers/GitPython" + remote_info_line_fmt = "* [new branch] nomatter -> %s" + fi = CmdFetchInfo._from_line(self.rorepo, + remote_info_line_fmt % "local/master", + fetch_info_line_fmt % 'remote-tracking branch') + + # we wouldn't be here if it wouldn't have worked + + # handles non-default refspecs: One can specify a different path in refs/remotes + # or a special path just in refs/something for instance + + fi = CmdFetchInfo._from_line(self.rorepo, + remote_info_line_fmt % "subdir/tagname", + fetch_info_line_fmt % 'tag') + + assert isinstance(fi.ref, TagReference) + assert fi.ref.path.startswith('refs/tags') + + # it could be in a remote direcftory though + fi = CmdFetchInfo._from_line(self.rorepo, + remote_info_line_fmt % "remotename/tags/tagname", + fetch_info_line_fmt % 'tag') + + assert isinstance(fi.ref, TagReference) + assert fi.ref.path.startswith('refs/remotes/') + + # it can also be anywhere ! + tag_path = "refs/something/remotename/tags/tagname" + fi = CmdFetchInfo._from_line(self.rorepo, + remote_info_line_fmt % tag_path, + fetch_info_line_fmt % 'tag') + + assert isinstance(fi.ref, TagReference) + assert fi.ref.path == tag_path + + # branches default to refs/remotes + fi = CmdFetchInfo._from_line(self.rorepo, + remote_info_line_fmt % "remotename/branch", + fetch_info_line_fmt % 'branch') + + assert isinstance(fi.ref, RemoteReference) + assert fi.ref.remote_name == 'remotename' + + # but you can force it anywhere, in which case we only have a references + fi = CmdFetchInfo._from_line(self.rorepo, + remote_info_line_fmt % "refs/something/branch", + fetch_info_line_fmt % 'branch') + + assert type(fi.ref) is Reference + assert fi.ref.path == "refs/something/branch" + + + diff --git a/git/test/refs/test_refs.py b/git/test/refs/test_refs.py index 668dd94c..81be173c 100644 --- a/git/test/refs/test_refs.py +++ b/git/test/refs/test_refs.py @@ -29,6 +29,11 @@ class TestRefs(TestBase): assert isinstance(instance, ref_type) # END for each name # END for each type + + # invalid path + self.failUnlessRaises(ValueError, TagReference, self.rorepo, "refs/invalid/tag") + # works without path check + TagReference(self.rorepo, "refs/invalid/tag", check_path=False) def test_tag_base(self): tag_object_refs = list() diff --git a/git/test/test_remote.py b/git/test/test_remote.py index 8ae9fe43..cef8687b 100644 --- a/git/test/test_remote.py +++ b/git/test/test_remote.py @@ -256,7 +256,7 @@ class TestRemote(TestBase): shutil.rmtree(other_repo_dir) # END test and cleanup - def _test_push_and_pull(self,remote, rw_repo, remote_repo): + def _verify_push_and_pull(self,remote, rw_repo, remote_repo): # push our changes lhead = rw_repo.head lindex = rw_repo.index @@ -407,7 +407,7 @@ class TestRemote(TestBase): # END for each rename ( back to prev_name ) # PUSH/PULL TESTING - self._test_push_and_pull(remote, rw_repo, remote_repo) + self._verify_push_and_pull(remote, rw_repo, remote_repo) # FETCH TESTING # Only for remotes - local cases are the same or less complicated |