diff options
author | Sebastian Thiel <byronimo@gmail.com> | 2009-11-03 14:04:32 +0100 |
---|---|---|
committer | Sebastian Thiel <byronimo@gmail.com> | 2009-11-03 14:04:32 +0100 |
commit | e70f3218e910d2b3dcb8a5ab40c65b6bd7a8e9a8 (patch) | |
tree | fae4ea605cdc996ded52c72dd8131a62c9a598d5 | |
parent | b2ccae0d7fca3a99fc6a3f85f554d162a3fdc916 (diff) | |
download | gitpython-e70f3218e910d2b3dcb8a5ab40c65b6bd7a8e9a8.tar.gz |
Intermediate commit with a few added and improved tests as well as many fixes
-rw-r--r-- | TODO | 10 | ||||
-rw-r--r-- | lib/git/refs.py | 3 | ||||
-rw-r--r-- | lib/git/remote.py | 44 | ||||
-rw-r--r-- | test/git/test_remote.py | 68 |
4 files changed, 110 insertions, 15 deletions
@@ -131,6 +131,16 @@ Submodules ---------- * add submodule support +TestSystem +---------- +* Figure out a good way to indicate the required presense of a git-daemon to host + a specific path. Ideally, the system would detect the missing daemon and inform + the user about the required command-line to start the daemon where we need it. + Reason for us being unable to start a daemon is that it will always fork - we can + only kill itself, but not its children. Even if we would a pgrep like match, we still + would not know whether it truly is our daemons - in that case user permissions should + stop us though. + Tree ---- * Should return submodules during iteration ( identifies as commit ) diff --git a/lib/git/refs.py b/lib/git/refs.py index 9b089a25..cea3e720 100644 --- a/lib/git/refs.py +++ b/lib/git/refs.py @@ -167,6 +167,9 @@ class Reference(LazyMixin, Iterable): Instance of type Reference, Head, Tag, SymbolicReference or HEAD depending on the given path """ + if not path: + raise ValueError("Cannot create Reference from %r" % path) + if path == 'HEAD': return HEAD(repo, path) diff --git a/lib/git/remote.py b/lib/git/remote.py index a19428b2..482df233 100644 --- a/lib/git/remote.py +++ b/lib/git/remote.py @@ -7,9 +7,11 @@ Module implementing a remote object allowing easy access to git remotes """ +from errors import GitCommandError from git.utils import LazyMixin, Iterable, IterableList from objects import Commit from refs import Reference, RemoteReference, SymbolicReference, TagReference + import re import os @@ -146,6 +148,7 @@ class PushInfo(object): info = remote.push()[0] info.flags # bitflags providing more information about the result info.local_ref # Reference pointing to the local reference that was pushed + # It is None if the ref was deleted. info.remote_ref_string # path to the remote reference located on the remote side info.remote_ref # Remote Reference on the local side corresponding to # the remote_ref_string. It can be a TagReference as well. @@ -154,8 +157,8 @@ class PushInfo(object): """ __slots__ = ('local_ref', 'remote_ref_string', 'flags', 'old_commit', '_remote') - NO_MATCH, REJECTED, REMOTE_REJECTED, REMOTE_FAILURE, DELETED, \ - FORCED_UPDATE, FAST_FORWARD, UP_TO_DATE, ERROR = [ 1 << x for x in range(9) ] + NEW_TAG, NEW_HEAD, NO_MATCH, REJECTED, REMOTE_REJECTED, REMOTE_FAILURE, DELETED, \ + FORCED_UPDATE, FAST_FORWARD, UP_TO_DATE, ERROR = [ 1 << x for x in range(11) ] _flag_map = { 'X' : NO_MATCH, '-' : DELETED, '*' : 0, '+' : FORCED_UPDATE, ' ' : FAST_FORWARD, @@ -194,6 +197,7 @@ class PushInfo(object): Create a new PushInfo instance as parsed from line which is expected to be like c refs/heads/master:refs/heads/master 05d2687..1d0568e """ + print line control_character, from_to, summary = line.split('\t', 3) flags = 0 @@ -206,7 +210,10 @@ class PushInfo(object): # from_to handling from_ref_string, to_ref_string = from_to.split(':') - from_ref = Reference.from_path(remote.repo, from_ref_string) + if flags & cls.DELETED: + from_ref = None + else: + from_ref = Reference.from_path(remote.repo, from_ref_string) # commit handling, could be message or commit info old_commit = None @@ -219,6 +226,10 @@ class PushInfo(object): flags |= cls.REMOTE_FAILURE elif "[no match]" in summary: flags |= cls.ERROR + elif "[new tag]" in summary: + flags |= cls.NEW_TAG + elif "[new branch]" in summary: + flags |= cls.NEW_HEAD # uptodate encoded in control character else: # fast-forward or forced update - was encoded in control character, @@ -249,8 +260,9 @@ class FetchInfo(object): """ __slots__ = ('ref','commit_before_forced_update', 'flags', 'note') - HEAD_UPTODATE, REJECTED, FORCED_UPDATE, FAST_FORWARD, NEW_TAG, \ - TAG_UPDATE, NEW_HEAD, ERROR = [ 1 << x for x in range(8) ] + NEW_TAG, NEW_HEAD, HEAD_UPTODATE, TAG_UPDATE, REJECTED, FORCED_UPDATE, \ + FAST_FORWARD, ERROR = [ 1 << x for x in range(8) ] + # %c %-*s %-*s -> %s (%s) re_fetch_result = re.compile("^\s*(.) (\[?[\w\s\.]+\]?)\s+(.+) -> ([/\w_\.-]+)( \(.*\)?$)?") @@ -568,8 +580,20 @@ class Remote(LazyMixin, Iterable): # END for each progress line output = IterableList('name') - output.extend(PushInfo._from_line(self, line) for line in proc.stdout.readlines()) - proc.wait() + for line in proc.stdout.readlines(): + try: + output.append(PushInfo._from_line(self, line)) + except ValueError: + # if an error happens, additional info is given which we cannot parse + pass + # END exception handling + # END for each line + try: + proc.wait() + except GitCommandError: + # if a push has rejected items, the command has non-zero return status + pass + # END exception handling return output @@ -634,7 +658,11 @@ class Remote(LazyMixin, Iterable): Returns IterableList(PushInfo, ...) iterable list of PushInfo instances, each one informing about an individual head which had been updated on the remote - side + side. + If the push contains rejected heads, these will have the PushInfo.ERROR bit set + in their flags. + If the operation fails completely, the length of the returned IterableList will + be null. """ proc = self.repo.git.push(self, refspec, porcelain=True, as_process=True, **kwargs) return self._get_push_info(proc, progress or PushProgress()) diff --git a/test/git/test_remote.py b/test/git/test_remote.py index 602c74cf..306d0da3 100644 --- a/test/git/test_remote.py +++ b/test/git/test_remote.py @@ -18,16 +18,21 @@ class TestPushProgress(PushProgress): __slots__ = ( "_seen_lines", "_stages_per_op" ) def __init__(self): super(TestPushProgress, self).__init__() - self._seen_lines = 0 + self._seen_lines = list() self._stages_per_op = dict() def _parse_progress_line(self, line): + # we may remove the line later if it is dropped + # Keep it for debugging + self._seen_lines.append(line) super(TestPushProgress, self)._parse_progress_line(line) assert len(line) > 1, "line %r too short" % line - self._seen_lines += 1 def line_dropped(self, line): - pass + try: + self._seen_lines.remove(line) + except ValueError: + pass def update(self, op_code, cur_count, max_count=None, message=''): # check each stage only comes once @@ -45,7 +50,8 @@ class TestPushProgress(PushProgress): if not self._seen_lines: return - assert len(self._seen_ops) == 3 + # sometimes objects are not compressed which is okay + assert len(self._seen_ops) in (2,3) assert self._stages_per_op # must have seen all stages @@ -90,7 +96,10 @@ class TestRemote(TestBase): assert has_one else: # there must be a remote commit - assert isinstance(info.local_ref, Reference) + if info.flags & info.DELETED == 0: + assert isinstance(info.local_ref, Reference) + else: + assert info.local_ref is None assert type(info.remote_ref) in (TagReference, RemoteReference) # END error checking # END for each info @@ -239,6 +248,7 @@ class TestRemote(TestBase): # well, works nicely # self.failUnlessRaises(GitCommandError, remote.push) + # simple file push self._commit_random_file(rw_repo) progress = TestPushProgress() res = remote.push(lhead.reference, progress) @@ -246,10 +256,54 @@ class TestRemote(TestBase): self._test_push_result(res, remote) progress.make_assertion() + # rejected - undo last commit + lhead.reset("HEAD~1") + res = remote.push(lhead.reference) + assert res[0].flags & PushInfo.ERROR + assert res[0].flags & PushInfo.REJECTED + self._test_push_result(res, remote) + + # force rejected pull + res = remote.push('+%s' % lhead.reference) + assert res[0].flags & PushInfo.ERROR == 0 + assert res[0].flags & PushInfo.FORCED_UPDATE + self._test_push_result(res, remote) + + # invalid refspec + res = remote.push("hellothere") + assert len(res) == 0 + + # push new tags + progress = TestPushProgress() + to_be_updated = "my_tag.1.0RV" + new_tag = TagReference.create(rw_repo, to_be_updated) + other_tag = TagReference.create(rw_repo, "my_obj_tag.2.1aRV", message="my message") + res = remote.push(progress=progress, tags=True) + assert res[-1].flags & PushInfo.NEW_TAG + progress.make_assertion() + self._test_push_result(res, remote) + + # update push new tags + # Rejection is default + new_tag = TagReference.create(rw_repo, to_be_updated, ref='HEAD~1', force=True) + res = remote.push(tags=True) + self._test_push_result(res, remote) + assert res[-1].flags & PushInfo.REJECTED and res[-1].flags & PushInfo.ERROR + + # push force this tag + res = remote.push("+%s" % new_tag.path) + assert res[-1].flags & PushInfo.ERROR == 0 and res[-1].flags & PushInfo.FORCED_UPDATE + + # delete tag - have to do it using refspec + res = remote.push(":%s" % new_tag.path) + self._test_push_result(res, remote) + assert res[0].flags & PushInfo.DELETED self.fail("test --all") - self.fail("test rewind and force -push") - self.fail("test general fail due to invalid refspec") + + # push new branch + + # delete new branch # pull is essentially a fetch + merge, hence we just do a light # test here, leave the reset to the actual merge testing |