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.hooks import Hooks
32
from bzrlib.trace import warning, mutter
37
36
"""Return a sequence of possible editor binaries for the current platform"""
39
yield os.environ["BZR_EDITOR"], '$BZR_EDITOR'
38
yield os.environ["BZR_EDITOR"]
43
42
e = config.GlobalConfig().get_editor()
45
yield e, config.config_filename()
47
46
for varname in 'VISUAL', 'EDITOR':
48
47
if varname in os.environ:
49
yield os.environ[varname], '$' + varname
48
yield os.environ[varname]
51
50
if sys.platform == 'win32':
52
51
for editor in 'wordpad.exe', 'notepad.exe':
55
54
for editor in ['/usr/bin/editor', 'vi', 'pico', 'nano', 'joe']:
59
58
def _run_editor(filename):
60
59
"""Try to execute an editor to edit the commit message."""
61
for candidate, candidate_source in _get_editor():
62
edargs = candidate.split(' ')
60
for e in _get_editor():
64
63
## mutter("trying editor: %r", (edargs +[filename]))
65
64
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)))
66
# We're searching for an editor, so catch safe errors and continue
67
if e.errno in (errno.ENOENT, ):
141
135
msgfilename, hasinfo = _create_temp_file_with_commit_template(
142
136
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.
138
if not msgfilename or not _run_editor(msgfilename):
160
143
lastline, nlines = 0, 0
201
184
os.unlink(msgfilename)
202
185
except IOError, e:
204
"failed to unlink %s: %s; ignored", msgfilename, e)
186
warning("failed to unlink %s: %s; ignored", msgfilename, e)
207
189
def _create_temp_file_with_commit_template(infotext,
257
239
from StringIO import StringIO # must be unicode-safe
258
240
from bzrlib.status import show_tree_status
259
241
status_tmp = StringIO()
260
show_tree_status(working_tree, specific_files=specific_files,
261
to_file=status_tmp, verbose=True)
242
show_tree_status(working_tree, specific_files=specific_files,
262
244
return status_tmp.getvalue()
293
275
"""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
278
e.g. ['commit_message_template'] is the list of items to be called to
297
279
generate a commit message template
303
285
These are all empty initially.
305
287
Hooks.__init__(self)
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))
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'] = []
317
297
hooks = MessageEditorHooks()