@@ -43,24 +43,74 @@ class PushProgress(object):
4343 Handler providing an interface to parse progress information emitted by git-push
4444 and to dispatch callbacks allowing subclasses to react to the progress.
4545 """
46- BEGIN , END , COUNTING , COMPRESSING , WRITING = [ 1 << x for x in range (1 , 6 ) ]
46+ BEGIN , END , COUNTING , COMPRESSING , WRITING = [ 1 << x for x in range (5 ) ]
4747 STAGE_MASK = BEGIN | END
4848 OP_MASK = COUNTING | COMPRESSING | WRITING
4949
50- __slots__ = "_cur_line"
50+ __slots__ = ("_cur_line" , "_seen_ops" )
51+ re_op_absolute = re .compile ("([\w\s]+):\s+()(\d+)()(, done\.)?\s*" )
52+ re_op_relative = re .compile ("([\w\s]+):\s+(\d+)% \((\d+)/(\d+)\)(,.* done\.)?$" )
53+
54+ def __init__ (self ):
55+ self ._seen_ops = list ()
5156
5257 def _parse_progress_line (self , line ):
5358 """
5459 Parse progress information from the given line as retrieved by git-push
5560 """
61+ # handle
62+ # Counting objects: 4, done.
63+ # Compressing objects: 50% (1/2) \rCompressing objects: 100% (2/2) \rCompressing objects: 100% (2/2), done.
5664 self ._cur_line = line
65+ sub_lines = line .split ('\r ' )
66+ for sline in sub_lines :
67+ sline = sline .rstrip ()
68+
69+ cur_count , max_count = None , None
70+ match = self .re_op_relative .match (sline )
71+ if match is None :
72+ match = self .re_op_absolute .match (sline )
73+
74+ if not match :
75+ self .line_dropped (sline )
76+ continue
77+ # END could not get match
78+
79+ op_code = 0
80+ op_name , percent , cur_count , max_count , done = match .groups ()
81+ # get operation id
82+ if op_name == "Counting objects" :
83+ op_code |= self .COUNTING
84+ elif op_name == "Compressing objects" :
85+ op_code |= self .COMPRESSING
86+ elif op_name == "Writing objects" :
87+ op_code |= self .WRITING
88+ else :
89+ raise ValueError ("Operation name %r unknown" % op_name )
90+
91+ # figure out stage
92+ if op_code not in self ._seen_ops :
93+ self ._seen_ops .append (op_code )
94+ op_code |= self .BEGIN
95+ # END begin opcode
96+
97+ message = ''
98+ if done is not None and 'done.' in done :
99+ op_code |= self .END
100+ message = done .replace ( ", done." , "" )[2 :]
101+ # END end flag handling
102+
103+ self .update (op_code , cur_count , max_count , message )
104+
105+ # END for each sub line
57106
58107 def line_dropped (self , line ):
59108 """
60109 Called whenever a line could not be understood and was therefore dropped.
61110 """
111+ pass
62112
63- def update (self , op_code , cur_count , max_count = None ):
113+ def update (self , op_code , cur_count , max_count = None , message = '' ):
64114 """
65115 Called whenever the progress changes
66116
@@ -81,39 +131,106 @@ def update(self, op_code, cur_count, max_count=None):
81131 ``max_count``
82132 The maximum count of items we expect. It may be None in case there is
83133 no maximum number of items or if it is (yet) unknown.
84-
134+
135+ ``message``
136+ In case of the 'WRITING' operation, it contains the amount of bytes
137+ transferred. It may possibly be used for other purposes as well.
85138 You may read the contents of the current line in self._cur_line
86139 """
140+ pass
87141
88142
89143class PushInfo (object ):
90144 """
91145 Carries information about the result of a push operation of a single head::
92- todo
93-
146+ info = remote.push()[0]
147+ info.flags # bitflags providing more information about the result
148+ info.local_ref # Reference pointing to the local reference that was pushed
149+ info.remote_ref_string # path to the remote reference located on the remote side
150+ info.remote_ref # Remote Reference on the local side corresponding to
151+ # the remote_ref_string. It can be a TagReference as well.
152+ info.old_commit # commit at which the remote_ref was standing before we pushed
153+ # it to local_ref.commit. Will be None if an error was indicated
94154 """
95- __slots__ = ('local_ref' , 'remote_ref ' )
155+ __slots__ = ('local_ref' , 'remote_ref_string' , 'flags' , 'old_commit' , '_remote ' )
96156
97157 NO_MATCH , REJECTED , REMOTE_REJECTED , REMOTE_FAILURE , DELETED , \
98- FORCED_UPDATE , FAST_FORWARD , ERROR = [ 1 << x for x in range (1 , 9 ) ]
158+ FORCED_UPDATE , FAST_FORWARD , UP_TO_DATE , ERROR = [ 1 << x for x in range (9 ) ]
99159
100160 _flag_map = { 'X' : NO_MATCH , '-' : DELETED , '*' : 0 ,
101- '+' : FORCED_UPDATE , ' ' : FAST_FORWARD }
161+ '+' : FORCED_UPDATE , ' ' : FAST_FORWARD ,
162+ '=' : UP_TO_DATE , '!' : ERROR }
102163
103- def __init__ (self , local_ref , remote_ref ):
164+ def __init__ (self , flags , local_ref , remote_ref_string , remote , old_commit = None ):
104165 """
105166 Initialize a new instance
106167 """
168+ self .flags = flags
107169 self .local_ref = local_ref
108- self .remote_ref = remote_ref
170+ self .remote_ref_string = remote_ref_string
171+ self ._remote = remote
172+ self .old_commit = old_commit
173+
174+ @property
175+ def remote_ref (self ):
176+ """
177+ Returns
178+ Remote Reference or TagReference in the local repository corresponding
179+ to the remote_ref_string kept in this instance.
180+ """
181+ # translate heads to a local remote, tags stay as they are
182+ if self .remote_ref_string .startswith ("refs/tags" ):
183+ return TagReference (self ._remote .repo , self .remote_ref_string )
184+ elif self .remote_ref_string .startswith ("refs/heads" ):
185+ remote_ref = Reference (self ._remote .repo , self .remote_ref_string )
186+ return RemoteReference (self ._remote .repo , "refs/remotes/%s/%s" % (str (self ._remote ), remote_ref .name ))
187+ else :
188+ raise ValueError ("Could not handle remote ref: %r" % self .remote_ref_string )
189+ # END
109190
110191 @classmethod
111- def _from_line (cls , repo , line ):
192+ def _from_line (cls , remote , line ):
112193 """
113194 Create a new PushInfo instance as parsed from line which is expected to be like
114195 c refs/heads/master:refs/heads/master 05d2687..1d0568e
115196 """
116- raise NotImplementedError ("todo" )
197+ control_character , from_to , summary = line .split ('\t ' , 3 )
198+ flags = 0
199+
200+ # control character handling
201+ try :
202+ flags |= cls ._flag_map [ control_character ]
203+ except KeyError :
204+ raise ValueError ("Control Character %r unknown as parsed from line %r" % (control_character , line ))
205+ # END handle control character
206+
207+ # from_to handling
208+ from_ref_string , to_ref_string = from_to .split (':' )
209+ from_ref = Reference .from_path (remote .repo , from_ref_string )
210+
211+ # commit handling, could be message or commit info
212+ old_commit = None
213+ if summary .startswith ('[' ):
214+ if "[rejected]" in summary :
215+ flags |= cls .REJECTED
216+ elif "[remote rejected]" in summary :
217+ flags |= cls .REMOTE_REJECTED
218+ elif "[remote failure]" in summary :
219+ flags |= cls .REMOTE_FAILURE
220+ elif "[no match]" in summary :
221+ flags |= cls .ERROR
222+ # uptodate encoded in control character
223+ else :
224+ # fast-forward or forced update - was encoded in control character,
225+ # but we parse the old and new commit
226+ split_token = "..."
227+ if control_character == " " :
228+ split_token = ".."
229+ old_sha , new_sha = summary .split (' ' )[0 ].split (split_token )
230+ old_commit = Commit (remote .repo , old_sha )
231+ # END message handling
232+
233+ return PushInfo (flags , from_ref , to_ref_string , remote , old_commit )
117234
118235
119236class FetchInfo (object ):
@@ -133,7 +250,7 @@ class FetchInfo(object):
133250 __slots__ = ('ref' ,'commit_before_forced_update' , 'flags' , 'note' )
134251
135252 HEAD_UPTODATE , REJECTED , FORCED_UPDATE , FAST_FORWARD , NEW_TAG , \
136- TAG_UPDATE , NEW_HEAD , ERROR = [ 1 << x for x in range (1 , 9 ) ]
253+ TAG_UPDATE , NEW_HEAD , ERROR = [ 1 << x for x in range (8 ) ]
137254 # %c %-*s %-*s -> %s (%s)
138255 re_fetch_result = re .compile ("^\s*(.) (\[?[\w\s\.]+\]?)\s+(.+) -> ([/\w_\.-]+)( \(.*\)?$)?" )
139256
@@ -446,12 +563,12 @@ def _get_fetch_info_from_stderr(self, stderr):
446563 def _get_push_info (self , proc , progress ):
447564 # read progress information from stderr
448565 # we hope stdout can hold all the data, it should ...
449- for line in proc .stderr .readline ():
450- progress ._parse_progress_line (line )
566+ for line in proc .stderr .readlines ():
567+ progress ._parse_progress_line (line . rstrip () )
451568 # END for each progress line
452569
453570 output = IterableList ('name' )
454- output .extend (PushInfo ._from_line (self . repo , line ) for line in proc .stdout .readlines ())
571+ output .extend (PushInfo ._from_line (self , line ) for line in proc .stdout .readlines ())
455572 proc .wait ()
456573 return output
457574
0 commit comments