summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian Thiel <byronimo@gmail.com>2009-11-03 14:04:32 +0100
committerSebastian Thiel <byronimo@gmail.com>2009-11-03 14:04:32 +0100
commite70f3218e910d2b3dcb8a5ab40c65b6bd7a8e9a8 (patch)
treefae4ea605cdc996ded52c72dd8131a62c9a598d5
parentb2ccae0d7fca3a99fc6a3f85f554d162a3fdc916 (diff)
downloadgitpython-e70f3218e910d2b3dcb8a5ab40c65b6bd7a8e9a8.tar.gz
Intermediate commit with a few added and improved tests as well as many fixes
-rw-r--r--TODO10
-rw-r--r--lib/git/refs.py3
-rw-r--r--lib/git/remote.py44
-rw-r--r--test/git/test_remote.py68
4 files changed, 110 insertions, 15 deletions
diff --git a/TODO b/TODO
index 3a31976c..49d6728c 100644
--- a/TODO
+++ b/TODO
@@ -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