diff --git a/Default.sublime-commands b/Default.sublime-commands index 470d7f9..d3739d2 100644 --- a/Default.sublime-commands +++ b/Default.sublime-commands @@ -1,6 +1,14 @@ [ -{ - "caption": "Git Blame", - "command": "blame" -} + { + "caption": "Git Blame", + "command": "blame" + }, + { + "caption": "Git Blame Show All", + "command": "blame_show_all" + }, + { + "caption": "Git Blame Erase All", + "command": "blame_erase_all" + } ] diff --git a/README.md b/README.md index 53c8c9e..c2d1ef0 100644 --- a/README.md +++ b/README.md @@ -2,20 +2,28 @@ [![GitHub issues](https://img.shields.io/github/issues/psykzz/st3-gitblame.svg)](https://github.com/psykzz/st3-gitblame/issues) -# Git blame - Sublime text 3 plugin +# Git blame - Sublime text 3 plugin Sublime text 3 - Git blame the line -Small plugin to parse git blame and add a view to show the user and datetime of the blame. You can click the sha to copy it or click the ✖ to close. +Small plugin to parse git blame and add a view to show the user and datetime of the blame. You can click the sha to copy it or click the ✖ to close. Also, this package provides a command to see all the blame result of a file. View on [packagecontrol.io](https://packagecontrol.io/packages/Git%20blame) ## Usage +### Checking the results of `git blame` for selected lines + > CTRL + ALT + B (Q on linux) - + > Right click > Git blame - + +### Checking the result of `git blame` for the whole file + +To show the `git blame` result: Open the command pallette and select `Git Blame Show All`. + +To erase the `git blame` result: Open the command pallette and select `Git Blame Erase All`. Or, you can click the ✖ icon to erase it. Also, the result is automatically erased when you start to modify the file. + ## Example screen shot 2017-07-20 at 11 12 51 diff --git a/git-blame.py b/git-blame.py index 2df5eff..7ced011 100644 --- a/git-blame.py +++ b/git-blame.py @@ -1,11 +1,14 @@ import sublime import sublime_plugin import os +import re import functools import subprocess from subprocess import check_output as shell -stylesheet = ''' +PHANTOM_KEY_ALL = 'git-blame-all' + +stylesheet_one = ''' ''' -template = ''' +template_one = ''' {stylesheet}
@@ -58,6 +61,43 @@ ''' +stylesheet_all = ''' + +''' + +template_all = ''' + + {stylesheet} +
+ + {sha} ({user} {date} {time}) + ''' + chr(0x00D7) + ''' + +
+ +''' + # Sometimes this fails on other OS, just error silently try: si = subprocess.STARTUPINFO() @@ -65,6 +105,7 @@ except: si = None + class BlameCommand(sublime_plugin.TextCommand): def __init__(self, view): @@ -148,13 +189,149 @@ def run(self, edit): sha, user, date, time = self.parse_blame(result) - body = template.format(sha=sha, user=user, date=date, time=time, stylesheet=stylesheet) + body = template_one.format(sha=sha, user=user, date=date, time=time, stylesheet=stylesheet_one) phantom = sublime.Phantom(line, body, sublime.LAYOUT_BLOCK, self.on_phantom_close) phantoms.append(phantom) self.phantom_set.update(phantoms) +class BlameShowAllCommand(sublime_plugin.TextCommand): + + # The fixed length for author names + NAME_LENGTH = 10 + + def __init__(self, view): + super().__init__(view) + self.phantom_set = sublime.PhantomSet(self.view, PHANTOM_KEY_ALL) + self.pattern = None + + def run(self, edit): + if self.view.is_dirty(): + sublime.status_message("The file needs to be saved for git blame.") + return + + self.view.erase_phantoms(PHANTOM_KEY_ALL) + + blame_lines = self.get_blame_lines(self.view.file_name()) + + if not blame_lines: + return + + phantoms = [] + for l in blame_lines: + parsed = self.parse_blame(l) + if not parsed: + continue + + sha, author, date, time, line_number = parsed + + body = template_all.format(sha=sha, + user=self.format_name(author), + date=date, + time=time, + stylesheet=stylesheet_all) + + line_point = self.get_line_point(line_number - 1) + phantom = sublime.Phantom(line_point, + body, + sublime.LAYOUT_INLINE, + self.on_phantom_close) + phantoms.append(phantom) + + self.phantom_set.update(phantoms) + + def get_blame_lines(self, path): + '''Run `git blame` and get the output lines. + ''' + try: + # The option --show-name is necessary to force file name display. + command = ["git", "blame", "--show-name", "--minimal", "-w", path] + output = shell(command, + cwd=os.path.dirname(os.path.realpath(path)), + startupinfo=si, + stderr=subprocess.STDOUT) + return output.decode("UTF-8").splitlines() + except subprocess.CalledProcessError as e: + print("Git blame: git error {}:\n{}".format(e.returncode, e.output.decode("UTF-8"))) + except Exception as e: + print("Git blame: Unexpected error:", e) + + def parse_blame(self, blame): + '''Parses git blame output. + ''' + if not self.pattern: + self.prepare_pattern() + + m = self.pattern.match(blame) + if m: + sha = m.group('sha') + # Currently file is not used. + # file = m.group('file') + author = m.group('author') + date = m.group('date') + time = m.group('time') + line_number = int(m.group('line_number')) + return sha, author, date, time, line_number + else: + return None + + def prepare_pattern(self): + '''Prepares the regex pattern to parse git blame output. + ''' + # The SHA output by git-blame may have a leading caret to indicate + # that it is a "boundary commit". + p_sha = r'(?P\^?\w+)' + p_file = r'((?P[\S ]+)\s+)' + p_author = r'(?P.+?)' + p_date = r'(?P\d{4}-\d{2}-\d{2})' + p_time = r'(?P