@@ -38,32 +38,46 @@ def _call_config(self, method, *args, **kwargs):
38
38
return getattr (self ._config , method )(self ._section_name , * args , ** kwargs )
39
39
40
40
41
- class PushProgress (object ):
41
+ class RemoteProgress (object ):
42
42
"""
43
43
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.
45
45
"""
46
46
BEGIN , END , COUNTING , COMPRESSING , WRITING = [ 1 << x for x in range (5 ) ]
47
47
STAGE_MASK = BEGIN | END
48
48
OP_MASK = COUNTING | COMPRESSING | WRITING
49
49
50
50
__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+)\)(.*)" )
53
53
54
54
def __init__ (self ):
55
55
self ._seen_ops = list ()
56
56
57
57
def _parse_progress_line (self , line ):
58
58
"""
59
59
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"""
61
62
# handle
62
63
# Counting objects: 4, done.
63
64
# Compressing objects: 50% (1/2) \rCompressing objects: 100% (2/2) \rCompressing objects: 100% (2/2), done.
64
65
self ._cur_line = line
65
66
sub_lines = line .split ('\r ' )
67
+ failed_lines = list ()
66
68
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
67
81
sline = sline .rstrip ()
68
82
69
83
cur_count , max_count = None , None
@@ -73,11 +87,13 @@ def _parse_progress_line(self, line):
73
87
74
88
if not match :
75
89
self .line_dropped (sline )
90
+ failed_lines .append (sline )
76
91
continue
77
92
# END could not get match
78
93
79
94
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
+
81
97
# get operation id
82
98
if op_name == "Counting objects" :
83
99
op_code |= self .COUNTING
@@ -106,8 +122,8 @@ def _parse_progress_line(self, line):
106
122
# END end message handling
107
123
108
124
self .update (op_code , cur_count , max_count , message )
109
-
110
125
# END for each sub line
126
+ return failed_lines
111
127
112
128
def line_dropped (self , line ):
113
129
"""
@@ -574,38 +590,75 @@ def update(self, **kwargs):
574
590
self .repo .git .remote ("update" , self .name )
575
591
return self
576
592
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 ):
578
629
# skip first line as it is some remote info we are not interested in
579
630
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
581
642
582
643
# read head information
583
644
fp = open (os .path .join (self .repo .git_dir , 'FETCH_HEAD' ),'r' )
584
645
fetch_head_info = fp .readlines ()
585
646
fp .close ()
586
647
648
+ assert len (fetch_info_lines ) == len (fetch_head_info )
649
+
587
650
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 )
589
654
return output
590
655
591
656
def _get_push_info (self , proc , progress ):
592
657
# read progress information from stderr
593
658
# we hope stdout can hold all the data, it should ...
594
659
# read the lines manually as it will use carriage returns between the messages
595
660
# 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 )
609
662
610
663
output = IterableList ('name' )
611
664
for line in proc .stdout .readlines ():
@@ -616,19 +669,12 @@ def _get_push_info(self, proc, progress):
616
669
pass
617
670
# END exception handling
618
671
# 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 )
628
674
return output
629
675
630
676
631
- def fetch (self , refspec = None , ** kwargs ):
677
+ def fetch (self , refspec = None , progress = None , ** kwargs ):
632
678
"""
633
679
Fetch the latest changes for this remote
634
680
@@ -643,7 +689,9 @@ def fetch(self, refspec=None, **kwargs):
643
689
See also git-push(1).
644
690
645
691
Taken from the git manual
646
-
692
+ ``progress``
693
+ See 'push' method
694
+
647
695
``**kwargs``
648
696
Additional arguments to be passed to git-fetch
649
697
@@ -655,25 +703,28 @@ def fetch(self, refspec=None, **kwargs):
655
703
As fetch does not provide progress information to non-ttys, we cannot make
656
704
it available here unfortunately as in the 'push' method.
657
705
"""
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 () )
660
708
661
- def pull (self , refspec = None , ** kwargs ):
709
+ def pull (self , refspec = None , progress = None , ** kwargs ):
662
710
"""
663
711
Pull changes from the given branch, being the same as a fetch followed
664
712
by a merge of branch with your local branch.
665
713
666
714
``refspec``
667
715
see 'fetch' method
716
+
717
+ ``progress``
718
+ see 'push' method
668
719
669
720
``**kwargs``
670
721
Additional arguments to be passed to git-pull
671
722
672
723
Returns
673
724
Please see 'fetch' method
674
725
"""
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 () )
677
728
678
729
def push (self , refspec = None , progress = None , ** kwargs ):
679
730
"""
@@ -683,7 +734,7 @@ def push(self, refspec=None, progress=None, **kwargs):
683
734
see 'fetch' method
684
735
685
736
``progress``
686
- Instance of type PushProgress allowing the caller to receive
737
+ Instance of type RemoteProgress allowing the caller to receive
687
738
progress information until the method returns.
688
739
If None, progress information will be discarded
689
740
@@ -700,7 +751,7 @@ def push(self, refspec=None, progress=None, **kwargs):
700
751
be null.
701
752
"""
702
753
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 ())
704
755
705
756
@property
706
757
def config_reader (self ):
0 commit comments