170 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			170 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| import functools
 | |
| import tempfile
 | |
| import os
 | |
| 
 | |
| import sublime
 | |
| import sublime_plugin
 | |
| from git import GitTextCommand, GitWindowCommand, plugin_file, view_contents, _make_text_safeish
 | |
| import add
 | |
| 
 | |
| history = []
 | |
| 
 | |
| 
 | |
| class GitQuickCommitCommand(GitTextCommand):
 | |
|     def run(self, edit):
 | |
|         self.get_window().show_input_panel("Message", "",
 | |
|             self.on_input, None, None)
 | |
| 
 | |
|     def on_input(self, message):
 | |
|         if message.strip() == "":
 | |
|             self.panel("No commit message provided")
 | |
|             return
 | |
|         self.run_command(['git', 'add', self.get_file_name()],
 | |
|             functools.partial(self.add_done, message))
 | |
| 
 | |
|     def add_done(self, message, result):
 | |
|         if result.strip():
 | |
|             sublime.error_message("Error adding file:\n" + result)
 | |
|             return
 | |
|         self.run_command(['git', 'commit', '-m', message])
 | |
| 
 | |
| 
 | |
| # Commit is complicated. It'd be easy if I just wanted to let it run
 | |
| # on OSX, and assume that subl was in the $PATH. However... I can't do
 | |
| # that. Second choice was to set $GIT_EDITOR to sublime text for the call
 | |
| #  to commit, and let that Just Work. However, on Windows you can't pass
 | |
| # -w to sublime, which means the editor won't wait, and so the commit will fail
 | |
| # with an empty message.
 | |
| # Thus this flow:
 | |
| # 1. `status --porcelain --untracked-files=no` to know whether files need
 | |
| #    to be committed
 | |
| # 2. `status` to get a template commit message (not the exact one git uses; I
 | |
| #    can't see a way to ask it to output that, which is not quite ideal)
 | |
| # 3. Create a scratch buffer containing the template
 | |
| # 4. When this buffer is closed, get its contents with an event handler and
 | |
| #    pass execution back to the original command. (I feel that the way this
 | |
| #    is done is  a total hack. Unfortunately, I cannot see a better way right
 | |
| #    now.)
 | |
| # 5. Strip lines beginning with # from the message, and save in a temporary
 | |
| #    file
 | |
| # 6. `commit -F [tempfile]`
 | |
| class GitCommitCommand(GitWindowCommand):
 | |
|     active_message = False
 | |
|     extra_options = ""
 | |
| 
 | |
|     def run(self):
 | |
|         self.lines = []
 | |
|         self.working_dir = self.get_working_dir()
 | |
|         self.run_command(
 | |
|             ['git', 'status', '--untracked-files=no', '--porcelain'],
 | |
|             self.porcelain_status_done
 | |
|             )
 | |
| 
 | |
|     def porcelain_status_done(self, result):
 | |
|         # todo: split out these status-parsing things... asdf
 | |
|         has_staged_files = False
 | |
|         result_lines = result.rstrip().split('\n')
 | |
|         for line in result_lines:
 | |
|             if line and not line[0].isspace():
 | |
|                 has_staged_files = True
 | |
|                 break
 | |
|         if not has_staged_files:
 | |
|             self.panel("Nothing to commit")
 | |
|             return
 | |
|         # Okay, get the template!
 | |
|         s = sublime.load_settings("Git.sublime-settings")
 | |
|         if s.get("verbose_commits"):
 | |
|             self.run_command(['git', 'diff', '--staged', '--no-color'], self.diff_done)
 | |
|         else:
 | |
|             self.run_command(['git', 'status'], self.diff_done)
 | |
| 
 | |
|     def diff_done(self, result):
 | |
|         settings = sublime.load_settings("Git.sublime-settings")
 | |
|         historySize = settings.get('history_size')
 | |
| 
 | |
|         def format(line):
 | |
|             return '# ' + line.replace("\n", " ")
 | |
| 
 | |
|         if not len(self.lines):
 | |
