@@ -38,32 +38,46 @@ def _call_config(self, method, *args, **kwargs):
3838 return getattr (self ._config , method )(self ._section_name , * args , ** kwargs )
3939
4040
41- class PushProgress (object ):
41+ class RemoteProgress (object ):
4242 """
4343 Handler providing an interface to parse progress information emitted by git-push
44- and to dispatch callbacks allowing subclasses to react to the progress.
44+ and git-fetch and to dispatch callbacks allowing subclasses to react to the progress.
4545 """
4646 BEGIN , END , COUNTING , COMPRESSING , WRITING = [ 1 << x for x in range (5 ) ]
4747 STAGE_MASK = BEGIN | END
4848 OP_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
5454 def __init__ (self ):
5555 self ._seen_ops = list ()
5656
5757 def _parse_progress_line (self , line ):
5858 """
5959 Parse 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.
6465 self ._cur_line = line
6566 sub_lines = line .split ('\r ' )
67+ failed_lines = list ()
6668 for 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
6781 sline = sline .rstrip ()
6882
6983 cur_count , max_count = None , None
@@ -73,11 +87,13 @@ def _parse_progress_line(self, line):
7387
7488 if not match :
7589 self .line_dropped (sline )
90+ failed_lines .append (sline )
7691 continue
7792 # END could not get match
7893
7994 op_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
8298 if op_name == "Counting objects" :
8399 op_code |= self .COUNTING
@@ -106,8 +122,8 @@ def _parse_progress_line(self, line):
106122 # END end message handling
107123
108124 self .update (op_code , cur_count , max_count , message )
109-
110125 # END for each sub line
126+ return failed_lines
111127
112128 def line_dropped (self , line ):
113129 """
@@ -574,38 +590,75 @@ def update(self, **kwargs):
574590 self .repo .git .remote ("update" , self .name )
575591 return 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
579630 output = 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
583644 fp = open (os .path .join (self .repo .git_dir , 'FETCH_HEAD' ),'r' )
584645 fetch_head_info = fp .readlines ()
585646 fp .close ()
586647
648+ assert len (fetch_info_lines ) == len (fetch_head_info )
649+
587650 output .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 )
589654 return output
590655
591656 def _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
610663 output = IterableList ('name' )
611664 for line in proc .stdout .readlines ():
@@ -616,19 +669,12 @@ def _get_push_info(self, proc, progress):
616669 pass
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 )
628674 return output
629675
630676
631- def fetch (self , refspec = None , ** kwargs ):
677+ def fetch (self , refspec = None , progress = None , ** kwargs ):
632678 """
633679 Fetch the latest changes for this remote
634680
@@ -643,7 +689,9 @@ def fetch(self, refspec=None, **kwargs):
643689 See also git-push(1).
644690
645691 Taken from the git manual
646-
692+ ``progress``
693+ See 'push' method
694+
647695 ``**kwargs``
648696 Additional arguments to be passed to git-fetch
649697
@@ -655,25 +703,28 @@ def fetch(self, refspec=None, **kwargs):
655703 As fetch does not provide progress information to non-ttys, we cannot make
656704 it 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 """
663711 Pull changes from the given branch, being the same as a fetch followed
664712 by a merge of branch with your local branch.
665713
666714 ``refspec``
667715 see 'fetch' method
716+
717+ ``progress``
718+ see 'push' method
668719
669720 ``**kwargs``
670721 Additional arguments to be passed to git-pull
671722
672723 Returns
673724 Please 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
678729 def push (self , refspec = None , progress = None , ** kwargs ):
679730 """
@@ -683,7 +734,7 @@ def push(self, refspec=None, progress=None, **kwargs):
683734 see 'fetch' method
684735
685736 ``progress``
686- Instance of type PushProgress allowing the caller to receive
737+ Instance of type RemoteProgress allowing the caller to receive
687738 progress information until the method returns.
688739 If None, progress information will be discarded
689740
@@ -700,7 +751,7 @@ def push(self, refspec=None, progress=None, **kwargs):
700751 be null.
701752 """
702753 proc = 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
706757 def config_reader (self ):
0 commit comments