Add the 'sync' command
[stgit] / stgit / commands / common.py
index 836884c..466f584 100644 (file)
@@ -98,6 +98,8 @@ def git_id(rev):
                 return series.get_patch(patch).get_old_top()
             elif patch_id == 'bottom.old':
                 return series.get_patch(patch).get_old_bottom()
+            elif patch_id == 'log':
+                return series.get_patch(patch).get_log()
         if patch == 'base' and patch_id == None:
             return read_string(series.get_base_file())
     except RevParseException:
@@ -107,18 +109,20 @@ def git_id(rev):
 def check_local_changes():
     if git.local_changes():
         raise CmdException, \
-              'local changes in the tree. Use "refresh" to commit them'
+              'local changes in the tree. Use "refresh" or "status --reset"'
 
 def check_head_top_equal():
     if not crt_series.head_top_equal():
-        raise CmdException, \
-              'HEAD and top are not the same. You probably committed\n' \
-              '  changes to the tree outside of StGIT. If you know what you\n' \
-              '  are doing, use the "refresh -f" command'
+        raise CmdException(
+            'HEAD and top are not the same. You probably committed\n'
+            '  changes to the tree outside of StGIT. To bring them\n'
+            '  into StGIT, use the "assimilate" command')
 
 def check_conflicts():
     if os.path.exists(os.path.join(basedir.get(), 'conflicts')):
-        raise CmdException, 'Unsolved conflicts. Please resolve them first'
+        raise CmdException, \
+              'Unsolved conflicts. Please resolve them first or\n' \
+              '  revert the changes with "status --reset"'
 
 def print_crt_patch(branch = None):
     if not branch:
@@ -194,20 +198,85 @@ def push_patches(patches, check_merged = False):
             else:
                 print 'done'
 
-def pop_patches(patches):
+def pop_patches(patches, keep = False):
     """Pop the patches in the list from the stack. It is assumed that
     the patches are listed in the stack reverse order.
     """
-    p = patches[-1]
-    if len(patches) == 1:
-        print 'Popping patch "%s"...' % p,
+    if len(patches) == 0:
+        print 'nothing to push/pop'
     else:
-        print 'Popping "%s" - "%s" patches...' % (patches[0], p),
-    sys.stdout.flush()
+        p = patches[-1]
+        if len(patches) == 1:
+            print 'Popping patch "%s"...' % p,
+        else:
+            print 'Popping "%s" - "%s" patches...' % (patches[0], p),
+        sys.stdout.flush()
+
+        crt_series.pop_patch(p, keep)
+
+        print 'done'
+
+def parse_patches(patch_args, patch_list, boundary = 0, ordered = False):
+    """Parse patch_args list for patch names in patch_list and return
+    a list. The names can be individual patches and/or in the
+    patch1..patch2 format.
+    """
+    patches = []
+
+    for name in patch_args:
+        pair = name.split('..')
+        for p in pair:
+            if p and not p in patch_list:
+                raise CmdException, 'Unknown patch name: %s' % p
+
+        if len(pair) == 1:
+            # single patch name
+            pl = pair
+        elif len(pair) == 2:
+            # patch range [p1]..[p2]
+            # inclusive boundary
+            if pair[0]:
+                first = patch_list.index(pair[0])
+            else:
+                first = -1
+            # exclusive boundary
+            if pair[1]:
+                last = patch_list.index(pair[1]) + 1
+            else:
+                last = -1
+
+            # only cross the boundary if explicitly asked
+            if not boundary:
+                boundary = len(patch_list)
+            if first < 0:
+                if last <= boundary:
+                    first = 0
+                else:
+                    first = boundary
+            if last < 0:
+                if first < boundary:
+                    last = boundary
+                else:
+                    last = len(patch_list)
+
+            if last > first:
+                pl = patch_list[first:last]
+            else:
+                pl = patch_list[(last - 1):(first + 1)]
+                pl.reverse()
+        else:
+            raise CmdException, 'Malformed patch name: %s' % name
 
-    crt_series.pop_patch(p)
+        for p in pl:
+            if p in patches:
+                raise CmdException, 'Duplicate patch name: %s' % p
 
-    print 'done'
+        patches += pl
+
+    if ordered:
+        patches = [p for p in patch_list if p in patches]
+
+    return patches
 
 def name_email(address):
     """Return a tuple consisting of the name and email parsed from a
@@ -234,12 +303,27 @@ def name_email_date(address):
 
     return str_list[0]
 
-def make_patch_name(msg):
+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.
-    """
+    from the first 30 characters of the top line of the string passed
+    as argument."""
     if not msg:
         return None
 
-    subject_line = msg.lstrip().split('\n', 1)[0]
+    subject_line = msg[:30].lstrip().split('\n', 1)[0].lower()
     return re.sub('[\W]+', '-', subject_line).strip('-')
+
+def make_patch_name(msg, unacceptable, default_name = 'patch',
+                    alternative = True):
+    """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 alternative and unacceptable(patchname):
+        suffix = 0
+        while unacceptable('%s-%d' % (patchname, suffix)):
+            suffix += 1
+        patchname = '%s-%d' % (patchname, suffix)
+    return patchname