D7net
Home
Console
Upload
information
Create File
Create Folder
About
Tools
blackhat.pw
:
/
kunden
/
usr
/
lib
/
python3
/
dist-packages
/
breezy
/
Filename :
msgeditor.py
back
Copy
# Copyright (C) 2005-2011 Canonical Ltd # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA """Commit message editor support.""" import codecs import os import sys from io import BytesIO, StringIO from subprocess import call from . import bedding, cmdline, config, osutils, trace, transport, ui from .errors import BzrError from .hooks import Hooks class BadCommitMessageEncoding(BzrError): _fmt = ( "The specified commit message contains characters unsupported by " "the current encoding." ) def _get_editor(): """Return sequence of possible editor binaries for the current platform.""" try: yield os.environ["BRZ_EDITOR"], "$BRZ_EDITOR" except KeyError: pass e = config.GlobalStack().get("editor") if e is not None: yield e, bedding.config_path() for varname in "VISUAL", "EDITOR": if varname in os.environ: yield os.environ[varname], "$" + varname if sys.platform == "win32": for editor in "wordpad.exe", "notepad.exe": yield editor, None else: for editor in ["/usr/bin/editor", "vi", "pico", "nano", "joe"]: yield editor, None def _run_editor(filename): """Try to execute an editor to edit the commit message.""" for candidate, candidate_source in _get_editor(): edargs = cmdline.split(candidate) try: x = call(edargs + [filename]) except OSError as e: if candidate_source is not None: # We tried this editor because some user configuration (an # environment variable or config file) said to try it. Let # the user know their configuration is broken. trace.warning( 'Could not start editor "{}" (specified by {}): {}\n'.format( candidate, candidate_source, str(e) ) ) continue raise if x == 0: return True elif x == 127: continue else: break raise BzrError( "Could not start any editor.\nPlease specify one with:\n" " - $BRZ_EDITOR\n - editor=/some/path in {}\n" " - $VISUAL\n - $EDITOR".format(bedding.config_path()) ) DEFAULT_IGNORE_LINE = "{bar} {msg} {bar}".format( bar="-" * 14, msg="This line and the following will be ignored", ) def edit_commit_message(infotext, ignoreline=DEFAULT_IGNORE_LINE, start_message=None): """Let the user edit a commit message in a temp file. This is run if they don't give a message or message-containing file on the command line. :param infotext: Text to be displayed at bottom of message for the user's reference; currently similar to 'bzr status'. :param ignoreline: The separator to use above the infotext. :param start_message: The text to place above the separator, if any. This will not be removed from the message after the user has edited it. :return: commit message or None. """ if start_message is not None: start_message = start_message.encode(osutils.get_user_encoding()) infotext = infotext.encode(osutils.get_user_encoding(), "replace") return edit_commit_message_encoded(infotext, ignoreline, start_message) def edit_commit_message_encoded( infotext, ignoreline=DEFAULT_IGNORE_LINE, start_message=None ): """Let the user edit a commit message in a temp file. This is run if they don't give a message or message-containing file on the command line. :param infotext: Text to be displayed at bottom of message for the user's reference; currently similar to 'bzr status'. The string is already encoded :param ignoreline: The separator to use above the infotext. :param start_message: The text to place above the separator, if any. This will not be removed from the message after the user has edited it. The string is already encoded :return: commit message or None. """ msgfilename = None try: msgfilename, hasinfo = _create_temp_file_with_commit_template( infotext, ignoreline, start_message ) if not msgfilename: return None basename = osutils.basename(msgfilename) msg_transport = transport.get_transport_from_path(osutils.dirname(msgfilename)) reference_content = msg_transport.get_bytes(basename) if not _run_editor(msgfilename): return None edited_content = msg_transport.get_bytes(basename) if edited_content == reference_content: if not ui.ui_factory.confirm_action( "Commit message was not edited, use anyway", "breezy.msgeditor.unchanged", {}, ): # Returning "" makes cmd_commit raise 'empty commit message # specified' which is a reasonable error, given the user has # rejected using the unedited template. return "" started = False msg = [] lastline, nlines = 0, 0 with codecs.open( msgfilename, mode="rb", encoding=osutils.get_user_encoding() ) as f: try: for line in f: stripped_line = line.strip() # strip empty line before the log message starts if not started: if stripped_line != "": started = True else: continue # check for the ignore line only if there # is additional information at the end if hasinfo and stripped_line == ignoreline: break nlines += 1 # keep track of the last line that had some content if stripped_line != "": lastline = nlines msg.append(line) except UnicodeDecodeError: raise BadCommitMessageEncoding() if len(msg) == 0: return "" # delete empty lines at the end del msg[lastline:] # add a newline at the end, if needed if not msg[-1].endswith("\n"): return "{}{}".format("".join(msg), "\n") else: return "".join(msg) finally: # delete the msg file in any case if msgfilename is not None: try: os.unlink(msgfilename) except OSError as e: trace.warning("failed to unlink %s: %s; ignored", msgfilename, e) def _create_temp_file_with_commit_template( infotext, ignoreline=DEFAULT_IGNORE_LINE, start_message=None, tmpdir=None ): """Create temp file and write commit template in it. :param infotext: Text to be displayed at bottom of message for the user's reference; currently similar to 'bzr status'. The text is already encoded. :param ignoreline: The separator to use above the infotext. :param start_message: The text to place above the separator, if any. This will not be removed from the message after the user has edited it. The string is already encoded :return: 2-tuple (temp file name, hasinfo) """ import tempfile tmp_fileno, msgfilename = tempfile.mkstemp(prefix="bzr_log.", dir=tmpdir, text=True) with os.fdopen(tmp_fileno, "wb") as msgfile: if start_message is not None: msgfile.write(b"%s\n" % start_message) if infotext is not None and infotext != "": hasinfo = True trailer = b"\n\n%s\n\n%s" % ( ignoreline.encode(osutils.get_user_encoding()), infotext, ) msgfile.write(trailer) else: hasinfo = False return (msgfilename, hasinfo) def make_commit_message_template(working_tree, specific_files): """Prepare a template file for a commit into a branch. Returns a unicode string containing the template. """ # TODO: make provision for this to be overridden or modified by a hook # # TODO: Rather than running the status command, should prepare a draft of # the revision to be committed, then pause and ask the user to # confirm/write a message. from .status import show_tree_status status_tmp = StringIO() show_tree_status( working_tree, specific_files=specific_files, to_file=status_tmp, verbose=True ) return status_tmp.getvalue() def make_commit_message_template_encoded( working_tree, specific_files, diff=None, output_encoding="utf-8" ): """Prepare a template file for a commit into a branch. Returns an encoded string. """ # TODO: make provision for this to be overridden or modified by a hook # # TODO: Rather than running the status command, should prepare a draft of # the revision to be committed, then pause and ask the user to # confirm/write a message. from .diff import show_diff_trees template = make_commit_message_template(working_tree, specific_files) template = template.encode(output_encoding, "replace") if diff: stream = BytesIO() show_diff_trees( working_tree.basis_tree(), working_tree, stream, specific_files, path_encoding=output_encoding, ) template = template + b"\n" + stream.getvalue() return template class MessageEditorHooks(Hooks): """A dictionary mapping hook name to a list of callables for message editor hooks. e.g. ['commit_message_template'] is the list of items to be called to generate a commit message template """ def __init__(self): """Create the default hooks. These are all empty initially. """ Hooks.__init__(self, "breezy.msgeditor", "hooks") self.add_hook( "set_commit_message", "Set a fixed commit message. " "set_commit_message is called with the " "breezy.commit.Commit object (so you can also change e.g. " "revision properties by editing commit.builder._revprops) and the " "message so far. set_commit_message must return the message to " "use or None if it should use the message editor as normal.", (2, 4), ) self.add_hook( "commit_message_template", "Called when a commit message is being generated. " "commit_message_template is called with the breezy.commit.Commit " "object and the message that is known so far. " "commit_message_template must return a new message to use (which " "could be the same as it was given). When there are multiple " "hooks registered for commit_message_template, they are chained " "with the result from the first passed into the second, and so " "on.", (1, 10), ) hooks = MessageEditorHooks() def set_commit_message(commit, start_message=None): """Sets the commit message. :param commit: Commit object for the active commit. :return: The commit message or None to continue using the message editor. """ start_message = None for hook in hooks["set_commit_message"]: start_message = hook(commit, start_message) return start_message def generate_commit_message_template(commit, start_message=None): """Generate a commit message template. :param commit: Commit object for the active commit. :param start_message: Message to start with. :return: A start commit message or None for an empty start commit message. """ start_message = None for hook in hooks["commit_message_template"]: start_message = hook(commit, start_message) return start_message