@@ -38,32 +38,46 @@ def _call_config(self, method, *args, **kwargs):
3838return getattr (self ._config ,method )(self ._section_name ,* args ,** kwargs )
3939
4040
41- class PushProgress (object ):
41+ class RemoteProgress (object ):
4242"""
4343Handler providing an interface to parse progress information emitted by git-push
44- and to dispatch callbacks allowing subclasses to react to the progress.
44+ andgit-fetch and to dispatch callbacks allowing subclasses to react to the progress.
4545"""
4646BEGIN ,END ,COUNTING ,COMPRESSING ,WRITING = [1 << x for x in range (5 ) ]
4747STAGE_MASK = BEGIN | END
4848OP_MASK = COUNTING | COMPRESSING | WRITING
4949
5050__slots__ = ("_cur_line" ,"_seen_ops" )
51- re_op_absolute = re .compile ("([\w\s]+):\s+()(\d+)()(.*)" )
52- re_op_relative = re .compile ("([\w\s]+):\s+(\d+)% \((\d+)/(\d+)\)(.*)" )
51+ re_op_absolute = re .compile ("(remote: )?( [\w\s]+):\s+()(\d+)()(.*)" )
52+ re_op_relative = re .compile ("(remote: )?( [\w\s]+):\s+(\d+)% \((\d+)/(\d+)\)(.*)" )
5353
5454def __init__ (self ):
5555self ._seen_ops = list ()
5656
5757def _parse_progress_line (self ,line ):
5858"""
5959Parse progress information from the given line as retrieved by git-push
60- """
60+ or git-fetch
61+ @return: list(line, ...) list of lines that could not be processed"""
6162# handle
6263# Counting objects: 4, done.
6364# Compressing objects: 50% (1/2) \rCompressing objects: 100% (2/2) \rCompressing objects: 100% (2/2), done.
6465self ._cur_line = line
6566sub_lines = line .split ('\r ' )
67+ failed_lines = list ()
6668for sline in sub_lines :
69+ # find esacpe characters and cut them away - regex will not work with
70+ # them as they are non-ascii. As git might expect a tty, it will send them
71+ last_valid_index = None
72+ for i ,c in enumerate (reversed (sline )):
73+ if ord (c )< 32 :
74+ # its a slice index
75+ last_valid_index = - i - 1
76+ # END character was non-ascii
77+ # END for each character in sline
78+ if last_valid_index is not None :
79+ sline = sline [:last_valid_index ]
80+ # END cut away invalid part
6781sline = sline .rstrip ()
6882
6983cur_count ,max_count = None ,None
@@ -73,11 +87,13 @@ def _parse_progress_line(self, line):
7387
7488if not match :
7589self .line_dropped (sline )
90+ failed_lines .append (sline )
7691continue
7792# END could not get match
7893
7994op_code = 0
80- op_name ,percent ,cur_count ,max_count ,message = match .groups ()
95+ remote ,op_name ,percent ,cur_count ,max_count ,message = match .groups ()
96+
8197# get operation id
8298if op_name == "Counting objects" :
8399op_code |= self .COUNTING
@@ -106,8 +122,8 @@ def _parse_progress_line(self, line):
106122# END end message handling
107123
108124self .update (op_code ,cur_count ,max_count ,message )
109-
110125# END for each sub line
126+ return failed_lines
111127
112128def line_dropped (self ,line ):
113129"""
@@ -574,38 +590,75 @@ def update(self, **kwargs):
574590self .repo .git .remote ("update" ,self .name )
575591return self
576592
577- def _get_fetch_info_from_stderr (self ,stderr ):
593+ def _digest_process_messages (self ,fh ,progress ):
594+ """Read progress messages from file-like object fh, supplying the respective
595+ progress messages to the progress instance.
596+ @return: list(line, ...) list of lines without linebreaks that did
597+ not contain progress information"""
598+ line_so_far = ''
599+ dropped_lines = list ()
600+ while True :
601+ char = fh .read (1 )
602+ if not char :
603+ break
604+
605+ if char in ('\r ' ,'\n ' ):
606+ dropped_lines .extend (progress ._parse_progress_line (line_so_far ))
607+ line_so_far = ''
608+ else :
609+ line_so_far += char
610+ # END process parsed line
611+ # END while file is not done reading
612+ return dropped_lines
613+
614+
615+ def _finalize_proc (self ,proc ):
616+ """Wait for the process (fetch, pull or push) and handle its errors accordingly"""
617+ try :
618+ proc .wait ()
619+ except GitCommandError ,e :
620+ # if a push has rejected items, the command has non-zero return status
621+ # a return status of 128 indicates a connection error - reraise the previous one
622+ if proc .poll ()== 128 :
623+ raise
624+ pass
625+ # END exception handling
626+
627+
628+ def _get_fetch_info_from_stderr (self ,proc ,progress ):
578629# skip first line as it is some remote info we are not interested in
579630output = IterableList ('name' )
580- err_info = stderr .splitlines ()[1 :]
631+
632+
633+ # lines which are no progress are fetch info lines
634+ # this also waits for the command to finish
635+ # Skip some progress lines that don't provide relevant information
636+ fetch_info_lines = list ()
637+ for line in self ._digest_process_messages (proc .stderr ,progress ):
638+ if line .startswith ('From' )or line .startswith ('remote: Total' ):
639+ continue
640+ fetch_info_lines .append (line )
641+ # END for each line
581642
582643# read head information
583644fp = open (os .path .join (self .repo .git_dir ,'FETCH_HEAD' ),'r' )
584645fetch_head_info = fp .readlines ()
585646fp .close ()
586647
648+ assert len (fetch_info_lines )== len (fetch_head_info )
649+
587650output .extend (FetchInfo ._from_line (self .repo ,err_line ,fetch_line )
588- for err_line ,fetch_line in zip (err_info ,fetch_head_info ))
651+ for err_line ,fetch_line in zip (fetch_info_lines ,fetch_head_info ))
652+
653+ self ._finalize_proc (proc )
589654return output
590655
591656def _get_push_info (self ,proc ,progress ):
592657# read progress information from stderr
593658# we hope stdout can hold all the data, it should ...
594659# read the lines manually as it will use carriage returns between the messages
595660# to override the previous one. This is why we read the bytes manually
596- line_so_far = ''
597- while True :
598- char = proc .stderr .read (1 )
599- if not char :
600- break
601-
602- if char in ('\r ' ,'\n ' ):
603- progress ._parse_progress_line (line_so_far )
604- line_so_far = ''
605- else :
606- line_so_far += char
607- # END process parsed line
608- # END for each progress line
661+ self ._digest_process_messages (proc .stderr ,progress )
609662
610663output = IterableList ('name' )
611664for line in proc .stdout .readlines ():
@@ -616,19 +669,12 @@ def _get_push_info(self, proc, progress):
616669pass
617670# END exception handling
618671# END for each line
619- try :
620- proc .wait ()
621- except GitCommandError ,e :
622- # if a push has rejected items, the command has non-zero return status
623- # a return status of 128 indicates a connection error - reraise the previous one
624- if proc .poll ()== 128 :
625- raise
626- pass
627- # END exception handling
672+
673+ self ._finalize_proc (proc )
628674return output
629675
630676
631- def fetch (self ,refspec = None ,** kwargs ):
677+ def fetch (self ,refspec = None ,progress = None , ** kwargs ):
632678"""
633679Fetch the latest changes for this remote
634680
@@ -643,7 +689,9 @@ def fetch(self, refspec=None, **kwargs):
643689See also git-push(1).
644690
645691Taken from the git manual
646-
692+ ``progress``
693+ See 'push' method
694+
647695``**kwargs``
648696Additional arguments to be passed to git-fetch
649697
@@ -655,25 +703,28 @@ def fetch(self, refspec=None, **kwargs):
655703As fetch does not provide progress information to non-ttys, we cannot make
656704it available here unfortunately as in the 'push' method.
657705"""
658- status , stdout , stderr = self .repo .git .fetch (self ,refspec ,with_extended_output = True ,v = True ,** kwargs )
659- return self ._get_fetch_info_from_stderr (stderr )
706+ proc = self .repo .git .fetch (self ,refspec ,with_extended_output = True , as_process = True ,v = True ,** kwargs )
707+ return self ._get_fetch_info_from_stderr (proc , progress or RemoteProgress () )
660708
661- def pull (self ,refspec = None ,** kwargs ):
709+ def pull (self ,refspec = None ,progress = None , ** kwargs ):
662710"""
663711Pull changes from the given branch, being the same as a fetch followed
664712by a merge of branch with your local branch.
665713
666714``refspec``
667715see 'fetch' method
716+
717+ ``progress``
718+ see 'push' method
668719
669720``**kwargs``
670721Additional arguments to be passed to git-pull
671722
672723Returns
673724Please see 'fetch' method
674725"""
675- status , stdout , stderr = self .repo .git .pull (self ,refspec ,with_extended_output = True ,v = True ,** kwargs )
676- return self ._get_fetch_info_from_stderr (stderr )
726+ proc = self .repo .git .pull (self ,refspec ,with_extended_output = True , as_process = True ,v = True ,** kwargs )
727+ return self ._get_fetch_info_from_stderr (proc , progress or RemoteProgress () )
677728
678729def push (self ,refspec = None ,progress = None ,** kwargs ):
679730"""
@@ -683,7 +734,7 @@ def push(self, refspec=None, progress=None, **kwargs):
683734see 'fetch' method
684735
685736``progress``
686- Instance of typePushProgress allowing the caller to receive
737+ Instance of typeRemoteProgress allowing the caller to receive
687738progress information until the method returns.
688739If None, progress information will be discarded
689740
@@ -700,7 +751,7 @@ def push(self, refspec=None, progress=None, **kwargs):
700751be null.
701752"""
702753proc = self .repo .git .push (self ,refspec ,porcelain = True ,as_process = True ,** kwargs )
703- return self ._get_push_info (proc ,progress or PushProgress ())
754+ return self ._get_push_info (proc ,progress or RemoteProgress ())
704755
705756@property
706757def config_reader (self ):