forked from kemayo/sublime-text-git
-
Notifications
You must be signed in to change notification settings - Fork 0
/
commit.py
168 lines (140 loc) · 6.2 KB
/
commit.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
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
self.run_command(['git', 'commit', '-F', message_file.name, self.extra_options],
self.commit_done, working_dir=self.working_dir)
def commit_done(self, result):
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')