|             self.lines = ["", ""]
 | |
| 
 | |
|         self.lines.extend(map(format, history[:historySize]))
 | |
|         self.lines.extend([
 | |
|             "# --------------",
 | |
|             "# Please enter the commit message for your changes. Everything below",
 | |
|             "# this paragraph is ignored, and an empty message aborts the commit.",
 | |
|             "# Just close the window to accept your message.",
 | |
|             result.strip()
 | |
|         ])
 | |
|         template = "\n".join(self.lines)
 | |
|         msg = self.window.new_file()
 | |
|         msg.set_scratch(True)
 | |
|         msg.set_name("COMMIT_EDITMSG")
 | |
|         self._output_to_view(msg, template, syntax=plugin_file("syntax/Git Commit Message.tmLanguage"))
 | |
|         msg.sel().clear()
 | |
|         msg.sel().add(sublime.Region(0, 0))
 | |
|         GitCommitCommand.active_message = self
 | |
| 
 | |
|     def message_done(self, message):
 | |
|         # filter out the comments (git commit doesn't do this automatically)
 | |
|         settings = sublime.load_settings("Git.sublime-settings")
 | |
|         historySize = settings.get('history_size')
 | |
|         lines = [line for line in message.split("\n# --------------")[0].split("\n")
 | |
|             if not line.lstrip().startswith('#')]
 | |
|         message = '\n'.join(lines).strip()
 | |
| 
 | |
|         if len(message) and historySize:
 | |
|             history.insert(0, message)
 | |
|         # write the temp file
 | |
|         message_file = tempfile.NamedTemporaryFile(delete=False)
 | |
|         message_file.write(_make_text_safeish(message, self.fallback_encoding, 'encode'))
 | |
|         message_file.close()
 | |
|         self.message_file = message_file
 | |
|         # and actually commit
 | |
|         with open(message_file.name, 'r') as fp:
 | |
|             self.run_command(['git', 'commit', '-F', '-', self.extra_options],
 | |
|                 self.commit_done, working_dir=self.working_dir, stdin=fp.read())
 | |
| 
 | |
|     def commit_done(self, result, **kwargs):
 | |
|         os.remove(self.message_file.name)
 | |
|         self.panel(result)
 | |
| 
 | |
| 
 | |
| class GitCommitAmendCommand(GitCommitCommand):
 | |
|     extra_options = "--amend"
 | |
| 
 | |
|     def diff_done(self, result):
 | |
|         self.after_show = result
 | |
|         self.run_command(['git', 'log', '-n', '1', '--format=format:%B'], self.amend_diff_done)
 | |
| 
 | |
|     def amend_diff_done(self, result):
 | |
|         self.lines = result.split("\n")
 | |
|         super(GitCommitAmendCommand, self).diff_done(self.after_show)
 | |
| 
 | |
| 
 | |
| class GitCommitMessageListener(sublime_plugin.EventListener):
 | |
|     def on_close(self, view):
 | |
|         if view.name() != "COMMIT_EDITMSG":
 | |
|             return
 | |
|         command = GitCommitCommand.active_message
 | |
|         if not command:
 | |
|             return
 | |
|         message = view_contents(view)
 | |
|         command.message_done(message)
 | |
| 
 | |
| 
 | |
| class GitCommitHistoryCommand(sublime_plugin.TextCommand):
 | |
|     def run(self, edit):
 | |
|         self.edit = edit
 | |
|         self.view.window().show_quick_panel(history, self.panel_done, sublime.MONOSPACE_FONT)
 | |
| 
 | |
|     def panel_done(self, index):
 | |
|         if index > -1:
 | |
|             self.view.replace(self.edit, self.view.sel()[0], history[index] + '\n')
 | |
| 
 | |
| 
 | |
| class GitCommitSelectedHunk(add.GitAddSelectedHunkCommand):
 | |
|     def run(self, edit):
 | |
|         self.run_command(['git', 'diff', '--no-color', self.get_file_name()], self.cull_diff)
 | |
|         self.get_window().run_command('git_commit')
 |