Rename "stg coalesce" to "stg squash"
[stgit] / stgit / commands / sink.py
index 85cc70f..d4561ed 100644 (file)
@@ -17,26 +17,43 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 """
 
 import sys, os
-from optparse import OptionParser, make_option
-
+from stgit.argparse import opt
 from stgit.commands.common import *
 from stgit.utils import *
-from stgit import stack, git
-
-
-help = 'send patches deeper down the stack'
-usage = """%prog [-t <target patch>] [-n] [<patches>]
-
-Pop all patches (or all patches including <target patch>), then
-push the specified <patches> (the current patch by default), and
-then push back into place the formerly-applied patches (unless -n
-is also given)."""
-
-options = [make_option('-n', '--nopush',
-                       help = 'do not push the patches back after sinking',
-                       action = 'store_true'),
-           make_option('-t', '--to', metavar = 'TARGET',
-                       help = 'sink patches below TARGET patch')]
+from stgit import argparse, stack, git
+
+help = 'Send patches deeper down the stack'
+kind = 'stack'
+usage = ['[-t <target patch>] [-n] [<patches>]']
+description = """
+This is the opposite operation of stglink:float[]: move the specified
+patches down the stack.  It is for example useful to group stable
+patches near the bottom of the stack, where they are less likely to be
+impacted by the push of another patch, and from where they can be more
+easily committed or pushed.
+
+If no patch is specified on command-line, the current patch gets sunk.
+By default patches are sunk to the bottom of the stack, but the '--to'
+option allows to place them under any applied patch.
+
+Sinking internally involves popping all patches (or all patches
+including <target patch>), then pushing the patches to sink, and then
+(unless '--nopush' is also given) pushing back into place the
+formerly-applied patches."""
+
+args = [argparse.patch_range(argparse.applied_patches,
+                             argparse.unapplied_patches)]
+options = [
+    opt('-n', '--nopush', action = 'store_true',
+        short = 'Do not push the patches back after sinking', long = """
+        Do not push back on the stack the formerly-applied patches.
+        Only the patches to sink are pushed."""),
+    opt('-t', '--to', metavar = 'TARGET', args = [argparse.applied_patches],
+        short = 'Sink patches below the TARGET patch', long = """
+        Specify a target patch to place the patches below, instead of
+        sinking them to the bottom of the stack.""")]
+
+directory = DirectoryGotoToplevel(log = True)
 
 def func(parser, options, args):
     """Sink patches down the stack.
@@ -44,22 +61,47 @@ def func(parser, options, args):
 
     check_local_changes()
     check_conflicts()
-    check_head_top_equal()
+    check_head_top_equal(crt_series)
 
     oldapplied = crt_series.get_applied()
     unapplied = crt_series.get_unapplied()
-    all = unapplied + oldapplied
+    all = oldapplied + unapplied
+
+    if options.to and not options.to in oldapplied:
+        raise CmdException('Cannot sink below %s, since it is not applied'
+                           % options.to)
 
     if len(args) > 0:
         patches = parse_patches(args, all)
     else:
-        patches = [ crt_series.get_current() ]
-
-    crt_series.pop_patch(options.to or oldapplied[0])
-    push_patches(patches)
-
+        current = crt_series.get_current()
+        if not current:
+            raise CmdException('No patch applied')
+        patches = [current]
+
+    before_patches = after_patches = []
+
+    # pop necessary patches
+    if oldapplied:
+        if options.to:
+            pop_idx = oldapplied.index(options.to)
+        else:
+            pop_idx = 0
+        after_patches = [p for p in oldapplied[pop_idx:] if p not in patches]
+
+        # find the deepest patch to pop
+        sink_applied = [p for p in oldapplied if p in patches]
+        if sink_applied:
+            sinked_idx = oldapplied.index(sink_applied[0])
+            if sinked_idx < pop_idx:
+                # this is the case where sink brings patches forward
+                before_patches = [p for p in oldapplied[sinked_idx:pop_idx]
+                                  if p not in patches]
+                pop_idx = sinked_idx
+
+        crt_series.pop_patch(oldapplied[pop_idx])
+
+    push_patches(crt_series, before_patches)
+    push_patches(crt_series, patches)
     if not options.nopush:
-        newapplied = crt_series.get_applied()
-        def not_reapplied_yet(p):
-            return not p in newapplied
-        push_patches(filter(not_reapplied_yet, oldapplied))
+        push_patches(crt_series, after_patches)