summaryrefslogtreecommitdiff
path: root/git
diff options
context:
space:
mode:
Diffstat (limited to 'git')
-rw-r--r--git/db/cmd/base.py31
-rw-r--r--git/refs/reference.py51
-rw-r--r--git/refs/remote.py20
-rw-r--r--git/refs/symbolic.py1
-rw-r--r--git/test/db/cmd/test_base.py63
-rw-r--r--git/test/refs/test_refs.py5
-rw-r--r--git/test/test_remote.py4
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