@@ -135,6 +135,21 @@ def die(msg):
135
135
sys .stderr .write (msg + "\n " )
136
136
sys .exit (1 )
137
137
138
+ # We need different encoding/decoding strategies for text data being passed
139
+ # around in pipes depending on python version
140
+ if bytes is not str :
141
+ # For python3, always encode and decode as appropriate
142
+ def decode_text_stream (s ):
143
+ return s .decode () if isinstance (s , bytes ) else s
144
+ def encode_text_stream (s ):
145
+ return s .encode () if isinstance (s , str ) else s
146
+ else :
147
+ # For python2.7, pass read strings as-is, but also allow writing unicode
148
+ def decode_text_stream (s ):
149
+ return s
150
+ def encode_text_stream (s ):
151
+ return s .encode ('utf_8' ) if isinstance (s , unicode ) else s
152
+
138
153
def write_pipe (c , stdin ):
139
154
if verbose :
140
155
sys .stderr .write ('Writing pipe: %s\n ' % str (c ))
@@ -151,6 +166,8 @@ def write_pipe(c, stdin):
151
166
152
167
def p4_write_pipe (c , stdin ):
153
168
real_cmd = p4_build_cmd (c )
169
+ if bytes is not str and isinstance (stdin , str ):
170
+ stdin = encode_text_stream (stdin )
154
171
return write_pipe (real_cmd , stdin )
155
172
156
173
def read_pipe_full (c ):
@@ -164,7 +181,7 @@ def read_pipe_full(c):
164
181
expand = not isinstance (c , list )
165
182
p = subprocess .Popen (c , stdout = subprocess .PIPE , stderr = subprocess .PIPE , shell = expand )
166
183
(out , err ) = p .communicate ()
167
- return (p .returncode , out , err )
184
+ return (p .returncode , out , decode_text_stream ( err ) )
168
185
169
186
def read_pipe (c , ignore_error = False ):
170
187
""" Read output from command. Returns the output text on
@@ -187,11 +204,11 @@ def read_pipe_text(c):
187
204
if retcode != 0 :
188
205
return None
189
206
else :
190
- return out .rstrip ()
207
+ return decode_text_stream ( out ) .rstrip ()
191
208
192
- def p4_read_pipe (c , ignore_error = False ):
209
+ def p4_read_pipe (c , ignore_error = False , raw = False ):
193
210
real_cmd = p4_build_cmd (c )
194
- return read_pipe (real_cmd , ignore_error )
211
+ return read_pipe (real_cmd , ignore_error , raw = raw )
195
212
196
213
def read_pipe_lines (c ):
197
214
if verbose :
@@ -200,7 +217,7 @@ def read_pipe_lines(c):
200
217
expand = not isinstance (c , list )
201
218
p = subprocess .Popen (c , stdout = subprocess .PIPE , shell = expand )
202
219
pipe = p .stdout
203
- val = pipe .readlines ()
220
+ val = [ decode_text_stream ( line ) for line in pipe .readlines ()]
204
221
if pipe .close () or p .wait ():
205
222
die ('Command failed: %s' % str (c ))
206
223
@@ -231,6 +248,7 @@ def p4_has_move_command():
231
248
cmd = p4_build_cmd (["move" , "-k" , "@from" , "@to" ])
232
249
p = subprocess .Popen (cmd , stdout = subprocess .PIPE , stderr = subprocess .PIPE )
233
250
(out , err ) = p .communicate ()
251
+ err = decode_text_stream (err )
234
252
# return code will be 1 in either case
235
253
if err .find ("Invalid option" ) >= 0 :
236
254
return False
@@ -611,6 +629,20 @@ def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None, skip_info=False,
611
629
try :
612
630
while True :
613
631
entry = marshal .load (p4 .stdout )
632
+ if bytes is not str :
633
+ # Decode unmarshalled dict to use str keys and values, except for:
634
+ # - `data` which may contain arbitrary binary data
635
+ # - `depotFile[0-9]*`, `path`, or `clientFile` which may contain non-UTF8 encoded text
636
+ decoded_entry = {}
637
+ for key , value in entry .items ():
638
+ key = key .decode ()
639
+ if isinstance (value , bytes ) and not (key in ('data' , 'path' , 'clientFile' ) or key .startswith ('depotFile' )):
640
+ value = value .decode ()
641
+ decoded_entry [key ] = value
642
+ # Parse out data if it's an error response
643
+ if decoded_entry .get ('code' ) == 'error' and 'data' in decoded_entry :
644
+ decoded_entry ['data' ] = decoded_entry ['data' ].decode ()
645
+ entry = decoded_entry
614
646
if skip_info :
615
647
if 'code' in entry and entry ['code' ] == 'info' :
616
648
continue
@@ -828,6 +860,7 @@ def branch_exists(branch):
828
860
cmd = [ "git" , "rev-parse" , "--symbolic" , "--verify" , branch ]
829
861
p = subprocess .Popen (cmd , stdout = subprocess .PIPE , stderr = subprocess .PIPE )
830
862
out , _ = p .communicate ()
863
+ out = decode_text_stream (out )
831
864
if p .returncode :
832
865
return False
833
866
# expect exactly one line of output: the branch name
@@ -1971,7 +2004,7 @@ def applyCommit(self, id):
1971
2004
tmpFile = os .fdopen (handle , "w+b" )
1972
2005
if self .isWindows :
1973
2006
submitTemplate = submitTemplate .replace ("\n " , "\r \n " )
1974
- tmpFile .write (submitTemplate )
2007
+ tmpFile .write (encode_text_stream ( submitTemplate ) )
1975
2008
tmpFile .close ()
1976
2009
1977
2010
if self .prepare_p4_only :
@@ -2018,7 +2051,7 @@ def applyCommit(self, id):
2018
2051
if self .edit_template (fileName ):
2019
2052
# read the edited message and submit
2020
2053
tmpFile = open (fileName , "rb" )
2021
- message = tmpFile .read ()
2054
+ message = decode_text_stream ( tmpFile .read () )
2022
2055
tmpFile .close ()
2023
2056
if self .isWindows :
2024
2057
message = message .replace ("\r \n " , "\n " )
@@ -2707,7 +2740,7 @@ def splitFilesIntoBranches(self, commit):
2707
2740
return branches
2708
2741
2709
2742
def writeToGitStream (self , gitMode , relPath , contents ):
2710
- self .gitStream .write ('M %s inline %s \n ' % (gitMode , relPath ))
2743
+ self .gitStream .write (encode_text_stream ( u 'M {} inline {} \n '. format (gitMode , relPath ) ))
2711
2744
self .gitStream .write ('data %d\n ' % sum (len (d ) for d in contents ))
2712
2745
for d in contents :
2713
2746
self .gitStream .write (d )
@@ -2748,7 +2781,7 @@ def streamOneP4File(self, file, contents):
2748
2781
git_mode = "120000"
2749
2782
# p4 print on a symlink sometimes contains "target\n";
2750
2783
# if it does, remove the newline
2751
- data = '' .join (contents )
2784
+ data = '' .join (decode_text_stream ( c ) for c in contents )
2752
2785
if not data :
2753
2786
# Some version of p4 allowed creating a symlink that pointed
2754
2787
# to nothing. This causes p4 errors when checking out such
@@ -2802,7 +2835,7 @@ def streamOneP4File(self, file, contents):
2802
2835
pattern = p4_keywords_regexp_for_type (type_base , type_mods )
2803
2836
if pattern :
2804
2837
regexp = re .compile (pattern , re .VERBOSE )
2805
- text = '' .join (contents )
2838
+ text = '' .join (decode_text_stream ( c ) for c in contents )
2806
2839
text = regexp .sub (r'$\1$' , text )
2807
2840
contents = [ text ]
2808
2841
@@ -2817,7 +2850,7 @@ def streamOneP4Deletion(self, file):
2817
2850
if verbose :
2818
2851
sys .stdout .write ("delete %s\n " % relPath )
2819
2852
sys .stdout .flush ()
2820
- self .gitStream .write ("D %s \n " % relPath )
2853
+ self .gitStream .write (encode_text_stream ( u'D {} \n ' . format ( relPath )) )
2821
2854
2822
2855
if self .largeFileSystem and self .largeFileSystem .isLargeFile (relPath ):
2823
2856
self .largeFileSystem .removeLargeFile (relPath )
@@ -2917,9 +2950,9 @@ def streamP4FilesCbSelf(entry):
2917
2950
if 'shelved_cl' in f :
2918
2951
# Handle shelved CLs using the "p4 print file@=N" syntax to print
2919
2952
# the contents
2920
- fileArg = '%s@=%d' % ( f ['path' ], f ['shelved_cl' ])
2953
+ fileArg = f ['path' ] + encode_text_stream ( '@={}' . format ( f ['shelved_cl' ]) )
2921
2954
else :
2922
- fileArg = '%s#%s' % ( f ['path' ], f ['rev' ])
2955
+ fileArg = f ['path' ] + encode_text_stream ( '#{}' . format ( f ['rev' ]) )
2923
2956
2924
2957
fileArgs .append (fileArg )
2925
2958
0 commit comments