1
# Copyright (C) 2005-2010 Canonical Ltd
1
# Copyright (C) 2005, 2006 Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
18
"""Commit message editor support."""
22
23
from subprocess import call
25
26
from bzrlib import (
32
30
from bzrlib.errors import BzrError, BadCommitMessageEncoding
33
from bzrlib.hooks import HookPoint, Hooks
31
from bzrlib.trace import warning
37
35
"""Return a sequence of possible editor binaries for the current platform"""
39
yield os.environ["BZR_EDITOR"], '$BZR_EDITOR'
37
yield os.environ["BZR_EDITOR"]
43
41
e = config.GlobalConfig().get_editor()
45
yield e, config.config_filename()
47
45
for varname in 'VISUAL', 'EDITOR':
48
46
if varname in os.environ:
49
yield os.environ[varname], '$' + varname
47
yield os.environ[varname]
51
49
if sys.platform == 'win32':
52
50
for editor in 'wordpad.exe', 'notepad.exe':
55
53
for editor in ['/usr/bin/editor', 'vi', 'pico', 'nano', 'joe']:
59
57
def _run_editor(filename):
60
58
"""Try to execute an editor to edit the commit message."""
61
for candidate, candidate_source in _get_editor():
62
edargs = candidate.split(' ')
59
for e in _get_editor():
64
62
## mutter("trying editor: %r", (edargs +[filename]))
65
63
x = call(edargs + [filename])
67
if candidate_source is not None:
68
# We tried this editor because some user configuration (an
69
# environment variable or config file) said to try it. Let
70
# the user know their configuration is broken.
72
'Could not start editor "%s" (specified by %s): %s\n'
73
% (candidate, candidate_source, str(e)))
65
# We're searching for an editor, so catch safe errors and continue
66
if e.errno in (errno.ENOENT, ):
141
134
msgfilename, hasinfo = _create_temp_file_with_commit_template(
142
135
infotext, ignoreline, start_message)
145
basename = osutils.basename(msgfilename)
146
msg_transport = transport.get_transport(osutils.dirname(msgfilename))
147
reference_content = msg_transport.get_bytes(basename)
148
if not _run_editor(msgfilename):
150
edited_content = msg_transport.get_bytes(basename)
151
if edited_content == reference_content:
152
if not ui.ui_factory.get_boolean(
153
"Commit message was not edited, use anyway"):
154
# Returning "" makes cmd_commit raise 'empty commit message
155
# specified' which is a reasonable error, given the user has
156
# rejected using the unedited template.
137
if not msgfilename or not _run_editor(msgfilename):
160
142
lastline, nlines = 0, 0
201
183
os.unlink(msgfilename)
202
184
except IOError, e:
204
"failed to unlink %s: %s; ignored", msgfilename, e)
185
warning("failed to unlink %s: %s; ignored", msgfilename, e)
207
188
def _create_temp_file_with_commit_template(infotext,
257
238
from StringIO import StringIO # must be unicode-safe
258
239
from bzrlib.status import show_tree_status
259
240
status_tmp = StringIO()
260
show_tree_status(working_tree, specific_files=specific_files,
261
to_file=status_tmp, verbose=True)
241
show_tree_status(working_tree, specific_files=specific_files,
262
243
return status_tmp.getvalue()
287
268
template = template + '\n' + stream.getvalue()
292
class MessageEditorHooks(Hooks):
293
"""A dictionary mapping hook name to a list of callables for message editor
296
e.g. ['commit_message_template'] is the list of items to be called to
297
generate a commit message template
301
"""Create the default hooks.
303
These are all empty initially.
306
self.create_hook(HookPoint('commit_message_template',
307
"Called when a commit message is being generated. "
308
"commit_message_template is called with the bzrlib.commit.Commit "
309
"object and the message that is known so far. "
310
"commit_message_template must return a new message to use (which "
311
"could be the same as it was given. When there are multiple "
312
"hooks registered for commit_message_template, they are chained "
313
"with the result from the first passed into the second, and so "
314
"on.", (1, 10), None))
317
hooks = MessageEditorHooks()
320
def generate_commit_message_template(commit, start_message=None):
321
"""Generate a commit message template.
323
:param commit: Commit object for the active commit.
324
:param start_message: Message to start with.
325
:return: A start commit message or None for an empty start commit message.
328
for hook in hooks['commit_message_template']:
329
start_message = hook(commit, start_message)