1414from git .util import (
1515 bin_to_hex ,
1616 hex_to_bin ,
17- RemoteProgress ,
1817 isfile ,
1918 join_path ,
2019 join ,
2120 Actor ,
22- IterableList
21+ IterableList ,
2322 )
24- from git .db .interface import FetchInfo as GitdbFetchInfo
25- from git .db .interface import PushInfo as GitdbPushInfo
2623from git .db .interface import (
24+ FetchInfo ,
25+ PushInfo ,
2726 HighLevelRepository ,
28- TransportDB
27+ TransportDB ,
28+ RemoteProgress
2929 )
3030from git .cmd import Git
3131from git .refs import (
4141import sys
4242
4343
44- __all__ = ('CmdTransportMixin' , 'RemoteProgress ' , 'GitCommandMixin ' ,
45- 'CmdObjectDBRMixin' , 'CmdHighLevelRepository' )
44+ __all__ = ('CmdTransportMixin' , 'GitCommandMixin ' , 'CmdPushInfo' , 'CmdFetchInfo ' ,
45+ 'CmdRemoteProgress' , ' CmdObjectDBRMixin' , 'CmdHighLevelRepository' )
4646
4747
4848#{ Utilities
@@ -115,13 +115,13 @@ def get_fetch_info_from_stderr(repo, proc, progress):
115115
116116 assert len (fetch_info_lines ) == len (fetch_head_info )
117117
118- output .extend (FetchInfo ._from_line (repo , err_line , fetch_line )
118+ output .extend (CmdFetchInfo ._from_line (repo , err_line , fetch_line )
119119 for err_line ,fetch_line in zip (fetch_info_lines , fetch_head_info ))
120120
121121 finalize_process (proc )
122122 return output
123123
124- def get_push_info (repo , proc , progress ):
124+ def get_push_info (repo , remotename_or_url , proc , progress ):
125125 # read progress information from stderr
126126 # we hope stdout can hold all the data, it should ...
127127 # read the lines manually as it will use carriage returns between the messages
@@ -131,7 +131,7 @@ def get_push_info(repo, proc, progress):
131131 output = IterableList ('name' )
132132 for line in proc .stdout .readlines ():
133133 try :
134- output .append (PushInfo ._from_line (repo , line ))
134+ output .append (CmdPushInfo ._from_line (repo , remotename_or_url , line ))
135135 except ValueError :
136136 # if an error happens, additional info is given which we cannot parse
137137 pass
@@ -143,37 +143,119 @@ def get_push_info(repo, proc, progress):
143143
144144#} END utilities
145145
146- class PushInfo ( GitdbPushInfo ):
146+ class CmdRemoteProgress ( RemoteProgress ):
147147 """
148- Carries information about the result of a push operation of a single head::
149-
150- info = remote.push()[0]
151- info.flags # bitflags providing more information about the result
152- info.local_ref # Reference pointing to the local reference that was pushed
153- # It is None if the ref was deleted.
154- info.remote_ref_string # path to the remote reference located on the remote side
155- info.remote_ref # Remote Reference on the local side corresponding to
156- # the remote_ref_string. It can be a TagReference as well.
157- info.old_commit_binsha # binary sha at which the remote_ref was standing before we pushed
158- # it to local_ref.commit. Will be None if an error was indicated
159- info.summary # summary line providing human readable english text about the push
160- """
161- __slots__ = ('local_ref' , 'remote_ref_string' , 'flags' , 'old_commit_binsha' , '_remote' , 'summary' )
148+ A Remote progress implementation taking a user derived progress to call the
149+ respective methods on.
150+ """
151+ __slots__ = ("_seen_ops" , '_progress' )
152+ re_op_absolute = re .compile ("(remote: )?([\w\s]+):\s+()(\d+)()(.*)" )
153+ re_op_relative = re .compile ("(remote: )?([\w\s]+):\s+(\d+)% \((\d+)/(\d+)\)(.*)" )
154+
155+ def __init__ (self , progress_instance = None ):
156+ self ._seen_ops = list ()
157+ if progress_instance is None :
158+ progress_instance = RemoteProgress ()
159+ #END assure proper instance
160+ self ._progress = progress_instance
161+
162+ def _parse_progress_line (self , line ):
163+ """Parse progress information from the given line as retrieved by git-push
164+ or git-fetch
165+
166+ Call the own update(), __call__() and line_dropped() methods according
167+ to the parsed result.
168+
169+ :return: list(line, ...) list of lines that could not be processed"""
170+ # handle
171+ # Counting objects: 4, done.
172+ # Compressing objects: 50% (1/2) \rCompressing objects: 100% (2/2) \rCompressing objects: 100% (2/2), done.
173+ sub_lines = line .split ('\r ' )
174+ failed_lines = list ()
175+ for sline in sub_lines :
176+ # find esacpe characters and cut them away - regex will not work with
177+ # them as they are non-ascii. As git might expect a tty, it will send them
178+ last_valid_index = None
179+ for i ,c in enumerate (reversed (sline )):
180+ if ord (c ) < 32 :
181+ # its a slice index
182+ last_valid_index = - i - 1
183+ # END character was non-ascii
184+ # END for each character in sline
185+ if last_valid_index is not None :
186+ sline = sline [:last_valid_index ]
187+ # END cut away invalid part
188+ sline = sline .rstrip ()
189+
190+ cur_count , max_count = None , None
191+ match = self .re_op_relative .match (sline )
192+ if match is None :
193+ match = self .re_op_absolute .match (sline )
194+
195+ if not match :
196+ self ._progress .line_dropped (sline )
197+ failed_lines .append (sline )
198+ continue
199+ # END could not get match
200+
201+ op_code = 0
202+ remote , op_name , percent , cur_count , max_count , message = match .groups ()
203+
204+ # get operation id
205+ if op_name == "Counting objects" :
206+ op_code |= self .COUNTING
207+ elif op_name == "Compressing objects" :
208+ op_code |= self .COMPRESSING
209+ elif op_name == "Writing objects" :
210+ op_code |= self .WRITING
211+ else :
212+ raise ValueError ("Operation name %r unknown" % op_name )
213+
214+ # figure out stage
215+ if op_code not in self ._seen_ops :
216+ self ._seen_ops .append (op_code )
217+ op_code |= self .BEGIN
218+ # END begin opcode
219+
220+ if message is None :
221+ message = ''
222+ # END message handling
223+
224+ message = message .strip ()
225+ done_token = ', done.'
226+ if message .endswith (done_token ):
227+ op_code |= self .END
228+ message = message [:- len (done_token )]
229+ # END end message handling
230+
231+ self ._progress .update (op_code , cur_count , max_count , message , line )
232+ self ._progress (message , line )
233+ # END for each sub line
234+ return failed_lines
235+
236+
237+ class CmdPushInfo (PushInfo ):
238+ """
239+ Pure Python implementation of a PushInfo interface
240+ """
241+ __slots__ = ('local_ref' , 'remote_ref_string' , 'flags' , 'old_commit_binsha' ,
242+ '_remotename_or_url' , 'repo' , 'summary' )
162243
163- _flag_map = { 'X' : GitdbPushInfo .NO_MATCH ,
164- '-' : GitdbPushInfo .DELETED , '*' : 0 ,
165- '+' : GitdbPushInfo .FORCED_UPDATE ,
166- ' ' : GitdbPushInfo .FAST_FORWARD ,
167- '=' : GitdbPushInfo .UP_TO_DATE ,
168- '!' : GitdbPushInfo .ERROR }
244+ _flag_map = { 'X' : PushInfo .NO_MATCH ,
245+ '-' : PushInfo .DELETED , '*' : 0 ,
246+ '+' : PushInfo .FORCED_UPDATE ,
247+ ' ' : PushInfo .FAST_FORWARD ,
248+ '=' : PushInfo .UP_TO_DATE ,
249+ '!' : PushInfo .ERROR }
169250
170- def __init__ (self , flags , local_ref , remote_ref_string , remote , old_commit_binsha = None ,
251+ def __init__ (self , flags , local_ref , remote_ref_string , repo , remotename_or_url , old_commit_binsha = None ,
171252 summary = '' ):
172253 """ Initialize a new instance """
173254 self .flags = flags
174255 self .local_ref = local_ref
256+ self .repo = repo
175257 self .remote_ref_string = remote_ref_string
176- self ._remote = remote
258+ self ._remotename_or_url = remotename_or_url
177259 self .old_commit_binsha = old_commit_binsha
178260 self .summary = summary
179261
@@ -185,16 +267,20 @@ def remote_ref(self):
185267 to the remote_ref_string kept in this instance."""
186268 # translate heads to a local remote, tags stay as they are
187269 if self .remote_ref_string .startswith ("refs/tags" ):
188- return TagReference (self ._remote . repo , self .remote_ref_string )
270+ return TagReference (self .repo , self .remote_ref_string )
189271 elif self .remote_ref_string .startswith ("refs/heads" ):
190- remote_ref = Reference (self ._remote .repo , self .remote_ref_string )
191- return RemoteReference (self ._remote .repo , "refs/remotes/%s/%s" % (str (self ._remote ), remote_ref .name ))
272+ remote_ref = Reference (self .repo , self .remote_ref_string )
273+ if '/' in self ._remotename_or_url :
274+ 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 )
275+ return remote_ref
276+ #END assert correct input
277+ return RemoteReference (self .repo , "refs/remotes/%s/%s" % (str (self ._remotename_or_url ), remote_ref .name ))
192278 else :
193279 raise ValueError ("Could not handle remote ref: %r" % self .remote_ref_string )
194280 # END
195281
196282 @classmethod
197- def _from_line (cls , remote , line ):
283+ def _from_line (cls , repo , remotename_or_url , line ):
198284 """Create a new PushInfo instance as parsed from line which is expected to be like
199285 refs/heads/master:refs/heads/master 05d2687..1d0568e"""
200286 control_character , from_to , summary = line .split ('\t ' , 3 )
@@ -212,7 +298,7 @@ def _from_line(cls, remote, line):
212298 if flags & cls .DELETED :
213299 from_ref = None
214300 else :
215- from_ref = Reference .from_path (remote . repo , from_ref_string )
301+ from_ref = Reference .from_path (repo , from_ref_string )
216302
217303 # commit handling, could be message or commit info
218304 old_commit_binsha = None
@@ -237,38 +323,27 @@ def _from_line(cls, remote, line):
237323 if control_character == " " :
238324 split_token = ".."
239325 old_sha , new_sha = summary .split (' ' )[0 ].split (split_token )
240- # have to use constructor here as the sha usually is abbreviated
241- old_commit_binsha = remote .repo .commit (old_sha )
326+ old_commit_binsha = repo .resolve (old_sha )
242327 # END message handling
243328
244- return PushInfo (flags , from_ref , to_ref_string , remote , old_commit_binsha , summary )
329+ return cls (flags , from_ref , to_ref_string , repo , remotename_or_url , old_commit_binsha , summary )
245330
246331
247- class FetchInfo ( GitdbFetchInfo ):
332+ class CmdFetchInfo ( FetchInfo ):
248333 """
249- Carries information about the results of a fetch operation of a single head::
250-
251- info = remote.fetch()[0]
252- info.ref # Symbolic Reference or RemoteReference to the changed
253- # remote head or FETCH_HEAD
254- info.flags # additional flags to be & with enumeration members,
255- # i.e. info.flags & info.REJECTED
256- # is 0 if ref is FETCH_HEAD
257- info.note # additional notes given by git-fetch intended for the user
258- info.old_commit_binsha # if info.flags & info.FORCED_UPDATE|info.FAST_FORWARD,
259- # field is set to the previous location of ref, otherwise None
334+ Pure python implementation of a FetchInfo interface
260335 """
261336 __slots__ = ('ref' ,'old_commit_binsha' , 'flags' , 'note' )
262337
263338 # %c %-*s %-*s -> %s (%s)
264339 re_fetch_result = re .compile ("^\s*(.) (\[?[\w\s\.]+\]?)\s+(.+) -> ([/\w_\+\.-]+)( \(.*\)?$)?" )
265340
266- _flag_map = { '!' : GitdbFetchInfo .ERROR ,
267- '+' : GitdbFetchInfo .FORCED_UPDATE ,
268- '-' : GitdbFetchInfo .TAG_UPDATE ,
341+ _flag_map = { '!' : FetchInfo .ERROR ,
342+ '+' : FetchInfo .FORCED_UPDATE ,
343+ '-' : FetchInfo .TAG_UPDATE ,
269344 '*' : 0 ,
270- '=' : GitdbFetchInfo .HEAD_UPTODATE ,
271- ' ' : GitdbFetchInfo .FAST_FORWARD }
345+ '=' : FetchInfo .HEAD_UPTODATE ,
346+ ' ' : FetchInfo .FAST_FORWARD }
272347
273348 def __init__ (self , ref , flags , note = '' , old_commit_binsha = None ):
274349 """
@@ -295,7 +370,7 @@ def commit(self):
295370 @classmethod
296371 def _from_line (cls , repo , line , fetch_line ):
297372 """Parse information from the given line as returned by git-fetch -v
298- and return a new FetchInfo object representing this information.
373+ and return a new CmdFetchInfo object representing this information.
299374
300375 We can handle a line as follows
301376 "%c %-*s %-*s -> %s%s"
@@ -366,7 +441,7 @@ def _from_line(cls, repo, line, fetch_line):
366441 split_token = '...'
367442 if control_character == ' ' :
368443 split_token = split_token [:- 1 ]
369- old_commit_binsha = repo .rev_parse (operation .split (split_token )[0 ])
444+ old_commit_binsha = repo .resolve (operation .split (split_token )[0 ])
370445 # END handle refspec
371446 # END reference flag handling
372447
@@ -443,7 +518,7 @@ def push(self, url, refspecs=None, progress=None, **kwargs):
443518 :param progress: RemoteProgress derived instance or None
444519 :param **kwargs: Additional arguments to be passed to the git-push process"""
445520 proc = self ._git .push (url , refspecs , porcelain = True , as_process = True , ** kwargs )
446- return get_push_info (self , proc , progress or RemoteProgress ( ))
521+ return get_push_info (self , url , proc , CmdRemoteProgress ( progress ))
447522
448523 def pull (self , url , refspecs = None , progress = None , ** kwargs ):
449524 """Fetch and merge the given refspecs.
@@ -453,15 +528,15 @@ def pull(self, url, refspecs=None, progress=None, **kwargs):
453528 :param refspecs: see push()
454529 :param progress: see push()"""
455530 proc = self ._git .pull (url , refspecs , with_extended_output = True , as_process = True , v = True , ** kwargs )
456- return get_fetch_info_from_stderr (self , proc , progress or RemoteProgress ( ))
531+ return get_fetch_info_from_stderr (self , proc , CmdRemoteProgress ( progress ))
457532
458533 def fetch (self , url , refspecs = None , progress = None , ** kwargs ):
459534 """Fetch the latest changes
460535 :param url: may be a remote name or a url
461536 :param refspecs: see push()
462537 :param progress: see push()"""
463538 proc = self ._git .fetch (url , refspecs , with_extended_output = True , as_process = True , v = True , ** kwargs )
464- return get_fetch_info_from_stderr (self , proc , progress or RemoteProgress ( ))
539+ return get_fetch_info_from_stderr (self , proc , CmdRemoteProgress ( progress ))
465540
466541 #} end transport db interface
467542
@@ -699,14 +774,14 @@ def clone(self, path, progress = None, **kwargs):
699774 All remaining keyword arguments are given to the git-clone command
700775
701776 For more information, see the respective method in HighLevelRepository"""
702- return self ._clone (self .git , self .git_dir , path , progress or RemoteProgress ( ), ** kwargs )
777+ return self ._clone (self .git , self .git_dir , path , CmdRemoteProgress ( progress ), ** kwargs )
703778
704779 @classmethod
705780 def clone_from (cls , url , to_path , progress = None , ** kwargs ):
706781 """
707782 :param kwargs: see the ``clone`` method
708783 For more information, see the respective method in the HighLevelRepository"""
709- return cls ._clone (cls .GitCls (os .getcwd ()), url , to_path , progress or RemoteProgress ( ), ** kwargs )
784+ return cls ._clone (cls .GitCls (os .getcwd ()), url , to_path , CmdRemoteProgress ( progress ), ** kwargs )
710785
711786 def archive (self , ostream , treeish = None , prefix = None , ** kwargs ):
712787 """For all args see HighLevelRepository interface
0 commit comments