"""
import errno, os, os.path, re, sys
+from stgit.exception import *
from stgit.config import config
+from stgit.out import *
__copyright__ = """
Copyright (C) 2005, Catalin Marinas <catalin.marinas@gmail.com>
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
-class MessagePrinter(object):
- def __init__(self):
- class Output(object):
- def __init__(self, write, flush):
- self.write = write
- self.flush = flush
- self.at_start_of_line = True
- self.level = 0
- def new_line(self):
- """Ensure that we're at the beginning of a line."""
- if not self.at_start_of_line:
- self.write('\n')
- self.at_start_of_line = True
- def single_line(self, msg, print_newline = True,
- need_newline = True):
- """Write a single line. Newline before and after are
- separately configurable."""
- if need_newline:
- self.new_line()
- if self.at_start_of_line:
- self.write(' '*self.level)
- self.write(msg)
- if print_newline:
- self.write('\n')
- self.at_start_of_line = True
- else:
- self.flush()
- self.at_start_of_line = False
- def tagged_lines(self, tag, lines):
- tag += ': '
- for line in lines:
- self.single_line(tag + line)
- tag = ' '*len(tag)
- def write_line(self, line):
- """Write one line of text on a lines of its own, not
- indented."""
- self.new_line()
- self.write('%s\n' % line)
- self.at_start_of_line = True
- def write_raw(self, string):
- """Write an arbitrary string, possibly containing
- newlines."""
- self.new_line()
- self.write(string)
- self.at_start_of_line = string.endswith('\n')
- self.__stdout = Output(sys.stdout.write, sys.stdout.flush)
- if sys.stdout.isatty():
- self.__out = self.__stdout
- else:
- self.__out = Output(lambda msg: None, lambda: None)
- def stdout(self, line):
- """Write a line to stdout."""
- self.__stdout.write_line(line)
- def stdout_raw(self, string):
- """Write a string possibly containing newlines to stdout."""
- self.__stdout.write_raw(string)
- def info(self, *msgs):
- for msg in msgs:
- self.__out.single_line(msg)
- def note(self, *msgs):
- self.__out.tagged_lines('Notice', msgs)
- def warn(self, *msgs):
- self.__out.tagged_lines('Warning', msgs)
- def error(self, *msgs):
- self.__out.tagged_lines('Error', msgs)
- def start(self, msg):
- """Start a long-running operation."""
- self.__out.single_line('%s ... ' % msg, print_newline = False)
- self.__out.level += 1
- def done(self, extramsg = None):
- """Finish long-running operation."""
- self.__out.level -= 1
- if extramsg:
- msg = 'done (%s)' % extramsg
- else:
- msg = 'done'
- self.__out.single_line(msg, need_newline = False)
-
-out = MessagePrinter()
-
def mkdir_file(filename, mode):
"""Opens filename with the given mode, creating the directory it's
in if it doesn't already exist."""
create_dirs(os.path.dirname(filename))
return file(filename, mode)
+def read_strings(filename):
+ """Reads the lines from a file
+ """
+ f = file(filename, 'r')
+ lines = [line.strip() for line in f.readlines()]
+ f.close()
+ return lines
+
def read_string(filename, multiline = False):
"""Reads the first line from a file
"""
f.close()
return result
+def write_strings(filename, lines):
+ """Write 'lines' sequence to file
+ """
+ f = file(filename, 'w+')
+ f.writelines([line + '\n' for line in lines])
+ f.close()
+
def write_string(filename, line, multiline = False):
"""Writes 'line' to file and truncates it
"""
# file1's parent dir may not be empty after move
pass
-class EditorException(Exception):
+class EditorException(StgException):
pass
+def get_editor():
+ for editor in [os.environ.get('GIT_EDITOR'),
+ config.get('stgit.editor'), # legacy
+ config.get('core.editor'),
+ os.environ.get('VISUAL'),
+ os.environ.get('EDITOR'),
+ 'vi']:
+ if editor:
+ return editor
+
def call_editor(filename):
"""Run the editor on the specified filename."""
-
- # the editor
- editor = config.get('stgit.editor')
- if editor:
- pass
- elif 'EDITOR' in os.environ:
- editor = os.environ['EDITOR']
- else:
- editor = 'vi'
- editor += ' %s' % filename
-
- out.start('Invoking the editor: "%s"' % editor)
- err = os.system(editor)
+ cmd = '%s %s' % (get_editor(), filename)
+ out.start('Invoking the editor: "%s"' % cmd)
+ err = os.system(cmd)
if err:
raise EditorException, 'editor failed, exit code: %d' % err
out.done()
+def edit_string(s, filename):
+ f = file(filename, 'w')
+ f.write(s)
+ f.close()
+ call_editor(filename)
+ f = file(filename)
+ s = f.read()
+ f.close()
+ os.remove(filename)
+ return s
+
+def find_patch_name(patchname, unacceptable):
+ """Find a patch name which is acceptable."""
+ if unacceptable(patchname):
+ suffix = 0
+ while unacceptable('%s-%d' % (patchname, suffix)):
+ suffix += 1
+ patchname = '%s-%d' % (patchname, suffix)
+ return patchname
+
def patch_name_from_msg(msg):
"""Return a string to be used as a patch name. This is generated
- from the top line of the string passed as argument, and is at most
- 30 characters long."""
+ from the top line of the string passed as argument."""
if not msg:
return None
+ name_len = config.getint('stgit.namelength')
+ if not name_len:
+ name_len = 30
+
subject_line = msg.split('\n', 1)[0].lstrip().lower()
- return re.sub('[\W]+', '-', subject_line).strip('-')[:30]
+ return re.sub('[\W]+', '-', subject_line).strip('-')[:name_len]
def make_patch_name(msg, unacceptable, default_name = 'patch'):
"""Return a patch name generated from the given commit message,
patchname = patch_name_from_msg(msg)
if not patchname:
patchname = default_name
- if unacceptable(patchname):
- suffix = 0
- while unacceptable('%s-%d' % (patchname, suffix)):
- suffix += 1
- patchname = '%s-%d' % (patchname, suffix)
- return patchname
+ return find_patch_name(patchname, unacceptable)
+
+# any and all functions are builtin in Python 2.5 and higher, but not
+# in 2.4.
+if not 'any' in dir(__builtins__):
+ def any(bools):
+ for b in bools:
+ if b:
+ return True
+ return False
+if not 'all' in dir(__builtins__):
+ def all(bools):
+ for b in bools:
+ if not b:
+ return False
+ return True
+
+def add_sign_line(desc, sign_str, name, email):
+ if not sign_str:
+ return desc
+ sign_str = '%s: %s <%s>' % (sign_str, name, email)
+ if sign_str in desc:
+ return desc
+ desc = desc.rstrip()
+ if not any(s in desc for s in ['\nSigned-off-by:', '\nAcked-by:']):
+ desc = desc + '\n'
+ return '%s\n%s\n' % (desc, sign_str)
+
+def parse_name_email(address):
+ """Return a tuple consisting of the name and email parsed from a
+ standard 'name <email>' or 'email (name)' string."""
+ address = re.sub(r'[\\"]', r'\\\g<0>', address)
+ str_list = re.findall(r'^(.*)\s*<(.*)>\s*$', address)
+ if not str_list:
+ str_list = re.findall(r'^(.*)\s*\((.*)\)\s*$', address)
+ if not str_list:
+ return None
+ return (str_list[0][1], str_list[0][0])
+ return str_list[0]
+
+def parse_name_email_date(address):
+ """Return a tuple consisting of the name, email and date parsed
+ from a 'name <email> date' string."""
+ address = re.sub(r'[\\"]', r'\\\g<0>', address)
+ str_list = re.findall('^(.*)\s*<(.*)>\s*(.*)\s*$', address)
+ if not str_list:
+ return None
+ return str_list[0]
+
+# Exit codes.
+STGIT_SUCCESS = 0 # everything's OK
+STGIT_GENERAL_ERROR = 1 # seems to be non-command-specific error
+STGIT_COMMAND_ERROR = 2 # seems to be a command that failed
+STGIT_CONFLICT = 3 # merge conflict, otherwise OK
+STGIT_BUG_ERROR = 4 # a bug in StGit
+
+def add_dict(d1, d2):
+ """Return a new dict with the contents of both d1 and d2. In case of
+ conflicting mappings, d2 takes precedence."""
+ d = dict(d1)
+ d.update(d2)
+ return d