diff options
Diffstat (limited to 'git/db/cmd/base.py')
-rw-r--r-- | git/db/cmd/base.py | 205 |
1 files changed, 140 insertions, 65 deletions
diff --git a/git/db/cmd/base.py b/git/db/cmd/base.py index 393d9262..78adbc6e 100644 --- a/git/db/cmd/base.py +++ b/git/db/cmd/base.py @@ -14,18 +14,18 @@ from git.base import ( from git.util import ( bin_to_hex, hex_to_bin, - RemoteProgress, isfile, join_path, join, Actor, - IterableList + IterableList, ) -from git.db.interface import FetchInfo as GitdbFetchInfo -from git.db.interface import PushInfo as GitdbPushInfo from git.db.interface import ( + FetchInfo, + PushInfo, HighLevelRepository, - TransportDB + TransportDB, + RemoteProgress ) from git.cmd import Git from git.refs import ( @@ -41,8 +41,8 @@ import os import sys -__all__ = ('CmdTransportMixin', 'RemoteProgress', 'GitCommandMixin', - 'CmdObjectDBRMixin', 'CmdHighLevelRepository') +__all__ = ('CmdTransportMixin', 'GitCommandMixin', 'CmdPushInfo', 'CmdFetchInfo', + 'CmdRemoteProgress', 'CmdObjectDBRMixin', 'CmdHighLevelRepository') #{ Utilities @@ -115,13 +115,13 @@ def get_fetch_info_from_stderr(repo, proc, progress): assert len(fetch_info_lines) == len(fetch_head_info) - output.extend(FetchInfo._from_line(repo, err_line, fetch_line) + output.extend(CmdFetchInfo._from_line(repo, err_line, fetch_line) for err_line,fetch_line in zip(fetch_info_lines, fetch_head_info)) finalize_process(proc) return output -def get_push_info(repo, proc, progress): +def get_push_info(repo, remotename_or_url, proc, progress): # read progress information from stderr # we hope stdout can hold all the data, it should ... # read the lines manually as it will use carriage returns between the messages @@ -131,7 +131,7 @@ def get_push_info(repo, proc, progress): output = IterableList('name') for line in proc.stdout.readlines(): try: - output.append(PushInfo._from_line(repo, line)) + output.append(CmdPushInfo._from_line(repo, remotename_or_url, line)) except ValueError: # if an error happens, additional info is given which we cannot parse pass @@ -143,37 +143,119 @@ def get_push_info(repo, proc, progress): #} END utilities -class PushInfo(GitdbPushInfo): +class CmdRemoteProgress(RemoteProgress): """ - Carries information about the result of a push operation of a single head:: - - 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. - info.old_commit_binsha # binary sha at which the remote_ref was standing before we pushed - # it to local_ref.commit. Will be None if an error was indicated - info.summary # summary line providing human readable english text about the push - """ - __slots__ = ('local_ref', 'remote_ref_string', 'flags', 'old_commit_binsha', '_remote', 'summary') + A Remote progress implementation taking a user derived progress to call the + respective methods on. + """ + __slots__ = ("_seen_ops", '_progress') + 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, progress_instance = None): + self._seen_ops = list() + if progress_instance is None: + progress_instance = RemoteProgress() + #END assure proper instance + self._progress = progress_instance + + def _parse_progress_line(self, line): + """Parse progress information from the given line as retrieved by git-push + or git-fetch + + Call the own update(), __call__() and line_dropped() methods according + to the parsed result. + + :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. + 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._progress.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._progress.update(op_code, cur_count, max_count, message, line) + self._progress(message, line) + # END for each sub line + return failed_lines + + +class CmdPushInfo(PushInfo): + """ + Pure Python implementation of a PushInfo interface + """ + __slots__ = ('local_ref', 'remote_ref_string', 'flags', 'old_commit_binsha', + '_remotename_or_url', 'repo', 'summary') - _flag_map = { 'X' : GitdbPushInfo.NO_MATCH, - '-' : GitdbPushInfo.DELETED, '*' : 0, - '+' : GitdbPushInfo.FORCED_UPDATE, - ' ' : GitdbPushInfo.FAST_FORWARD, - '=' : GitdbPushInfo.UP_TO_DATE, - '!' : GitdbPushInfo.ERROR } + _flag_map = { 'X' : PushInfo.NO_MATCH, + '-' : PushInfo.DELETED, '*' : 0, + '+' : PushInfo.FORCED_UPDATE, + ' ' : PushInfo.FAST_FORWARD, + '=' : PushInfo.UP_TO_DATE, + '!' : PushInfo.ERROR } - def __init__(self, flags, local_ref, remote_ref_string, remote, old_commit_binsha=None, + def __init__(self, flags, local_ref, remote_ref_string, repo, remotename_or_url, old_commit_binsha=None, summary=''): """ Initialize a new instance """ self.flags = flags self.local_ref = local_ref + self.repo = repo self.remote_ref_string = remote_ref_string - self._remote = remote + self._remotename_or_url = remotename_or_url self.old_commit_binsha = old_commit_binsha self.summary = summary @@ -185,16 +267,20 @@ class PushInfo(GitdbPushInfo): to the remote_ref_string kept in this instance.""" # translate heads to a local remote, tags stay as they are if self.remote_ref_string.startswith("refs/tags"): - return TagReference(self._remote.repo, self.remote_ref_string) + return TagReference(self.repo, self.remote_ref_string) elif self.remote_ref_string.startswith("refs/heads"): - remote_ref = Reference(self._remote.repo, self.remote_ref_string) - return RemoteReference(self._remote.repo, "refs/remotes/%s/%s" % (str(self._remote), remote_ref.name)) + remote_ref = Reference(self.repo, self.remote_ref_string) + if '/' in self._remotename_or_url: + sys.stderr.write("Cannot provide RemoteReference instance if it was created from a url instead of of a remote name: %s. Returning Reference instance instead" % sefl._remotename_or_url) + return remote_ref + #END assert correct input + return RemoteReference(self.repo, "refs/remotes/%s/%s" % (str(self._remotename_or_url), remote_ref.name)) else: raise ValueError("Could not handle remote ref: %r" % self.remote_ref_string) # END @classmethod - def _from_line(cls, remote, line): + def _from_line(cls, repo, remotename_or_url, line): """Create a new PushInfo instance as parsed from line which is expected to be like refs/heads/master:refs/heads/master 05d2687..1d0568e""" control_character, from_to, summary = line.split('\t', 3) @@ -212,7 +298,7 @@ class PushInfo(GitdbPushInfo): if flags & cls.DELETED: from_ref = None else: - from_ref = Reference.from_path(remote.repo, from_ref_string) + from_ref = Reference.from_path(repo, from_ref_string) # commit handling, could be message or commit info old_commit_binsha = None @@ -237,38 +323,27 @@ class PushInfo(GitdbPushInfo): if control_character == " ": split_token = ".." old_sha, new_sha = summary.split(' ')[0].split(split_token) - # have to use constructor here as the sha usually is abbreviated - old_commit_binsha = remote.repo.commit(old_sha) + old_commit_binsha = repo.resolve(old_sha) # END message handling - return PushInfo(flags, from_ref, to_ref_string, remote, old_commit_binsha, summary) + return cls(flags, from_ref, to_ref_string, repo, remotename_or_url, old_commit_binsha, summary) -class FetchInfo(GitdbFetchInfo): +class CmdFetchInfo(FetchInfo): """ - Carries information about the results of a fetch operation of a single head:: - - info = remote.fetch()[0] - 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 FETCH_HEAD - info.note # additional notes given by git-fetch intended for the user - info.old_commit_binsha # if info.flags & info.FORCED_UPDATE|info.FAST_FORWARD, - # field is set to the previous location of ref, otherwise None + Pure python implementation of a FetchInfo interface """ __slots__ = ('ref','old_commit_binsha', 'flags', 'note') # %c %-*s %-*s -> %s (%s) re_fetch_result = re.compile("^\s*(.) (\[?[\w\s\.]+\]?)\s+(.+) -> ([/\w_\+\.-]+)( \(.*\)?$)?") - _flag_map = { '!' : GitdbFetchInfo.ERROR, - '+' : GitdbFetchInfo.FORCED_UPDATE, - '-' : GitdbFetchInfo.TAG_UPDATE, + _flag_map = { '!' : FetchInfo.ERROR, + '+' : FetchInfo.FORCED_UPDATE, + '-' : FetchInfo.TAG_UPDATE, '*' : 0, - '=' : GitdbFetchInfo.HEAD_UPTODATE, - ' ' : GitdbFetchInfo.FAST_FORWARD } + '=' : FetchInfo.HEAD_UPTODATE, + ' ' : FetchInfo.FAST_FORWARD } def __init__(self, ref, flags, note = '', old_commit_binsha = None): """ @@ -295,7 +370,7 @@ class FetchInfo(GitdbFetchInfo): @classmethod 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. + and return a new CmdFetchInfo object representing this information. We can handle a line as follows "%c %-*s %-*s -> %s%s" @@ -366,7 +441,7 @@ class FetchInfo(GitdbFetchInfo): split_token = '...' if control_character == ' ': split_token = split_token[:-1] - old_commit_binsha = repo.rev_parse(operation.split(split_token)[0]) + old_commit_binsha = repo.resolve(operation.split(split_token)[0]) # END handle refspec # END reference flag handling @@ -443,7 +518,7 @@ class CmdTransportMixin(TransportDB): :param progress: RemoteProgress derived instance or None :param **kwargs: Additional arguments to be passed to the git-push process""" proc = self._git.push(url, refspecs, porcelain=True, as_process=True, **kwargs) - return get_push_info(self, proc, progress or RemoteProgress()) + return get_push_info(self, url, proc, CmdRemoteProgress(progress)) def pull(self, url, refspecs=None, progress=None, **kwargs): """Fetch and merge the given refspecs. @@ -453,7 +528,7 @@ class CmdTransportMixin(TransportDB): :param refspecs: see push() :param progress: see push()""" proc = self._git.pull(url, refspecs, with_extended_output=True, as_process=True, v=True, **kwargs) - return get_fetch_info_from_stderr(self, proc, progress or RemoteProgress()) + return get_fetch_info_from_stderr(self, proc, CmdRemoteProgress(progress)) def fetch(self, url, refspecs=None, progress=None, **kwargs): """Fetch the latest changes @@ -461,7 +536,7 @@ class CmdTransportMixin(TransportDB): :param refspecs: see push() :param progress: see push()""" proc = self._git.fetch(url, refspecs, with_extended_output=True, as_process=True, v=True, **kwargs) - return get_fetch_info_from_stderr(self, proc, progress or RemoteProgress()) + return get_fetch_info_from_stderr(self, proc, CmdRemoteProgress(progress)) #} end transport db interface @@ -699,14 +774,14 @@ class CmdHighLevelRepository(HighLevelRepository): All remaining keyword arguments are given to the git-clone command For more information, see the respective method in HighLevelRepository""" - return self._clone(self.git, self.git_dir, path, progress or RemoteProgress(), **kwargs) + return self._clone(self.git, self.git_dir, path, CmdRemoteProgress(progress), **kwargs) @classmethod def clone_from(cls, url, to_path, progress = None, **kwargs): """ :param kwargs: see the ``clone`` method For more information, see the respective method in the HighLevelRepository""" - return cls._clone(cls.GitCls(os.getcwd()), url, to_path, progress or RemoteProgress(), **kwargs) + return cls._clone(cls.GitCls(os.getcwd()), url, to_path, CmdRemoteProgress(progress), **kwargs) def archive(self, ostream, treeish=None, prefix=None, **kwargs): """For all args see HighLevelRepository interface |