summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/source/changes.rst8
-rw-r--r--objects/submodule/base.py21
-rw-r--r--objects/submodule/root.py20
-rw-r--r--remote.py121
-rw-r--r--util.py123
5 files changed, 158 insertions, 135 deletions
diff --git a/doc/source/changes.rst b/doc/source/changes.rst
index d820c8ca..74692955 100644
--- a/doc/source/changes.rst
+++ b/doc/source/changes.rst
@@ -21,15 +21,11 @@ Changelog
* ``create(...)`` method now supports the reflog, but will not raise ``GitCommandError`` anymore as it is a pure python implementation now. Instead, it raises ``OSError``.
- * **Intrusive Changes** to ``Actor`` type
-
- * the *name* field is now using unicode if ascii does not match
-
* **Intrusive Changes** to ``Repo`` type
- * ``create_head(...)`` method does not support **kwargs anymore, instead it supports a logmsg parameter
+ * ``create_head(...)`` method does not support kwargs anymore, instead it supports a logmsg parameter
-* Repo.rev_parse now supports the [ref]@{n} syntax, where n is the number of steps to look into the reference's past
+* Repo.rev_parse now supports the [ref]@{n} syntax, where *n* is the number of steps to look into the reference's past
* **BugFixes**
diff --git a/objects/submodule/base.py b/objects/submodule/base.py
index 36b48d78..85350e66 100644
--- a/objects/submodule/base.py
+++ b/objects/submodule/base.py
@@ -12,7 +12,8 @@ from StringIO import StringIO # need a dict to set bloody .name field
from git.util import (
Iterable,
join_path_native,
- to_native_path_linux
+ to_native_path_linux,
+ RemoteProgress
)
from git.config import SectionConstraint
@@ -20,6 +21,7 @@ from git.exc import (
InvalidGitRepositoryError,
NoSuchPathError
)
+
import stat
import git
@@ -29,7 +31,16 @@ import time
import shutil
-__all__ = ["Submodule"]
+__all__ = ["Submodule", "UpdateProgress"]
+
+
+class UpdateProgress(RemoteProgress):
+ """Class providing detailed progress information to the caller who should
+ derive from it and implement the ``update(...)`` message"""
+ ADD, REMOVE, UPDATE = [1 << x for x in range(RemoteProgress._num_op_codes, RemoteProgress._num_op_codes+3)]
+
+ __slots__ = tuple()
+
# IndexObject comes via util module, its a 'hacky' fix thanks to pythons import
@@ -285,7 +296,7 @@ class Submodule(util.IndexObject, Iterable, Traversable):
return sm
- def update(self, recursive=False, init=True, to_latest_revision=False):
+ def update(self, recursive=False, init=True, to_latest_revision=False, progress=None):
"""Update the repository of this submodule to point to the checkout
we point at with the binsha of this instance.
@@ -297,6 +308,7 @@ class Submodule(util.IndexObject, Iterable, Traversable):
This only works if we have a local tracking branch, which is the case
if the remote repository had a master branch, or of the 'branch' option
was specified for this submodule and the branch existed remotely
+ :param progress: UpdateProgress instance or None of no progress should be shown
:note: does nothing in bare repositories
:note: method is definitely not atomic if recurisve is True
:return: self"""
@@ -304,6 +316,9 @@ class Submodule(util.IndexObject, Iterable, Traversable):
return self
#END pass in bare mode
+ if progress is None:
+ progress = UpdateProgress()
+ #END handle progress
# ASSURE REPO IS PRESENT AND UPTODATE
#####################################
diff --git a/objects/submodule/root.py b/objects/submodule/root.py
index 753c6df4..b0dba08b 100644
--- a/objects/submodule/root.py
+++ b/objects/submodule/root.py
@@ -1,4 +1,4 @@
-from base import Submodule
+from base import Submodule, UpdateProgress
from util import (
find_first_remote_branch
)
@@ -9,7 +9,7 @@ import sys
__all__ = ["RootModule"]
-
+
class RootModule(Submodule):
"""A (virtual) Root of all submodules in the given repository. It can be used
to more easily traverse all submodules of the master repository"""
@@ -38,7 +38,8 @@ class RootModule(Submodule):
#{ Interface
- def update(self, previous_commit=None, recursive=True, force_remove=False, init=True, to_latest_revision=False):
+ def update(self, previous_commit=None, recursive=True, force_remove=False, init=True,
+ to_latest_revision=False, progress=None, dry_run=False):
"""Update the submodules of this repository to the current HEAD commit.
This method behaves smartly by determining changes of the path of a submodules
repository, next to changes to the to-be-checked-out commit or the branch to be
@@ -57,11 +58,18 @@ class RootModule(Submodule):
:param init: If we encounter a new module which would need to be initialized, then do it.
:param to_latest_revision: If True, instead of checking out the revision pointed to
by this submodule's sha, the checked out tracking branch will be merged with the
- newest remote branch fetched from the repository's origin"""
+ newest remote branch fetched from the repository's origin
+ :param progress: UpdateProgress instance or None if no progress should be sent
+ :param dry_run: if True, operations will not actually be performed. Progress messages
+ will change accordingly to indicate the WOULD DO state of the operation."""
if self.repo.bare:
raise InvalidGitRepositoryError("Cannot update submodules in bare repositories")
# END handle bare
+ if progress is None:
+ progress = UpdateProgress()
+ #END assure progress is set
+
repo = self.repo
# HANDLE COMMITS
@@ -125,7 +133,7 @@ class RootModule(Submodule):
assert nn not in [r.name for r in rmts]
smr = smm.create_remote(nn, sm.url)
- smr.fetch()
+ smr.fetch(progress=progress)
# If we have a tracking branch, it should be available
# in the new remote as well.
@@ -234,7 +242,7 @@ class RootModule(Submodule):
######################################
for sm in sms:
# update the submodule using the default method
- sm.update(recursive=False, init=init, to_latest_revision=to_latest_revision)
+ sm.update(recursive=False, init=init, to_latest_revision=to_latest_revision, progress=progress)
# update recursively depth first - question is which inconsitent
# state will be better in case it fails somewhere. Defective branch
diff --git a/remote.py b/remote.py
index 2e596ca1..69a8126b 100644
--- a/remote.py
+++ b/remote.py
@@ -7,14 +7,14 @@
# Module implementing a remote object allowing easy access to git remotes
from exc import GitCommandError
-from objects import Commit
from ConfigParser import NoOptionError
from config import SectionConstraint
from git.util import (
LazyMixin,
Iterable,
- IterableList
+ IterableList,
+ RemoteProgress
)
from refs import (
@@ -33,123 +33,6 @@ import sys
__all__ = ('RemoteProgress', 'PushInfo', 'FetchInfo', 'Remote')
-class RemoteProgress(object):
- """
- Handler providing an interface to parse progress information emitted by git-push
- and git-fetch and to dispatch callbacks allowing subclasses to react to the progress.
- """
- BEGIN, END, COUNTING, COMPRESSING, WRITING = [ 1 << x for x in range(5) ]
- STAGE_MASK = BEGIN|END
- OP_MASK = COUNTING|COMPRESSING|WRITING
-
- __slots__ = ("_cur_line", "_seen_ops")
- re_op_absolute = re.compile("(remote: )?([\w\s]+):\s+()(\d+)()(.*)")
- re_op_relative = re.compile("(remote: )?([\w\s]+):\s+(\d+)% \((\d+)/(\d+)\)(.*)")
-
- def __init__(self):
- self._seen_ops = list()
-
- def _parse_progress_line(self, line):
- """Parse progress information from the given line as retrieved by git-push
- or git-fetch
-
- :return: list(line, ...) list of lines that could not be processed"""
- # handle
- # Counting objects: 4, done.
- # Compressing objects: 50% (1/2) \rCompressing objects: 100% (2/2) \rCompressing objects: 100% (2/2), done.
- self._cur_line = line
- sub_lines = line.split('\r')
- failed_lines = list()
- for sline in sub_lines:
- # find esacpe characters and cut them away - regex will not work with
- # them as they are non-ascii. As git might expect a tty, it will send them
- last_valid_index = None
- for i,c in enumerate(reversed(sline)):
- if ord(c) < 32:
- # its a slice index
- last_valid_index = -i-1
- # END character was non-ascii
- # END for each character in sline
- if last_valid_index is not None:
- sline = sline[:last_valid_index]
- # END cut away invalid part
- sline = sline.rstrip()
-
- cur_count, max_count = None, None
- match = self.re_op_relative.match(sline)
- if match is None:
- match = self.re_op_absolute.match(sline)
-
- if not match:
- self.line_dropped(sline)
- failed_lines.append(sline)
- continue
- # END could not get match
-
- op_code = 0
- remote, op_name, percent, cur_count, max_count, message = match.groups()
-
- # get operation id
- if op_name == "Counting objects":
- op_code |= self.COUNTING
- elif op_name == "Compressing objects":
- op_code |= self.COMPRESSING
- elif op_name == "Writing objects":
- op_code |= self.WRITING
- else:
- raise ValueError("Operation name %r unknown" % op_name)
-
- # figure out stage
- if op_code not in self._seen_ops:
- self._seen_ops.append(op_code)
- op_code |= self.BEGIN
- # END begin opcode
-
- if message is None:
- message = ''
- # END message handling
-
- message = message.strip()
- done_token = ', done.'
- if message.endswith(done_token):
- op_code |= self.END
- message = message[:-len(done_token)]
- # END end message handling
-
- self.update(op_code, cur_count, max_count, message)
- # END for each sub line
- return failed_lines
-
- def line_dropped(self, line):
- """Called whenever a line could not be understood and was therefore dropped."""
- pass
-
- def update(self, op_code, cur_count, max_count=None, message=''):
- """Called whenever the progress changes
-
- :param op_code:
- Integer allowing to be compared against Operation IDs and stage IDs.
-
- Stage IDs are BEGIN and END. BEGIN will only be set once for each Operation
- ID as well as END. It may be that BEGIN and END are set at once in case only
- one progress message was emitted due to the speed of the operation.
- Between BEGIN and END, none of these flags will be set
-
- Operation IDs are all held within the OP_MASK. Only one Operation ID will
- be active per call.
- :param cur_count: Current absolute count of items
-
- :param max_count:
- The maximum count of items we expect. It may be None in case there is
- no maximum number of items or if it is (yet) unknown.
-
- :param message:
- In case of the 'WRITING' operation, it contains the amount of bytes
- transferred. It may possibly be used for other purposes as well.
-
- You may read the contents of the current line in self._cur_line"""
- pass
-
class PushInfo(object):
"""
diff --git a/util.py b/util.py
index 42719233..8c0b6697 100644
--- a/util.py
+++ b/util.py
@@ -22,7 +22,8 @@ from gitdb.util import (
__all__ = ( "stream_copy", "join_path", "to_native_path_windows", "to_native_path_linux",
"join_path_native", "Stats", "IndexFileSHA1Writer", "Iterable", "IterableList",
- "BlockingLockFile", "LockFile", 'Actor', 'get_user_id', 'assure_directory_exists')
+ "BlockingLockFile", "LockFile", 'Actor', 'get_user_id', 'assure_directory_exists',
+ 'RemoteProgress')
#{ Utility Methods
@@ -77,6 +78,7 @@ def join_path_native(a, *p):
def assure_directory_exists(path, is_file=False):
"""Assure that the directory pointed to by path exists.
+
:param is_file: If True, path is assumed to be a file and handled correctly.
Otherwise it must be a directory
:return: True if the directory was created, False if it already existed"""
@@ -103,6 +105,125 @@ def get_user_id():
#{ Classes
+class RemoteProgress(object):
+ """
+ Handler providing an interface to parse progress information emitted by git-push
+ and git-fetch and to dispatch callbacks allowing subclasses to react to the progress.
+ """
+ _num_op_codes = 5
+ BEGIN, END, COUNTING, COMPRESSING, WRITING = [1 << x for x in range(_num_op_codes)]
+ STAGE_MASK = BEGIN|END
+ OP_MASK = ~STAGE_MASK
+
+ __slots__ = ("_cur_line", "_seen_ops")
+ re_op_absolute = re.compile("(remote: )?([\w\s]+):\s+()(\d+)()(.*)")
+ re_op_relative = re.compile("(remote: )?([\w\s]+):\s+(\d+)% \((\d+)/(\d+)\)(.*)")
+
+ def __init__(self):
+ self._seen_ops = list()
+
+ def _parse_progress_line(self, line):
+ """Parse progress information from the given line as retrieved by git-push
+ or git-fetch
+
+ :return: list(line, ...) list of lines that could not be processed"""
+ # handle
+ # Counting objects: 4, done.
+ # Compressing objects: 50% (1/2) \rCompressing objects: 100% (2/2) \rCompressing objects: 100% (2/2), done.
+ self._cur_line = line
+ sub_lines = line.split('\r')
+ failed_lines = list()
+ for sline in sub_lines:
+ # find esacpe characters and cut them away - regex will not work with
+ # them as they are non-ascii. As git might expect a tty, it will send them
+ last_valid_index = None
+ for i,c in enumerate(reversed(sline)):
+ if ord(c) < 32:
+ # its a slice index
+ last_valid_index = -i-1
+ # END character was non-ascii
+ # END for each character in sline
+ if last_valid_index is not None:
+ sline = sline[:last_valid_index]
+ # END cut away invalid part
+ sline = sline.rstrip()
+
+ cur_count, max_count = None, None
+ match = self.re_op_relative.match(sline)
+ if match is None:
+ match = self.re_op_absolute.match(sline)
+
+ if not match:
+ self.line_dropped(sline)
+ failed_lines.append(sline)
+ continue
+ # END could not get match
+
+ op_code = 0
+ remote, op_name, percent, cur_count, max_count, message = match.groups()
+
+ # get operation id
+ if op_name == "Counting objects":
+ op_code |= self.COUNTING
+ elif op_name == "Compressing objects":
+ op_code |= self.COMPRESSING
+ elif op_name == "Writing objects":
+ op_code |= self.WRITING
+ else:
+ raise ValueError("Operation name %r unknown" % op_name)
+
+ # figure out stage
+ if op_code not in self._seen_ops:
+ self._seen_ops.append(op_code)
+ op_code |= self.BEGIN
+ # END begin opcode
+
+ if message is None:
+ message = ''
+ # END message handling
+
+ message = message.strip()
+ done_token = ', done.'
+ if message.endswith(done_token):
+ op_code |= self.END
+ message = message[:-len(done_token)]
+ # END end message handling
+
+ self.update(op_code, cur_count, max_count, message)
+ # END for each sub line
+ return failed_lines
+
+ def line_dropped(self, line):
+ """Called whenever a line could not be understood and was therefore dropped."""
+ pass
+
+ def update(self, op_code, cur_count, max_count=None, message=''):
+ """Called whenever the progress changes
+
+ :param op_code:
+ Integer allowing to be compared against Operation IDs and stage IDs.
+
+ Stage IDs are BEGIN and END. BEGIN will only be set once for each Operation
+ ID as well as END. It may be that BEGIN and END are set at once in case only
+ one progress message was emitted due to the speed of the operation.
+ Between BEGIN and END, none of these flags will be set
+
+ Operation IDs are all held within the OP_MASK. Only one Operation ID will
+ be active per call.
+ :param cur_count: Current absolute count of items
+
+ :param max_count:
+ The maximum count of items we expect. It may be None in case there is
+ no maximum number of items or if it is (yet) unknown.
+
+ :param message:
+ In case of the 'WRITING' operation, it contains the amount of bytes
+ transferred. It may possibly be used for other purposes as well.
+
+ You may read the contents of the current line in self._cur_line"""
+ pass
+
+
class Actor(object):
"""Actors hold information about a person acting on the repository. They
can be committers and authors or anything with a name and an email as