summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/git/remote.py87
-rw-r--r--test/git/test_remote.py20
2 files changed, 76 insertions, 31 deletions
diff --git a/lib/git/remote.py b/lib/git/remote.py
index 7f674d73..dde3be4c 100644
--- a/lib/git/remote.py
+++ b/lib/git/remote.py
@@ -9,7 +9,7 @@ Module implementing a remote object allowing easy access to git remotes
from git.utils import LazyMixin, Iterable, IterableList
from objects import Commit
-from refs import Reference, RemoteReference
+from refs import Reference, RemoteReference, SymbolicReference, TagReference
import re
import os
@@ -57,13 +57,16 @@ class Remote(LazyMixin, Iterable):
Carries information about the results of a fetch operation::
info = remote.fetch()[0]
- info.remote_ref # Symbolic Reference or RemoteReference to the changed remote head or FETCH_HEAD
- info.flags # additional flags to be & with enumeration members, i.e. info.flags & info.REJECTED
+ info.ref # Symbolic Reference or RemoteReference to the changed
+ # remote head or FETCH_HEAD
+ info.flags # additional flags to be & with enumeration members,
+ # i.e. info.flags & info.REJECTED
+ # is 0 if ref is SymbolicReference
info.note # additional notes given by git-fetch intended for the user
- info.commit_before_forced_update # if info.flags & info.FORCED_UPDATE, field is set to the
- # previous location of remote_ref, otherwise None
+ info.commit_before_forced_update # if info.flags & info.FORCED_UPDATE,
+ # field is set to the previous location of ref, otherwise None
"""
- __slots__ = ('remote_ref','commit_before_forced_update', 'flags', 'note')
+ __slots__ = ('ref','commit_before_forced_update', 'flags', 'note')
BRANCH_UPTODATE, REJECTED, FORCED_UPDATE, FAST_FORWARD, NEW_TAG, \
TAG_UPDATE, NEW_BRANCH, ERROR = [ 1 << x for x in range(1,9) ]
@@ -73,11 +76,11 @@ class Remote(LazyMixin, Iterable):
_flag_map = { '!' : ERROR, '+' : FORCED_UPDATE, '-' : TAG_UPDATE, '*' : 0,
'=' : BRANCH_UPTODATE, ' ' : FAST_FORWARD }
- def __init__(self, remote_ref, flags, note = '', old_commit = None):
+ def __init__(self, ref, flags, note = '', old_commit = None):
"""
Initialize a new instance
"""
- self.remote_ref = remote_ref
+ self.ref = ref
self.flags = flags
self.note = note
self.commit_before_forced_update = old_commit
@@ -91,10 +94,10 @@ class Remote(LazyMixin, Iterable):
Returns
Name of our remote ref
"""
- return self.remote_ref.name
+ return self.ref.name
@classmethod
- def _from_line(cls, repo, line):
+ def _from_line(cls, repo, line, fetch_line):
"""
Parse information from the given line as returned by git-fetch -v
and return a new FetchInfo object representing this information.
@@ -109,13 +112,43 @@ class Remote(LazyMixin, Iterable):
* means birth of new branch or tag
= means the head was up to date ( and not moved )
' ' means a fast-forward
+
+ fetch line is the corresponding line from FETCH_HEAD, like
+ acb0fa8b94ef421ad60c8507b634759a472cd56c not-for-merge branch '0.1.7RC' of /tmp/tmpya0vairemote_repo
"""
match = cls.re_fetch_result.match(line)
if match is None:
raise ValueError("Failed to parse line: %r" % line)
+
+ # parse lines
control_character, operation, local_remote_ref, remote_local_ref, note = match.groups()
+ try:
+ new_hex_sha, fetch_operation, fetch_note = fetch_line.split("\t")
+ ref_type_name, fetch_note = fetch_note.split(' ', 1)
+ except ValueError: # unpack error
+ raise ValueError("Failed to parse FETCH__HEAD line: %r" % fetch_line)
+
+ # handle FETCH_HEAD and figure out ref type
+ # If we do not specify a target branch like master:refs/remotes/origin/master,
+ # the fetch result is stored in FETCH_HEAD which destroys the rule we usually
+ # have. In that case we use a symbolic reference which is detached
+ ref_type = None
+ if remote_local_ref == "FETCH_HEAD":
+ ref_type = SymbolicReference
+ elif ref_type_name == "branch":
+ ref_type = RemoteReference
+ elif ref_type_name == "tag":
+ ref_type = TagReference
+ else:
+ raise TypeError("Cannot handle reference type: %r" % ref_type_name)
+
+ # create ref instance
+ if ref_type is SymbolicReference:
+ remote_local_ref = ref_type(repo, "FETCH_HEAD")
+ else:
+ remote_local_ref = Reference.from_path(repo, os.path.join(ref_type._common_path_default, remote_local_ref.strip()))
+ # END create ref instance
- remote_local_ref = Reference.from_path(repo, os.path.join(RemoteReference._common_path_default, remote_local_ref.strip()))
note = ( note and note.strip() ) or ''
# parse flags from control_character
@@ -126,17 +159,19 @@ class Remote(LazyMixin, Iterable):
raise ValueError("Control character %r unknown as parsed from line %r" % (control_character, line))
# END control char exception hanlding
- # parse operation string for more info
+ # parse operation string for more info - makes no sense for symbolic refs
old_commit = None
- if 'rejected' in operation:
- flags |= cls.REJECTED
- if 'new tag' in operation:
- flags |= cls.NEW_TAG
- if 'new branch' in operation:
- flags |= cls.NEW_BRANCH
- if '...' in operation:
- old_commit = Commit(repo, operation.split('...')[0])
- # END handle refspec
+ if isinstance(remote_local_ref, Reference):
+ if 'rejected' in operation:
+ flags |= cls.REJECTED
+ if 'new tag' in operation:
+ flags |= cls.NEW_TAG
+ if 'new branch' in operation:
+ flags |= cls.NEW_BRANCH
+ if '...' in operation:
+ old_commit = Commit(repo, operation.split('...')[0])
+ # END handle refspec
+ # END reference flag handling
return cls(remote_local_ref, flags, note, old_commit)
@@ -316,7 +351,15 @@ class Remote(LazyMixin, Iterable):
# skip first line as it is some remote info we are not interested in
print stderr
output = IterableList('name')
- output.extend(self.FetchInfo._from_line(self.repo, line) for line in stderr.splitlines()[1:])
+ err_info = stderr.splitlines()[1:]
+
+ # read head information
+ fp = open(os.path.join(self.repo.path, 'FETCH_HEAD'),'r')
+ fetch_head_info = fp.readlines()
+ fp.close()
+
+ output.extend(self.FetchInfo._from_line(self.repo, err_line, fetch_line)
+ for err_line,fetch_line in zip(err_info, fetch_head_info))
return output
def fetch(self, refspec=None, **kwargs):
diff --git a/test/git/test_remote.py b/test/git/test_remote.py
index 91d63ffd..638cb103 100644
--- a/test/git/test_remote.py
+++ b/test/git/test_remote.py
@@ -18,11 +18,13 @@ class TestRemote(TestBase):
def _test_fetch_result(self, results, remote):
- self._print_fetchhead(remote.repo)
+ # self._print_fetchhead(remote.repo)
assert len(results) > 0 and isinstance(results[0], remote.FetchInfo)
for info in results:
- assert info.flags != 0
- assert isinstance(info.remote_ref, (SymbolicReference, Reference))
+ if isinstance(info.ref, Reference):
+ assert info.flags != 0
+ # END referebce type flags handling
+ assert isinstance(info.ref, (SymbolicReference, Reference))
if info.flags & info.FORCED_UPDATE:
assert isinstance(info.commit_before_forced_update, Commit)
else:
@@ -31,8 +33,8 @@ class TestRemote(TestBase):
# END for each info
def _test_fetch_info(self, repo):
- self.failUnlessRaises(ValueError, Remote.FetchInfo._from_line, repo, "nonsense")
- self.failUnlessRaises(ValueError, Remote.FetchInfo._from_line, repo, "? [up to date] 0.1.7RC -> origin/0.1.7RC")
+ self.failUnlessRaises(ValueError, Remote.FetchInfo._from_line, repo, "nonsense", '')
+ self.failUnlessRaises(ValueError, Remote.FetchInfo._from_line, repo, "? [up to date] 0.1.7RC -> origin/0.1.7RC", '')
def _test_fetch(self,remote, rw_repo, remote_repo):
# specialized fetch testing to de-clutter the main test
@@ -80,7 +82,7 @@ class TestRemote(TestBase):
new_remote_branch.rename("other_branch_name")
res = fetch_and_test(remote)
other_branch_info = get_info(res, remote, new_remote_branch)
- assert other_branch_info.remote_ref.commit == new_branch_info.remote_ref.commit
+ assert other_branch_info.ref.commit == new_branch_info.ref.commit
# remove new branch
Head.delete(new_remote_branch.repo, new_remote_branch)
@@ -93,11 +95,11 @@ class TestRemote(TestBase):
assert len(stale_refs) == 2 and isinstance(stale_refs[0], RemoteReference)
RemoteReference.delete(rw_repo, *stale_refs)
- # test single branch fetch with refspec
+ # test single branch fetch with refspec including target remote
res = fetch_and_test(remote, refspec="master:refs/remotes/%s/master"%remote)
assert len(res) == 1 and get_info(res, remote, 'master')
- # without refspec
+ # ... with respec and no target
res = fetch_and_test(remote, refspec='master')
assert len(res) == 1
@@ -105,7 +107,7 @@ class TestRemote(TestBase):
rtag = TagReference.create(remote_repo, "1.0-RV_hello.there")
res = fetch_and_test(remote, tags=True)
ltag = res[str(rtag)]
- assert isinstance(ltag, TagReference)
+ assert isinstance(ltag.ref, TagReference)
# delete tag