Teach new infrastructure to diff two trees
[stgit] / stgit / utils.py
index 02e98e9..b75c3b4 100644 (file)
@@ -1,7 +1,8 @@
 """Common utility functions
 """
 
-import errno, os, os.path, re, sys
+import errno, optparse, os, os.path, re, sys
+from stgit.exception import *
 from stgit.config import config
 from stgit.out import *
 
@@ -166,7 +167,7 @@ def rename(basedir, file1, file2):
         # file1's parent dir may not be empty after move
         pass
 
-class EditorException(Exception):
+class EditorException(StgException):
     pass
 
 def call_editor(filename):
@@ -174,12 +175,8 @@ def call_editor(filename):
 
     # the editor
     editor = config.get('stgit.editor')
-    if editor:
-        pass
-    elif 'EDITOR' in os.environ:
-        editor = os.environ['EDITOR']
-    else:
-        editor = 'vi'
+    if not editor:
+        editor = os.environ.get('EDITOR', 'vi')
     editor += ' %s' % filename
 
     out.start('Invoking the editor: "%s"' % editor)
@@ -188,6 +185,17 @@ def call_editor(filename):
         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 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."""
@@ -214,3 +222,121 @@ def make_patch_name(msg, unacceptable, default_name = 'patch'):
             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)
+
+def make_message_options():
+    def no_dup(parser):
+        if parser.values.message != None:
+            raise optparse.OptionValueError(
+                'Cannot give more than one --message or --file')
+    def no_combine(parser):
+        if (parser.values.message != None
+            and parser.values.save_template != None):
+            raise optparse.OptionValueError(
+                'Cannot give both --message/--file and --save-template')
+    def msg_callback(option, opt_str, value, parser):
+        no_dup(parser)
+        parser.values.message = value
+        no_combine(parser)
+    def file_callback(option, opt_str, value, parser):
+        no_dup(parser)
+        if value == '-':
+            parser.values.message = sys.stdin.read()
+        else:
+            f = file(value)
+            parser.values.message = f.read()
+            f.close()
+        no_combine(parser)
+    def templ_callback(option, opt_str, value, parser):
+        if value == '-':
+            def w(s):
+                sys.stdout.write(s)
+        else:
+            def w(s):
+                f = file(value, 'w+')
+                f.write(s)
+                f.close()
+        parser.values.save_template = w
+        no_combine(parser)
+    m = optparse.make_option
+    return [m('-m', '--message', action = 'callback', callback = msg_callback,
+              dest = 'message', type = 'string',
+              help = 'use MESSAGE instead of invoking the editor'),
+            m('-f', '--file', action = 'callback', callback = file_callback,
+              dest = 'message', type = 'string', metavar = 'FILE',
+              help = 'use FILE instead of invoking the editor'),
+            m('--save-template', action = 'callback', callback = templ_callback,
+              metavar = 'FILE', dest = 'save_template', type = 'string',
+              help = 'save the message template to FILE and exit')]
+
+def make_diff_opts_option():
+    def diff_opts_callback(option, opt_str, value, parser):
+        if value:
+            parser.values.diff_flags.extend(value.split())
+        else:
+            parser.values.diff_flags = []
+    return [optparse.make_option(
+        '-O', '--diff-opts', dest = 'diff_flags',
+        default = (config.get('stgit.diff-opts') or '').split(),
+        action = 'callback', callback = diff_opts_callback,
+        type = 'string', metavar = 'OPTIONS',
+        help = 'extra options to pass to "git diff"')]
+
+# 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
+
+def strip_leading(prefix, s):
+    """Strip leading prefix from a string. Blow up if the prefix isn't
+    there."""
+    assert s.startswith(prefix)
+    return s[len(prefix):]
+
+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