X-Git-Url: https://git.distorted.org.uk/~mdw/stgit/blobdiff_plain/6e83f4d7c39b981146f57aad8049c5622799dd00..87c8a573e61d900c7304a4e4a4808199f31ef1f6:/stgit/utils.py diff --git a/stgit/utils.py b/stgit/utils.py index 68b8f58..3a480c0 100644 --- a/stgit/utils.py +++ b/stgit/utils.py @@ -1,7 +1,10 @@ """Common utility functions """ -import errno, os, os.path +import errno, optparse, os, os.path, re, sys +from stgit.exception import * +from stgit.config import config +from stgit.out import * __copyright__ = """ Copyright (C) 2005, Catalin Marinas @@ -26,6 +29,14 @@ def mkdir_file(filename, mode): 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 """ @@ -37,6 +48,13 @@ def read_string(filename, multiline = False): 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 """ @@ -109,28 +127,26 @@ def strip_prefix(prefix, string): assert string.startswith(prefix) return string[len(prefix):] -def remove_dirs(basedir, dirs): - """Starting at join(basedir, dirs), remove the directory if empty, - and try the same with its parent, until we find a nonempty - directory or reach basedir.""" - path = dirs - while path: - try: - os.rmdir(os.path.join(basedir, path)) - except OSError: - return # can't remove nonempty directory - path = os.path.dirname(path) +def strip_suffix(suffix, string): + """Return string, without the suffix. Blow up if string doesn't + end with suffix.""" + assert string.endswith(suffix) + return string[:-len(suffix)] def remove_file_and_dirs(basedir, file): """Remove join(basedir, file), and then remove the directory it was in if empty, and try the same with its parent, until we find a nonempty directory or reach basedir.""" os.remove(os.path.join(basedir, file)) - remove_dirs(basedir, os.path.dirname(file)) + try: + os.removedirs(os.path.join(basedir, os.path.dirname(file))) + except OSError: + # file's parent dir may not be empty after removal + pass def create_dirs(directory): """Create the given directory, if the path doesn't already exist.""" - if directory: + if directory and not os.path.isdir(directory): create_dirs(os.path.dirname(directory)) try: os.mkdir(directory) @@ -145,4 +161,98 @@ def rename(basedir, file1, file2): full_file2 = os.path.join(basedir, file2) create_dirs(os.path.dirname(full_file2)) os.rename(os.path.join(basedir, file1), full_file2) - remove_dirs(basedir, os.path.dirname(file1)) + try: + os.removedirs(os.path.join(basedir, os.path.dirname(file1))) + except OSError: + # file1's parent dir may not be empty after move + pass + +class EditorException(StgException): + pass + +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) + if err: + raise EditorException, 'editor failed, exit code: %d' % err + out.done() + +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.""" + if not msg: + return None + + name_len = config.get('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('-')[:name_len] + +def make_patch_name(msg, unacceptable, default_name = 'patch'): + """Return a patch name generated from the given commit message, + guaranteed to make unacceptable(name) be false. If the commit + message is empty, base the name on default_name instead.""" + 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 + +# 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 make_sign_options(): + def callback(option, opt_str, value, parser, sign_str): + if parser.values.sign_str not in [None, sign_str]: + raise optparse.OptionValueError( + '--ack and --sign were both specified') + parser.values.sign_str = sign_str + return [optparse.make_option('--sign', action = 'callback', + callback = callback, dest = 'sign_str', + callback_args = ('Signed-off-by',), + help = 'add Signed-off-by line'), + optparse.make_option('--ack', action = 'callback', + callback = callback, dest = 'sign_str', + callback_args = ('Acked-by',), + help = 'add Acked-by line')] + +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)