From 789a25e34a462b0b88e12c473fd67a2585cb832c Mon Sep 17 00:00:00 2001 From: watergear Date: Wed, 28 Sep 2016 09:58:03 +0800 Subject: [PATCH 1/5] support view commits by hash ids --- Default.sublime-commands | 8 ++++++++ git/history.py | 43 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/Default.sublime-commands b/Default.sublime-commands index aa581f94..fe993454 100644 --- a/Default.sublime-commands +++ b/Default.sublime-commands @@ -38,6 +38,14 @@ "caption": "Git: Log All", "command": "git_log_all" } + ,{ + "caption": "Git: View Commits by Hash IDs from Current Selections", + "command": "git_log_by_hash_id_from_sels" + } + ,{ + "caption": "Git: View Commits by Hash IDs from Current Lines", + "command": "git_log_by_hash_id_from_lines" + } ,{ "caption": "Git: Graph Current File", "command": "git_graph" diff --git a/git/history.py b/git/history.py index 233a084b..bbc965b1 100644 --- a/git/history.py +++ b/git/history.py @@ -2,10 +2,12 @@ import functools import re +import os +import os.path import sublime import sublime_plugin -from . import GitTextCommand, GitWindowCommand, plugin_file +from . import GitTextCommand, GitWindowCommand, plugin_file, git_root class GitBlameCommand(GitTextCommand): @@ -101,6 +103,41 @@ class GitLogCommand(GitLog, GitTextCommand): class GitLogAllCommand(GitLog, GitWindowCommand): pass +class GitLogMultiCommand(GitLog): + def log_results(self, refs): + for ref in refs: + self.log_result(ref) + +class GitLogMultiTextCommand(GitTextCommand): + def get_working_dir(self): + path = self.view.settings().get("git_log_graph_location") + return os.path.realpath(os.path.dirname(path)) if path else None + def get_file_name(self): + return os.path.basename(self.view.settings().get("git_log_graph_location")) + def is_enabled(self): + return bool(git_root(self.get_working_dir())) + +class GitLogByHashIdFromSelsCommand(GitLogMultiCommand, GitLogMultiTextCommand): + def run(self, edit=None): + refs = [] + view = self.active_view(); + for s in view.sel(): + if s.empty(): + s = view.word(s) + refs.append(view.substr(s)) + return self.log_results(refs) + +class GitLogByHashIdFromLinesCommand(GitLogMultiCommand, GitLogMultiTextCommand): + def run(self, edit=None): + refs = [] + view = self.active_view(); + for s in view.sel(): + s = view.line(s) + text = view.substr(s) + mm = re.search(r' (\w+) - ', text) + if mm: + refs.append(mm.group(1)) + return self.log_results(refs) class GitShow(object): def run(self, edit=None): @@ -165,7 +202,9 @@ def run(self, edit=None): ) def log_done(self, result): - self.scratch(result, title="Git Log Graph", syntax=plugin_file("syntax/Git Graph.tmLanguage")) + location = self.get_working_dir() + "/" + self.get_file_name() + view = self.scratch(result, title="Git Log Graph", syntax=plugin_file("syntax/Git Graph.tmLanguage")) + view.settings().set("git_log_graph_location", location) class GitGraphCommand(GitGraph, GitTextCommand): From 573ba9cd6c270dc720bbd24b272c950b58417d27 Mon Sep 17 00:00:00 2001 From: watergear Date: Thu, 29 Sep 2016 21:03:41 +0800 Subject: [PATCH 2/5] fix for get hash id by selector "string.sha" --- git/history.py | 55 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 14 deletions(-) diff --git a/git/history.py b/git/history.py index 738ded5d..760923f5 100644 --- a/git/history.py +++ b/git/history.py @@ -110,34 +110,61 @@ def log_results(self, refs): class GitLogMultiTextCommand(GitTextCommand): def get_working_dir(self): + # git_log_graph_location is used to show commit of the only file that lead to "Git: Graph Current File" + # git_root_dir is not work for above case + # also git_root_dir may be None when "Git: Graph All" path = self.view.settings().get("git_log_graph_location") - return os.path.realpath(os.path.dirname(path)) if path else None + if path: + return os.path.realpath(os.path.dirname(path)) + return self.view.settings().get("git_root_dir") def get_file_name(self): - return os.path.basename(self.view.settings().get("git_log_graph_location")) + path = self.view.settings().get("git_log_graph_location") + return os.path.basename(path) if path else None def is_enabled(self): - return bool(git_root(self.get_working_dir())) + view = self.view + selection = view.sel()[0] + return bool( + view.match_selector(selection.a, "text.git-blame") + or view.match_selector(selection.a, "text.git-graph") + ) class GitLogByHashIdFromSelsCommand(GitLogMultiCommand, GitLogMultiTextCommand): def run(self, edit=None): - refs = [] + hashes = [] view = self.active_view(); for s in view.sel(): if s.empty(): s = view.word(s) - refs.append(view.substr(s)) - return self.log_results(refs) + hashes.append(view.substr(s)) + return self.log_results(hashes) + +def get_contained_region_in_lines(regions, lines): + if len(lines) <= 0: + return None + whole_lines = lines[0] + for line in lines: + whole_lines = whole_lines.cover(line) + contained_regions = [] + for region in regions: + if not whole_lines.contains(region): + continue + for line in lines: + if line.contains(region): + contained_regions.append(region) + break + return contained_regions class GitLogByHashIdFromLinesCommand(GitLogMultiCommand, GitLogMultiTextCommand): def run(self, edit=None): - refs = [] view = self.active_view(); - for s in view.sel(): - s = view.line(s) - text = view.substr(s) - mm = re.search(r' (\w+) - ', text) - if mm: - refs.append(mm.group(1)) - return self.log_results(refs) + regions = view.find_by_selector("string.sha") + sel_lines = [] + for sel in view.sel(): + sel_lines.append(view.line(sel)) + hashes = [] + for region in get_contained_region_in_lines(regions, sel_lines): + hashes.append(view.substr(region)) + return self.log_results(hashes) class GitShow(object): def run(self, edit=None): From b2438b84fc9fc20cdaa96bacc63af577634e48d4 Mon Sep 17 00:00:00 2001 From: watergear Date: Thu, 29 Sep 2016 21:46:05 +0800 Subject: [PATCH 3/5] clean GotoCommit to share codes of same route --- git/history.py | 69 +++++++++++++++++--------------------------------- 1 file changed, 23 insertions(+), 46 deletions(-) diff --git a/git/history.py b/git/history.py index 760923f5..47fc7831 100644 --- a/git/history.py +++ b/git/history.py @@ -138,31 +138,34 @@ def run(self, edit=None): hashes.append(view.substr(s)) return self.log_results(hashes) -def get_contained_region_in_lines(regions, lines): - if len(lines) <= 0: - return None - whole_lines = lines[0] - for line in lines: - whole_lines = whole_lines.cover(line) - contained_regions = [] - for region in regions: - if not whole_lines.contains(region): - continue +class GitLogByHashIdFromLinesCommand(GitLogMultiCommand, GitLogMultiTextCommand): + def get_contained_region_in_lines(self, regions, lines): + if len(lines) <= 0: + return None + whole_lines = lines[0] for line in lines: - if line.contains(region): - contained_regions.append(region) - break - return contained_regions + whole_lines = whole_lines.cover(line) + contained_regions = [] + for region in regions: + if not whole_lines.contains(region): + continue + for line in lines: + if line.contains(region): + contained_regions.append(region) + break + return contained_regions + + def get_sels(self): + return self.active_view().sel() -class GitLogByHashIdFromLinesCommand(GitLogMultiCommand, GitLogMultiTextCommand): def run(self, edit=None): view = self.active_view(); regions = view.find_by_selector("string.sha") sel_lines = [] - for sel in view.sel(): + for sel in self.get_sels(): sel_lines.append(view.line(sel)) hashes = [] - for region in get_contained_region_in_lines(regions, sel_lines): + for region in self.get_contained_region_in_lines(regions, sel_lines): hashes.append(view.substr(region)) return self.log_results(hashes) @@ -318,32 +321,6 @@ def show_done(self, result): syntax=plugin_file("syntax/Git Commit View.tmLanguage")) -class GitGotoCommit(GitTextCommand): - def run(self, edit): - view = self.view - selection = view.sel()[0] - if not ( - self.view.match_selector(selection.a, "text.git-blame") - or self.view.match_selector(selection.a, "text.git-graph") - ): - return - - # Sublime is missing a "find scope in region" API, so we piece one together here: - line = view.line(view.sel()[0].a) - hashes = self.view.find_by_selector("string.sha") - commit = False - for region in hashes: - if line.contains(region): - commit = view.substr(region) - break - if not commit or commit.strip("0") == "": - return - working_dir = view.settings().get("git_root_dir") - self.run_command(['git', 'show', commit], self.show_done, working_dir=working_dir) - - def show_done(self, result): - self.scratch(result, title="Git Commit View", - syntax=plugin_file("syntax/Git Commit View.tmLanguage")) - - def is_enabled(self): - return True +class GitGotoCommit(GitLogByHashIdFromLinesCommand): + def get_sels(self): + return [self.active_view().sel()[0].a] # only for current single line From c25eb861f4803a8c23f17f1dd0204e4d21f9498a Mon Sep 17 00:00:00 2001 From: watergear Date: Fri, 30 Sep 2016 10:39:35 +0800 Subject: [PATCH 4/5] refactoring codes of GotoCommit and GitLogByHashId --- Default.sublime-commands | 16 ++++---- git/history.py | 87 ++++++++++++++++++++++------------------ 2 files changed, 57 insertions(+), 46 deletions(-) diff --git a/Default.sublime-commands b/Default.sublime-commands index edf098cf..576c7a57 100644 --- a/Default.sublime-commands +++ b/Default.sublime-commands @@ -39,12 +39,16 @@ "command": "git_log_all" } ,{ - "caption": "Git: View Commits by Hash IDs from Current Selections", - "command": "git_log_by_hash_id_from_sels" + "caption": "Git: View Selected Commits", + "command": "git_log_multi_from_sels" } ,{ - "caption": "Git: View Commits by Hash IDs from Current Lines", - "command": "git_log_by_hash_id_from_lines" + "caption": "Git: View Commits from Selected Lines", + "command": "git_log_multi_from_lines" + } + ,{ + "caption": "Git: View Commits from Current Lines", + "command": "git_goto_commit" } ,{ "caption": "Git: Graph Current File", @@ -54,10 +58,6 @@ "caption": "Git: Graph All", "command": "git_graph_all" } - ,{ - "caption": "Git: View selected commits", - "command": "git_goto_commit" - } ,{ "caption": "Git: Diff Current File", "command": "git_diff" diff --git a/git/history.py b/git/history.py index 803bd330..d7479749 100644 --- a/git/history.py +++ b/git/history.py @@ -103,42 +103,29 @@ class GitLogCommand(GitLog, GitTextCommand): class GitLogAllCommand(GitLog, GitWindowCommand): pass -class GitLogMultiCommand(GitLog): +class GitLogMulti(GitLog): def log_results(self, refs): for ref in refs: self.log_result(ref) -class GitLogMultiTextCommand(GitTextCommand): - def get_working_dir(self): - # git_log_graph_location is used to show commit of the only file that lead to "Git: Graph Current File" - # git_root_dir is not work for above case - # also git_root_dir may be None when "Git: Graph All" - path = self.view.settings().get("git_log_graph_location") - if path: - return os.path.realpath(os.path.dirname(path)) - return self.view.settings().get("git_root_dir") - def get_file_name(self): - path = self.view.settings().get("git_log_graph_location") - return os.path.basename(path) if path else None - def is_enabled(self): - view = self.view - selection = view.sel()[0] - return bool( - view.match_selector(selection.a, "text.git-blame") - or view.match_selector(selection.a, "text.git-graph") - ) + def get_hash_region(self): + return None -class GitLogByHashIdFromSelsCommand(GitLogMultiCommand, GitLogMultiTextCommand): def run(self, edit=None): hashes = [] view = self.active_view(); - for s in view.sel(): - if s.empty(): - s = view.word(s) - hashes.append(view.substr(s)) + for s in self.get_hash_region(): + hs = view.substr(s); + if hs.strip("0"): + hashes.append(hs) return self.log_results(hashes) -class GitLogByHashIdFromLinesCommand(GitLogMultiCommand, GitLogMultiTextCommand): +class GitLogMultiFromSels(GitLogMulti): + def get_hash_region(self): + view = self.active_view() + return [(s if not s.empty else view.word(s)) for s in view.sel()] + +class GitLogMultiFromLines(GitLogMulti): def get_contained_region_in_lines(self, regions, lines): if len(lines) <= 0: return None @@ -158,16 +145,44 @@ def get_contained_region_in_lines(self, regions, lines): def get_sels(self): return self.active_view().sel() - def run(self, edit=None): + def get_hash_region(self): view = self.active_view(); regions = view.find_by_selector("string.sha") - sel_lines = [] - for sel in self.get_sels(): - sel_lines.append(view.line(sel)) - hashes = [] - for region in self.get_contained_region_in_lines(regions, sel_lines): - hashes.append(view.substr(region)) - return self.log_results(hashes) + lines = [view.line(sel) for sel in self.get_sels()] + return self.get_contained_region_in_lines(regions, lines) + +class GitGotoCommit(GitLogMultiFromLines): + def get_sels(self): + return [sel.a for sel in self.active_view().sel()] # for the collections of first line at each selection + +class GitLogMultiTextCommand(GitTextCommand): + def get_working_dir(self): + # git_log_graph_location is used to show commit of the only file that lead to "Git: Graph Current File" + # git_root_dir is not work for above case + # also git_root_dir may be None when "Git: Graph All" + path = self.view.settings().get("git_log_graph_location") + if path: + return os.path.realpath(os.path.dirname(path)) + return self.view.settings().get("git_root_dir") + def get_file_name(self): + path = self.view.settings().get("git_log_graph_location") + return os.path.basename(path) if path else None + def is_enabled(self): + view = self.view + selection = view.sel()[0] + return bool( + view.match_selector(selection.a, "text.git-blame") + or view.match_selector(selection.a, "text.git-graph") + ) + +class GitLogMultiFromSelsCommand(GitLogMultiFromSels, GitLogMultiTextCommand): + pass + +class GitLogMultiFromLinesCommand(GitLogMultiFromLines, GitLogMultiTextCommand): + pass + +class GitGotoCommitCommand(GitGotoCommit, GitLogMultiTextCommand): + pass class GitShow(object): def run(self, edit=None): @@ -320,7 +335,3 @@ def show_done(self, result): self.scratch('\n\n'.join(commits), title="Git Commit Documentation", syntax=plugin_file("syntax/Git Commit View.tmLanguage")) - -class GitGotoCommit(GitLogByHashIdFromLinesCommand): - def get_sels(self): - return [sel.a for sel in self.active_view().sel()] # for the collections of first line at each selection From b10d3562ba123bc738d5ade3d6a4a8eb5f4d22c8 Mon Sep 17 00:00:00 2001 From: watergear Date: Fri, 30 Sep 2016 15:41:48 +0800 Subject: [PATCH 5/5] support view multiple commits as one diff --- Default.sublime-commands | 4 ++++ git/history.py | 45 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/Default.sublime-commands b/Default.sublime-commands index 576c7a57..d6e66d59 100644 --- a/Default.sublime-commands +++ b/Default.sublime-commands @@ -50,6 +50,10 @@ "caption": "Git: View Commits from Current Lines", "command": "git_goto_commit" } + ,{ + "caption": "Git: View Commits as One Diff", + "command": "git_log_as_one_diff" + } ,{ "caption": "Git: Graph Current File", "command": "git_graph" diff --git a/git/history.py b/git/history.py index d7479749..1c04bdfe 100644 --- a/git/history.py +++ b/git/history.py @@ -184,6 +184,51 @@ class GitLogMultiFromLinesCommand(GitLogMultiFromLines, GitLogMultiTextCommand): class GitGotoCommitCommand(GitGotoCommit, GitLogMultiTextCommand): pass +class GitLogAsOneDiff(GitLogMultiFromLines): + def log_results(self, refs): + n = len(refs) + if ( n < 1 ): + return + + self.files = set() + for ref in refs: + self.log_result(ref) + self.run_command( + ['git', 'diff', refs[n-1]+'~1', refs[0], '--', self.get_file_name()], + self.as_one_diff_done) + + def details_done(self, result): + for s in result.split('\n'): + mm = re.search(r'^[+]{3} b(.*)', s.strip()) + if ( mm ): + self.files.add(mm.group(1)) + + def as_one_diff_done(self, result): + poslist = [] + pos = 0 + diffTag = 'diff --git' + while True: + pos = result.find(diffTag, pos) + if pos < 0: + break + if (0 == pos) or ('\n' == result[pos-1]) or ('\a' == result[pos-1]): + poslist.append(pos) + pos += len(diffTag) + poslist.append(len(result)) + + results = [] + for i in range(0,len(poslist)-1): + for filename in self.files: + pos = result.find(filename, poslist[i], poslist[i+1]) + if 0 <= pos : + results.append(result[poslist[i]:poslist[i+1]]) + break + + self.scratch(''.join(results), title="Git One Diff Details", syntax=plugin_file("syntax/Git Commit View.tmLanguage")) + +class GitLogAsOneDiffCommand(GitLogAsOneDiff, GitLogMultiTextCommand): + pass + class GitShow(object): def run(self, edit=None): # GitLog Copy-Past