44import re
55import functools
66import subprocess
7- from subprocess import check_output as shell
87
98PHANTOM_KEY_ALL = 'git-blame-all'
109SETTING_PHANTOM_ALL_DISPLAYED = 'git-blame-all-displayed'
@@ -115,20 +114,15 @@ def __init__(self, view):
115114
116115 @functools .lru_cache (128 , False )
117116 def get_blame (self , line , path ):
118- try :
119- return shell (
120- ["git" , "blame" , "--minimal" , "-w" , "-L {0},{0}" .format (line ), os .path .basename (path )],
121- cwd = os .path .dirname (os .path .realpath (path )),
122- startupinfo = si ,
123- stderr = subprocess .STDOUT
124- )
125- except subprocess .CalledProcessError as e :
126- print ("Git blame: git error {}:\n {}" .format (e .returncode , e .output .decode ("UTF-8" )))
127- except Exception as e :
128- print ("Git blame: Unexpected error:" , e )
117+ return subprocess .check_output (
118+ ["git" , "blame" , "--minimal" , "-w" , "-L {0},{0}" .format (line ), os .path .basename (path )],
119+ cwd = os .path .dirname (os .path .realpath (path )),
120+ startupinfo = si ,
121+ stderr = subprocess .STDOUT
122+ ).decode ("utf-8" )
129123
130124 def parse_blame (self , blame ):
131- sha , file_path , user , date , time , tz_offset , * _ = blame .decode ( 'utf-8' ). split ()
125+ sha , file_path , user , date , time , tz_offset , * _ = blame .split ()
132126
133127 # Was part of the inital commit so no updates
134128 if file_path [0 ] == '(' :
@@ -145,14 +139,12 @@ def parse_blame(self, blame):
145139 return (sha , user [1 :], date , time )
146140
147141 def get_commit (self , sha , path ):
148- try :
149- return shell (
150- ["git" , "show" , sha ],
151- cwd = os .path .dirname (os .path .realpath (path )),
152- startupinfo = si
153- )
154- except Exception as e :
155- return
142+ return subprocess .check_output (
143+ ["git" , "show" , sha ],
144+ cwd = os .path .dirname (os .path .realpath (path )),
145+ startupinfo = si ,
146+ stderr = subprocess .STDOUT
147+ ).decode ('utf-8' )
156148
157149 def on_phantom_close (self , href ):
158150 href_parts = href .split ('-' )
@@ -170,7 +162,12 @@ def on_phantom_close(self, href):
170162 sublime .set_clipboard (sha )
171163 sublime .status_message ('Git SHA copied to clipboard' )
172164 elif intent == "show" :
173- desc = self .get_commit (sha , self .view .file_name ()).decode ('utf-8' )
165+ try :
166+ desc = self .get_commit (sha , self .view .file_name ())
167+ except Exception as e :
168+ communicate_error (e )
169+ return
170+
174171 buf = self .view .window ().new_file ()
175172 buf .run_command ('insert_commit_description' , {'desc' : desc , 'scratch_view_name' : 'commit ' + sha })
176173 else :
@@ -179,8 +176,7 @@ def on_phantom_close(self, href):
179176 self .view .erase_phantoms ('git-blame' )
180177
181178 def run (self , edit ):
182- if self .view .is_dirty ():
183- sublime .status_message ("The file needs to be saved for git blame." )
179+ if not view_is_suitable (self .view ):
184180 return
185181
186182 phantoms = []
@@ -196,12 +192,14 @@ def run(self, edit):
196192 line = self .view .line (region )
197193 (row , col ) = self .view .rowcol (region .begin ())
198194 full_path = self .view .file_name ()
199- result = self .get_blame (int (row ) + 1 , full_path )
200- if not result :
201- # Unable to get blame
195+
196+ try :
197+ blame_output = self .get_blame (int (row ) + 1 , full_path )
198+ except Exception as e :
199+ communicate_error (e )
202200 return
203201
204- sha , user , date , time = self .parse_blame (result )
202+ sha , user , date , time = self .parse_blame (blame_output )
205203
206204 body = template_one .format (sha = sha , user = user , date = date , time = time , stylesheet = stylesheet_one )
207205
@@ -221,8 +219,7 @@ def __init__(self, view):
221219 self .pattern = None
222220
223221 def run (self , edit ):
224- if self .view .is_dirty ():
225- sublime .status_message ("The file needs to be saved for git blame." )
222+ if not view_is_suitable (self .view ):
226223 return
227224
228225 self .view .erase_phantoms (PHANTOM_KEY_ALL )
@@ -234,12 +231,13 @@ def run(self, edit):
234231 self .view .settings ().set (SETTING_PHANTOM_ALL_DISPLAYED , False )
235232 return
236233
237- blame_lines = self .get_blame_lines (self .view .file_name ())
238-
239- if not blame_lines :
234+ try :
235+ blame_output = self .get_blame (self .view .file_name ())
236+ except Exception as e :
237+ communicate_error (e )
240238 return
241239
242- for l in blame_lines :
240+ for l in blame_output . splitlines () :
243241 parsed = self .parse_blame (l )
244242 if not parsed :
245243 continue
@@ -264,23 +262,14 @@ def run(self, edit):
264262 # Bring the phantoms into view without the user needing to manually scroll left.
265263 self .view .set_viewport_position ((0.0 , self .view .viewport_position ()[1 ]))
266264
267- def get_blame_lines (self , path ):
268- '''Run `git blame` and get the output lines.
269- '''
270- try :
265+ def get_blame (self , path ):
266+ return subprocess .check_output (
271267 # The option --show-name is necessary to force file name display.
272- command = ["git" , "blame" , "--show-name" , "--minimal" , "-w" , os .path .basename (path )]
273- output = shell (
274- command ,
275- cwd = os .path .dirname (os .path .realpath (path )),
276- startupinfo = si ,
277- stderr = subprocess .STDOUT
278- )
279- return output .decode ("UTF-8" ).splitlines ()
280- except subprocess .CalledProcessError as e :
281- print ("Git blame: git error {}:\n {}" .format (e .returncode , e .output .decode ("UTF-8" )))
282- except Exception as e :
283- print ("Git blame: Unexpected error:" , e )
268+ ["git" , "blame" , "--show-name" , "--minimal" , "-w" , os .path .basename (path )],
269+ cwd = os .path .dirname (os .path .realpath (path )),
270+ startupinfo = si ,
271+ stderr = subprocess .STDOUT
272+ ).decode ("utf-8" )
284273
285274 def parse_blame (self , blame ):
286275 '''Parses git blame output.
@@ -372,3 +361,25 @@ def run(self, edit, desc, scratch_view_name):
372361 view .set_syntax_file ('Packages/Diff/Diff.sublime-syntax' )
373362 view .insert (edit , 0 , desc )
374363 view .set_name (scratch_view_name )
364+
365+
366+ def view_is_suitable (view ):
367+ ok = view .file_name () and not view .is_dirty ()
368+ if not ok :
369+ communicate_error ("Please save file changes to disk first." )
370+ return ok
371+
372+
373+ def communicate_error (e , modal = True ):
374+ user_msg = "st3-gitblame:\n \n {}" .format (e )
375+ if isinstance (e , subprocess .CalledProcessError ):
376+ user_msg += "\n \n {}" .format (e .output .decode ("utf-8" ))
377+
378+ print ()
379+ if modal :
380+ sublime .error_message (user_msg )
381+ else :
382+ sublime .status_message (user_msg )
383+ # Unlike with the error dialog, a status message is not automatically
384+ # persisted in the console too.
385+ print (user_msg )
0 commit comments