1
# Copyright (C) 2005, 2006 Canonical Ltd
1
# Copyright (C) 2005-2010 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
18
"""Commit message editor support."""
23
22
from subprocess import call
26
25
from bzrlib import (
30
32
from bzrlib.errors import BzrError, BadCommitMessageEncoding
31
from bzrlib.hooks import Hooks
32
from bzrlib.trace import warning, mutter
33
from bzrlib.hooks import HookPoint, Hooks
36
37
"""Return a sequence of possible editor binaries for the current platform"""
38
yield os.environ["BZR_EDITOR"]
39
yield os.environ["BZR_EDITOR"], '$BZR_EDITOR'
42
43
e = config.GlobalConfig().get_editor()
45
yield e, config.config_filename()
46
47
for varname in 'VISUAL', 'EDITOR':
47
48
if varname in os.environ:
48
yield os.environ[varname]
49
yield os.environ[varname], '$' + varname
50
51
if sys.platform == 'win32':
51
52
for editor in 'wordpad.exe', 'notepad.exe':
54
55
for editor in ['/usr/bin/editor', 'vi', 'pico', 'nano', 'joe']:
58
59
def _run_editor(filename):
59
60
"""Try to execute an editor to edit the commit message."""
60
for e in _get_editor():
61
for candidate, candidate_source in _get_editor():
62
edargs = candidate.split(' ')
63
64
## mutter("trying editor: %r", (edargs +[filename]))
64
65
x = call(edargs + [filename])
66
# We're searching for an editor, so catch safe errors and continue
67
if e.errno in (errno.ENOENT, ):
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)))
135
141
msgfilename, hasinfo = _create_temp_file_with_commit_template(
136
142
infotext, ignoreline, start_message)
138
if not msgfilename or not _run_editor(msgfilename):
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.
143
160
lastline, nlines = 0, 0
184
201
os.unlink(msgfilename)
185
202
except IOError, e:
186
warning("failed to unlink %s: %s; ignored", msgfilename, e)
204
"failed to unlink %s: %s; ignored", msgfilename, e)
189
207
def _create_temp_file_with_commit_template(infotext,
239
257
from StringIO import StringIO # must be unicode-safe
240
258
from bzrlib.status import show_tree_status
241
259
status_tmp = StringIO()
242
show_tree_status(working_tree, specific_files=specific_files,
260
show_tree_status(working_tree, specific_files=specific_files,
261
to_file=status_tmp, verbose=True)
244
262
return status_tmp.getvalue()
275
293
"""A dictionary mapping hook name to a list of callables for message editor
278
e.g. ['commit_message_template'] is the list of items to be called to
296
e.g. ['commit_message_template'] is the list of items to be called to
279
297
generate a commit message template
285
303
These are all empty initially.
287
305
Hooks.__init__(self)
288
# Introduced in 1.10:
289
# Invoked to generate the commit message template shown in the editor
290
# The api signature is:
291
# (commit, message), and the function should return the new message
292
# There is currently no way to modify the order in which
293
# template hooks are invoked
294
self['commit_message_template'] = []
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))
297
317
hooks = MessageEditorHooks()