|
7 | 7 | # Module implementing a remote object allowing easy access to git remotes |
8 | 8 |
|
9 | 9 | from exc import GitCommandError |
10 | | -from objects import Commit |
11 | 10 | from ConfigParser import NoOptionError |
12 | 11 | from config import SectionConstraint |
13 | 12 |
|
14 | 13 | from git.util import ( |
15 | 14 | LazyMixin, |
16 | 15 | Iterable, |
17 | | - IterableList |
| 16 | + IterableList, |
| 17 | + RemoteProgress |
18 | 18 | ) |
19 | 19 |
|
20 | 20 | from refs import ( |
|
33 | 33 |
|
34 | 34 | __all__ = ('RemoteProgress', 'PushInfo', 'FetchInfo', 'Remote') |
35 | 35 |
|
36 | | -class RemoteProgress(object): |
37 | | - """ |
38 | | - Handler providing an interface to parse progress information emitted by git-push |
39 | | - and git-fetch and to dispatch callbacks allowing subclasses to react to the progress. |
40 | | - """ |
41 | | - BEGIN, END, COUNTING, COMPRESSING, WRITING = [ 1 << x for x in range(5) ] |
42 | | - STAGE_MASK = BEGIN|END |
43 | | - OP_MASK = COUNTING|COMPRESSING|WRITING |
44 | | - |
45 | | - __slots__ = ("_cur_line", "_seen_ops") |
46 | | - re_op_absolute = re.compile("(remote: )?([\w\s]+):\s+()(\d+)()(.*)") |
47 | | - re_op_relative = re.compile("(remote: )?([\w\s]+):\s+(\d+)% \((\d+)/(\d+)\)(.*)") |
48 | | - |
49 | | - def __init__(self): |
50 | | - self._seen_ops = list() |
51 | | - |
52 | | - def _parse_progress_line(self, line): |
53 | | - """Parse progress information from the given line as retrieved by git-push |
54 | | - or git-fetch |
55 | | - |
56 | | - :return: list(line, ...) list of lines that could not be processed""" |
57 | | - # handle |
58 | | - # Counting objects: 4, done. |
59 | | - # Compressing objects: 50% (1/2) \rCompressing objects: 100% (2/2) \rCompressing objects: 100% (2/2), done. |
60 | | - self._cur_line = line |
61 | | - sub_lines = line.split('\r') |
62 | | - failed_lines = list() |
63 | | - for sline in sub_lines: |
64 | | - # find esacpe characters and cut them away - regex will not work with |
65 | | - # them as they are non-ascii. As git might expect a tty, it will send them |
66 | | - last_valid_index = None |
67 | | - for i,c in enumerate(reversed(sline)): |
68 | | - if ord(c) < 32: |
69 | | - # its a slice index |
70 | | - last_valid_index = -i-1 |
71 | | - # END character was non-ascii |
72 | | - # END for each character in sline |
73 | | - if last_valid_index is not None: |
74 | | - sline = sline[:last_valid_index] |
75 | | - # END cut away invalid part |
76 | | - sline = sline.rstrip() |
77 | | - |
78 | | - cur_count, max_count = None, None |
79 | | - match = self.re_op_relative.match(sline) |
80 | | - if match is None: |
81 | | - match = self.re_op_absolute.match(sline) |
82 | | - |
83 | | - if not match: |
84 | | - self.line_dropped(sline) |
85 | | - failed_lines.append(sline) |
86 | | - continue |
87 | | - # END could not get match |
88 | | - |
89 | | - op_code = 0 |
90 | | - remote, op_name, percent, cur_count, max_count, message = match.groups() |
91 | | - |
92 | | - # get operation id |
93 | | - if op_name == "Counting objects": |
94 | | - op_code |= self.COUNTING |
95 | | - elif op_name == "Compressing objects": |
96 | | - op_code |= self.COMPRESSING |
97 | | - elif op_name == "Writing objects": |
98 | | - op_code |= self.WRITING |
99 | | - else: |
100 | | - raise ValueError("Operation name %r unknown" % op_name) |
101 | | - |
102 | | - # figure out stage |
103 | | - if op_code not in self._seen_ops: |
104 | | - self._seen_ops.append(op_code) |
105 | | - op_code |= self.BEGIN |
106 | | - # END begin opcode |
107 | | - |
108 | | - if message is None: |
109 | | - message = '' |
110 | | - # END message handling |
111 | | - |
112 | | - message = message.strip() |
113 | | - done_token = ', done.' |
114 | | - if message.endswith(done_token): |
115 | | - op_code |= self.END |
116 | | - message = message[:-len(done_token)] |
117 | | - # END end message handling |
118 | | - |
119 | | - self.update(op_code, cur_count, max_count, message) |
120 | | - # END for each sub line |
121 | | - return failed_lines |
122 | | - |
123 | | - def line_dropped(self, line): |
124 | | - """Called whenever a line could not be understood and was therefore dropped.""" |
125 | | - pass |
126 | | - |
127 | | - def update(self, op_code, cur_count, max_count=None, message=''): |
128 | | - """Called whenever the progress changes |
129 | | - |
130 | | - :param op_code: |
131 | | - Integer allowing to be compared against Operation IDs and stage IDs. |
132 | | - |
133 | | - Stage IDs are BEGIN and END. BEGIN will only be set once for each Operation |
134 | | - ID as well as END. It may be that BEGIN and END are set at once in case only |
135 | | - one progress message was emitted due to the speed of the operation. |
136 | | - Between BEGIN and END, none of these flags will be set |
137 | | - |
138 | | - Operation IDs are all held within the OP_MASK. Only one Operation ID will |
139 | | - be active per call. |
140 | | - :param cur_count: Current absolute count of items |
141 | | - |
142 | | - :param max_count: |
143 | | - The maximum count of items we expect. It may be None in case there is |
144 | | - no maximum number of items or if it is (yet) unknown. |
145 | | - |
146 | | - :param message: |
147 | | - In case of the 'WRITING' operation, it contains the amount of bytes |
148 | | - transferred. It may possibly be used for other purposes as well. |
149 | | - |
150 | | - You may read the contents of the current line in self._cur_line""" |
151 | | - pass |
152 | | - |
153 | 36 |
|
154 | 37 | class PushInfo(object): |
155 | 38 | """ |
|
0 commit comments