Commits

Yujie Wu  committed bc50dbd Merge

flow: Merged <hotfix> '0.9.5' to <master> ('default').

  • Participants
  • Parent commits d04677e, b95ba9a
  • Tags v0.9.5

Comments (0)

Files changed (1)

File src/hgflow.py

-"""commands to support Driessen's branching model
-An implementation of Vincent Driessen's branching model for Mercurial
+"""commands to support generalized Driessen's branching model
 """
+# License GPL 2.0
 #
-# Authoring history of this fork:
-#  o Jan 03 2011 - Mar 18 2011  Original version by yinwm <yinweiming@gmail.com>
-#  o Jun 29 2011 -              Rewritten by Yujie Wu <yujie.wu2@gmail.com>
-
-
-
+# hgflow.py - Mercurial extension to support generalized Driessen's branching model
+# Copyright (C) 2011-2012, Yujie Wu
+#
+# This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2 of the License or any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
+# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+
+    
 import os
 import sys
 import copy
 
 
 
-VERSION                   = "0.9.4"
-CONFIG_BASENAME           = ".flow"
-OLD_CONFIG_BASENAME       = ".hgflow"
+VERSION                   = "0.9.5"
+CONFIG_BASENAME           = ".hgflow"
+OLD_CONFIG_BASENAME       = ".flow"
 CONFIG_SECTION_BRANCHNAME = "branchname"
 STRIP_CHARS               = '\'"'
 
     flow: note:   hg commit --message "flow: Closed release 0.7." --close_branch
     flow: note:   hg update default
     """
+
     def __init__( self ) :
         self.ui           = None
         self._cmd         = None
         self._cmd_history = []
         self._via_quiet   = False
         self._dryrun      = False
+        self._common_opts = {}
+
         
     
-    
     def __getattr__( self, name ) :
         """
         Typical invocation of mercurial commands is in the form: commands.name( ... ).
         cmd_str = "hg " + (self._cmd[:-1] if (self._cmd[-1] == "_") else self._cmd)
         arg     = self._branch2str(   arg )
         kwarg   = self._branch2str( kwarg )
-
+        cmd     = self._cmd
+
+        if (cmd[0] == "q") :
+            where = extensions.find( "mq" )
+            cmd   = cmd[1:]
+        elif (cmd == "strip" ) : where = extensions.find( "mq"     )
+        elif (cmd == "rebase") : where = extensions.find( "rebase" )
+        else                   : where = mercurial.commands
+
+        opts = self._filter_common_options( where, cmd )
+        opts.update( kwarg )
+        kwarg = opts
+        
         for key, value in kwarg.items() :
             if (value is None) :
                 continue
             new_key = ""
             for e in key :
                 new_key += "-" if (e == "_") else e
-            key = new_key
-            
-            if (isinstance( value, str ) and -1 < value.find( " " )) :
-                new_value = ""
-                for c in value :
-                    if   (c == "\\") : new_value += "\\\\"
-                    elif (c == '"' ) : new_value += "\""
-                    else             : new_value += c
-                value = '"%s"' % new_value
+            key   = new_key
+            value = self._add_quotes( value )
                 
             if (isinstance( value, bool )) :
                 cmd_str = "%s --%s" % (cmd_str, key,)
             else :
                 cmd_str = "%s --%s %s" % (cmd_str, key, str( value ),)
         for e in arg :
-            cmd_str = "%s %s " % (cmd_str, str( e ),)
+            cmd_str = '%s %s ' % (cmd_str, self._add_quotes( str( e ) ),)
 
         self._cmd_history.append( cmd_str )
         
             return
         
         try :
-            ret   = None
-            cmd   = self._cmd
-            where = mercurial.commands
-            if (self._cmd[0] == "q") :
-                where = extensions.find( "mq" )
-                cmd   = self._cmd[1:]
-            elif (self._cmd == "rebase") :
-                where = extensions.find( "rebase" )
-            ret = getattr( where, cmd )( ui, repo, *arg, **kwarg )
-            if (ret and self._cmd not in ["commit", "rebase",]) :
+            ret = None
+            ret = getattr( where, cmd )( ui, repo, *arg, **opts )
+            if (ret and cmd not in ["commit", "rebase",]) :
                 raise AbortFlow( "Nonzero return code from hg command" )
         except Exception, e :
             raise AbortFlow( "Hg command failed: %s" % cmd_str, "abort: %s\n" % str( e ) )
 
+
+
+    def _add_quotes( self, value ) :
+        """
+        If value is a string that contains space, slash, double quote, and parenthesis (viz: '(' or ')'), then wrap the value
+        with double quotes and properly escape double-quotes and slashes in the string, returning the treated value.
+        Otherwise, return the value as is.        
+        """
+        if (isinstance( value, str ) and (1 in [c in value for c in " ()\\\""])) :
+             new_value = ""
+             for c in value :
+                 if   (c == "\\") : new_value += "\\\\"
+                 elif (c == '"' ) : new_value += "\""
+                 else             : new_value += c
+             value = '"%s"' % new_value
+        return value
+
         
 
     def _branch2str( self, value ) :
             return new_value
         return value
     
-            
+
+
+    def _filter_common_options( self, where, cmd ) :
+        """
+        If any common options are valid options of the command, return these options.
+
+        @type  where: module
+        @param where: Should be `mercurial.commands', or a Mercurial's plugin object.
+        @type  cmd  : C{str}
+        @param cmd  : command name
+        """
+        ret = {}
+        if (self._common_opts != {}) :
+            junk, table = mercurial.cmdutil.findcmd( cmd, where.table )
+            opts        = [e[1] for e in table[1]]
+            for e in self._common_opts :
+                if (e in opts) :
+                    ret[e] = self._common_opts[e]
+        return ret
+
+     
 
     def use_quiet_channel( self, via_quiet = True ) :
         """
 
 
 
+    def reg_common_options( self, opts ) :
+        """
+        Register common options.
+
+        @type  opts: C{dict}
+        @param opts: Common options. Key = option's flag, value = option's value. 
+        """
+        self._common_opts.update( opts )
+
+
+
     def dryrun( self, switch = None ) :
         """
         Switch the dry-run mode.
         """
         self.ui   = ui
         self.repo = repo
-        self.ctx  = self.repo[rev]
+        self.ctx  = repo[rev]
         
         self._fullname = str( self.ctx.branch() )
 
                 except :
                     self._error( "Flow has not been initialized for this repository." )
                     sys.exit( 1 )
-                self._warn( "Configuration file format of flow has changed since v0.9." )
-                self._warn( "Please use `hg flow upgrade` to upgrade the configuration file." )
             else :
                 self._error( "Flow has not been initialized for this repository." )
                 sys.exit( 1 )
                 commands.update( self.ui, self.repo, rev, *arg, **kwarg )
             else :
                 raise e
+        
         if (old_workspace_ctx != self.curr_workspace.ctx) :
             commands.update( self.ui, self.repo, rev, *arg, **kwarg )
         
         """
         Check if 'mq' extension is activated. If not, raise an 'AbortFlow' exception.
         
-        @raise AbortFlow: When 'rebase' extension is not found
+        @raise AbortFlow: When 'mq' extension is not found
         """
         try :
             extensions.find( "mq" )
         except KeyError :
             raise AbortFlow( "Cannot shelve/unshelve changes without 'mq' extension." )
+
+        
+
+    def _check_strip( self ) :
+        """
+        The 'strip' command comes with the 'mq' extension.
+        Check if 'mq' extension is activated. If not, raise an 'AbortFlow' exception.
+        
+        @raise AbortFlow: When 'mq' extension is not found
+        """
+        try :
+            extensions.find( "mq" )
+        except KeyError :
+            raise AbortFlow( "Cannot use 'strip' command without 'mq' extension." )
             
 
-                
 
     def _is_shelved( self, branch ) :
         """
 
 
 
-    def _unshelve( self, *arg, **kwarg ) :
+    def _unshelve( self, basename = None, **kwarg ) :
         """
         Unshelve the previously shelved changes to the workspace if C{self.autoshelve} is C{True}.
 
             C{hg import <patch_filename> --no-commit}
             C{hg qdelete <patchname>}
         where <patchname> follows the pattern: flow/<branch_fullname>.pch, which was previously created by flow's shelving.
+
+        @type  basename: C{str}
+        @param basename: Base name of the shelved patch file. Default is the name of current workspace branch.
         """
         if (self.autoshelve) :
-            shelve_name = "flow/" + self.curr_workspace.fullname() + ".pch"
+            basename    = basename if (basename) else self.curr_workspace.fullname()
+            shelve_name = "flow/" + basename + ".pch"
             patch_fname = self.repo.join( "patches/" + shelve_name )
             if (os.path.isfile( patch_fname )) :
                 self._check_mq()
         kwarg    = _getopt( self.ui, "start", kwarg )
         rev      = kwarg.pop( "rev",     None )
         msg      = kwarg.pop( "message", ""   )
+        dirty    = kwarg.pop( "dirty",   None )
         fullname = stream.get_fullname( basename )
         if (self._find_branch( fullname )) :
             self._warn( "An open branch named '%s' already exists in %s." % (basename, stream,) )
         else :
+            shelvedpatch_basename = self.curr_workspace.fullname()
             if (rev is None) :
                 from_branch = stream.source().trunk()
                 self._shelve()
                 self._update( rev = rev )
             if (msg) :
                 msg = "%s\n" % msg
-            self._create_branch( fullname, "%s%sCreated branch '%s'." % (msg, self.msg_prefix, fullname,) )
+            self._create_branch( fullname, "%s%sCreated branch '%s'." % (msg, self.msg_prefix, fullname,), **kwarg )
+            if (dirty) :
+                self._unshelve( shelvedpatch_basename )
 
             
 
         kwarg     = _getopt( self.ui, "log", kwarg )
         filenames = kwarg.pop( "file", [] )
         onstream  = kwarg.pop( "onstream", False )
+        closed    = kwarg.pop( "closed",   False )
         if (onstream) :
             filenames.extend( arg[1:] )
-            branches = stream.branches()
+            branches = stream.branches( "all" if (closed) else "open" )
             if (stream._trunk) :
                 branches.append( stream._trunk )
         else :
         """
         kwarg          = _getopt( self.ui, "abort", kwarg )
         msg            = kwarg.pop( "message",  ""    )
+        should_erase   = kwarg.pop( "erase",    False )
         onstream       = kwarg.pop( "onstream", False )
         curr_workspace = self.curr_workspace
         if (msg) :
             elif (stream._trunk) :
                 branches.append( stream.trunk() )
             for branch in branches :
-                self._update( branch )
-                self._commit( close_branch = True, message = "%s%sAborted %s %s." %
-                              (msg, self.msg_prefix, stream, branch.basename( stream, should_quote = True ),) )
-            if (self.curr_workspace != self.orig_workspace) :
+                if (should_erase) :
+                    self._strip( rev = ["branch('%s')" % branch,] )
+                else :
+                    self._update( branch )
+                    self._commit( close_branch = True, message = "%s%sAborted %s %s." %
+                                  (msg, self.msg_prefix, stream, branch.basename( stream, should_quote = True ),) )
+            if (self.curr_workspace != self.orig_workspace and self._orig_workspace not in branches) :
                 self._update( self.orig_workspace )
         else :
             if (curr_workspace.is_trunk( stream )) :
             if (curr_workspace not in stream) :
                 raise AbortFlow( "Your workspace is '%s' branch, which is not in %s." % (curr_workspace, stream,),
                                  "To abort a %s branch, you must first update to it." % stream )
-            self._commit( close_branch = True, message = "%s%sAborted %s '%s'." %
-                          (msg, self.msg_prefix, stream, curr_workspace.basename( stream ),) )
+            if (should_erase) :
+                self._strip( rev = ["branch('%s')" % curr_workspace,] )
+            else :
+                self._commit( close_branch = True, message = "%s%sAborted %s '%s'." %
+                              (msg, self.msg_prefix, stream, curr_workspace.basename( stream ),) )
             self._update( stream.trunk( trace = True ) )
         self._unshelve()
 
         """
         kwarg          = _getopt( self.ui, "promote", kwarg )
         rev            = kwarg.pop( "rev",     None )
+        tag_name       = kwarg.pop( "tag",     None )
         message        = kwarg.pop( "message", None )
         message        = (message + "\n") if (message) else ""
         orig_workspace = self.curr_workspace
                 self._commit( message = message + ("%sPromoted %s '%s' (%s) to '%s'." %
                               (self.msg_prefix, stream, promoted_branch.basename( stream ),
                                short( promoted_node ), dest,)), **kwarg )
+                if (tag_name) :
+                    self._tag( tag_name, **kwarg )
         else :
-            for s in stream.destin() :
+            destin = [STREAM["master"],] if (STREAM["develop"] == stream) else stream.destin()
+            for s in destin :
                 if (s == stream) :
                     continue
                 trunk = s.trunk()
                     self._commit( message = message + ("%sPromoted %s '%s' (%s) to '%s'." %
                                   (self.msg_prefix, stream, promoted_branch.basename( stream ),
                                    short( promoted_node ), trunk,)), **kwarg )
+                    if (tag_name) :
+                        self._tag( tag_name, **kwarg )
                 else :
                     self._error( "Cannot determine promote destination." )
                     return
 
             
 
-    def _commit_change( self, opt ) :
+    def _commit_change( self, opt, erase_branch = False ) :
         """
         Commit the changes in the workspace.
         Note that this method can potentially mutate C{opt}. Specifically, it will delete the C{commit} and C{message} keys if
-        they are present in C{opt}.
+        they present in C{opt}.
         
         @type  opt: C{dict}
         @param opt: Option dictionary. Recognizable keys are C{commit} and C{message}. The value of C{commit} should be a
             except KeyError :
                 pass
         elif (opt.get( "message" )) :
-            raise AbortFlow( "Cannot use the specified commit message.", "Did you forget to specify the -c option?" )
+            if (erase_branch) :
+                del opt["message"]
+            else :
+                raise AbortFlow( "Cannot use the specified commit message.", "Did you forget to specify the -c option?" )
         
         
             
             tag_name = None
 
         kwarg          = _getopt( self.ui, "finish", kwarg )
+        message        = kwarg.get( "message",  None  )
         onstream       = kwarg.pop( "onstream", False )
+        should_erase   = kwarg.pop( "erase",    False )
         curr_workspace = self.curr_workspace
         curr_stream    = curr_workspace.stream()
         name           = curr_workspace.basename( stream, should_quote = True )
         tag_name       = tag_name if (tag_name) else ("v" + name[1:-1])
         develop_stream = STREAM["develop"]
-        
+
+        if (should_erase) :
+            if (onstream       ) : raise AbortFlow( "'--erase' cannot be used together with '--onstream'." )
+            if (message is None) : raise AbortFlow( "'--message' is required when '--erase' is used." )
+            self._check_strip()
+            
         if (onstream) :
             if (stream in [develop_stream, STREAM["support"], STREAM["hotfix"], STREAM["release"],]) :
                 raise AbortFlow( "You cannot finish %s." % stream )
             raise AbortFlow( "Your workspace is '%s' branch, which is not in %s." % (curr_workspace, stream,),
                              "To finish a %s branch, you must first update to it." % stream )
 
-        # Commits changes (if any) in the current branch.
-        self._commit_change( kwarg )
-
         # Merges the workspace to its `destin` streams.
         destin_with_trunk    = []
         destin_without_trunk = []
                 destin_with_trunk.append( s )
             else :
                 destin_without_trunk.append( s )
+
+        if (should_erase) :
+            if (len( destin_with_trunk + destin_without_trunk ) > 1) :
+                raise AbortFlow( "'--erase' cannot be applied to branches with multiple merge destinations." )
+        
+        # Commits changes (if any) in the current branch.
+        self._commit_change( kwarg, should_erase )
+
         for s in destin_without_trunk :
             trunk = s.trunk()
             so = Stream( self.ui, self.repo,
                                       (self.msg_prefix, tr_name, stream.name(), name, ss, dvtrunk,), **kwarg )
         if (final_branch) :
             self._update( final_branch )
-    
+        if (should_erase) :
+            rev = "p1(.)"
+            rev = mercurial.scmutil.revsingle( self.repo, rev ).rev()
+            self._update( "tip" )
+            self._update( rev   )
+            self._revert( rev = "-1", all = True )
+            self._strip ( rev = ["branch('%s')" % curr_workspace,] )
+            self._commit( message = message, **kwarg )
+            
             
 
     def _execute_action( self, stream, *arg, **kwarg ) :
         orig_repo_status = self.repo.status()[:4]
         for e in orig_repo_status :
             try :
-                e.remove( ".flow" )
+                e.remove( CONFIG_BASENAME )
             except ValueError :
                 pass
 
             with open( config_fname, "w" ) as fh :
                 config.write( fh )
             repo_status = self.repo.status( unknown = True )
-            if (".flow" in repo_status[0]) :
+            if (CONFIG_BASENAME in repo_status[0]) :
                 self._commit( config_fname, message = "flow initialization: Modified configuration file." )
-            elif (".flow" in repo_status[4]) :
+            elif (CONFIG_BASENAME in repo_status[4]) :
                 self._add   ( config_fname )
                 self._commit( config_fname, message = "flow initialization: Added configuration file." )
 
         """
         Upgrade older version to the latest version.
         """
-        self._print( "Upgrade flow's configuration file from v0.7 (or v0.8) to v0.9." )
+        self._print( "Upgrade flow's configuration file from v0.9.4 (or older) to v0.9.5 (or latter)." )
         config_fname     = os.path.join( self.repo.root,     CONFIG_BASENAME )
         old_config_fname = os.path.join( self.repo.root, OLD_CONFIG_BASENAME )
         workspace = self.curr_workspace
             self._rename( old_config_fname, config_fname, force = True )
             self._commit( message = "flow upgrade: Renamed flow's configuration file from '.hgflow' to '.flow'." )
         self._update( workspace )
-
-        
-
-def flow_init_cmd( ui, repo, *args, **kwarg ) :
-    """
-    Initialize flow.
-    This command has been deprecated.
-    """
-    _warn( ui, "`hg flow-init` has been deprecated. In the future use `hg flow init` instead." )
-    Flow( ui, repo, init = True ).init( *args, **kwarg )
    
     
 
 def flow_cmd( ui, repo, cmd = None, *arg, **kwarg ) :
-    """Flow is a Mercurial extension implementing a generalized branching model,
-where Driessen's model is only a special case.
+    """Flow is a Mercurial extension to support the generalized Driessen's branching model.
 
 actions:
 
     # Supresses bookmarks, otherwise if the name of a bookmark happens to be the same as a named branch, hg will use the
     # bookmark's revision.
     repo._bookmarks = {}
-    
+
     flow = Flow( ui, repo, cmd in ["init", "upgrade",] )
     func = {
         "init"     : flow.init,
     if (kwarg.get( "dry_run" )) :
         _print( ui, "This is a dry run." )
         commands.use_quiet_channel( True )
-        
+
+    # Registers common options (such as "user").
+    common_opts = {}
+    for e in ["user",] :
+        v = kwarg.get( e )
+        if (v) :
+            common_opts[e] = v
+    commands.reg_common_options( common_opts )
+
     try :
         # If `cmd' is a command (instead of an action), checks the options for it.
         if (cmd in func) :
   * flow-init   Command `hg flow-init` is replaced by `hg flow init`.
   * [hgflow]    The '[hgflow]' section name in hg's configuration file is
                 renamed to '[flow]'.
-  * .hgflow     Configuration file is renamed from '.hgflow' to '.flow'. You can
-                use {{{hg flow upgrade}}} command to rename the file in all open
-                branches.
 """,
 
 "@examples" : """
  -r --rev REV       Revision to start a new branch from.
  -m --message TEXT  Record TEXT as commit message when open new branch.
  -d --date DATE     Record the specified DATE as commit date.
- 
+ -u --user USER     Use specified USER as committer.
+    --dirty         Start a new branch from current dirty workspace branch and
+                    move all uncommitted changes to the new branch.
+
 The new branch is named after <stream-prefix>/<name>.
 """,
 
  -c --commit        Commit changes before close the branch.
  -m --message TEXT  Record TEXT as commit message.
  -d --date DATE     Record the specified DATE as commit date.
+ -u --user USER     Use specified USER as committer.
+ -e --erase         Erase branch after it is merged successfully.
+
+N.B.: RE. '--erase': A branch cannot be erased if it has been previously merged
+to other branches, creating nodes that are not erased together with the branch.
 """,
 
 "@push" : """
 options:
  -F --file FILE [+]  File to show history of.
  -d --date DATE      Show revisions matching date spec.
+ -u --user USER      Show revisions committed by USER.
  -k --keyword TEXT   Do case-insensitive search for a given text.
  -p --patch          Show patch.
  -g --git            Use git extended diff format to show patch.
  -l --limit VALUE    Limit number of changesets displayed.
+ -c --closed         Show closed branches when used together with -s option.
  
 [+] marked option can be specified multiple times.
 """,
         
 "@abort" : """
-Abort the workspace branch, which is to simply close the branch.
+Aborting the workspace branch can be done in two ways:
+1. The default way is simply marking the branch as closed so that it will not
+   show up when you list alive branches, but all changesets in the branch
+   remain in the repository and you cannot reuse the branch's name for a
+   different branch.
+2. The other way is erasing the branch, in other words, completely deleting the
+   branch and all changesets in it from the repository. This way is
+   devastating, but you can clear unneeded changesets and reuse the branch's
+   name. To abort a branch in this way, you just add the {{{-e}}} option.
+   N.B.: A branch cannot be erased if you have previously merged it to other
+   branches that remain in the repository.
 
 syntax:
-{{{hg flow <stream> abort [-m <TEXT>]}}}
+{{{hg flow <stream> abort [-m <TEXT>] [-e]}}}
 
 option:
  -m --message TEXT  Record TEXT as commit message when close branch.
+ -e --erase         Abort branch and erase it.
 """,
 
 "@promote" : """
 Merge the workspace branch to destination branches. The destination branches,
 if omitted, will default to the trunk of the destination stream. The destination
-streams of basic streams are listed as follows:
+streams of the basic streams are listed as follows:
 
    stream          destination
 ------------+-----------------------
  <feature>    <develop>
- <develop>    n/a
+ <develop>    <master>
  <release>    <develop> & <master>
  <hotfix>     <develop> & <master>
  <master>     n/a
  natural      stream-trunk
  
 syntax:
-{{{hg flow <stream> promote [<branch-full-name>...] [<option>...]}}}
+{{{hg flow <stream> promote [<destination-branch-full-name>...] [<option>...]}}}
 
 The workspace branch must be in <stream>. If the `-r` option is omitted, its
 value will default to the head of the workspace branch.
 
 option:
--r --rev REV       Revision to promote to other branches.
--m --message TEXT  Record TEXT as commit message when promote branch.
--d --date DATE     Record the specified DATE as commit date.
+ -r --rev REV       Revision to promote to other branches.
+ -m --message TEXT  Record TEXT as commit message when promote branch.
+ -t --tag NAME      Tag the merging changeset with NAME
+ -d --date DATE     Record the specified DATE as commit date.
+ -u --user USER     Use specified USER as committer.
+
+examples:
+{{{> hg flow develop promote -t v0.2.0}}}
+# Immediately release <develop> trunk's tip into <master>, bypassing <release>.
+# What this command exactly does is to promote the <develop> trunk into
+# <master> (<master> is <develop>'s default promotion destination, so you don't
+# have to spell it out in the command), and then label the <master> snapshot
+# with "v0.2.0".
 """,
         
 "@rebase" : """
 """,
 
 "@init" : """
-Initialize the flow extension for the repository. The configuration file: .flow
-will be written in the root dir of the repository. The file will be tracked by
-hg. If you have multiple open branches, the .flow file should be present and
-synchronized in all of them -- init command will do this for you automatically.
+Initialize the flow extension for the repository. The configuration file:
+{{{.hgflow}}} will be written in the root dir of the repository. The file will be
+tracked by hg. If you have multiple open branches, the file should be present
+and synchronized in all of them -- init command will do this for you
+automatically.
 
 syntax:
 {{{hg flow init [<option>...]}}}
 
 options:
--f --force  Force reinitializing flow.
+ -f --force  Force reinitializing flow.
+ -u --user USER     Use specified USER as committer.
 """,
 
 "@upgrade" : """
-Upgrade the configuration file from v0.7 or v0.8 to v0.9 or later.
+Upgrade the configuration file from v0.9.4 (or older) to v0.9.5 or later.
 
 syntax:
 {{{hg flow upgrade}}}
 
 
 OPT_FILTER = {
-"init"    : ("force",),
-"start"   : ("rev", "message", "date",),
-"finish"  : ("commit", "message", "date", "onstream",),
+"init"    : ("force", "user",),
+"start"   : ("rev", "message", "date", "user", "dirty",),
+"finish"  : ("commit", "message", "date", "user", "erase", "onstream",),
 "list"    : ("closed",),
-"log"     : ("file", "date", "keyword", "patch", "git", "limit", "graph", "onstream",),
-"abort"   : ("onstream", "message",),
-"promote" : ("rev", "message", "date", "onstream",),
+"log"     : ("file", "date", "user", "keyword", "patch", "git", "limit", "graph", "closed", "onstream",),
+"abort"   : ("erase", "message", "onstream",),
+"promote" : ("rev", "message", "tag", "date", "user", "onstream",),
 "rebase"  : ("dest", "onstream",),
 }
 
                 else :
                     ret[e] = not default_value
 
-    bad_opt = [e for e in     opt if (e not in (["history", "dry_run",] + ret.keys()) and opt[e])  ]
+    bad_opt = [e for e in     opt if (e not in (["history", "dry_run"] + ret.keys()) and opt[e])]
     bad_opt = [e for e in bad_opt if (e in sys.argv) or (OPT_CONFLICT.get( e, [0,] )[0] not in rec_short)]
     
     if (bad_opt) :
     (flow_cmd,
      [("",  "history",   False, _("Print history of hg commands used in this workflow."),                          ),
       ("",  "dry-run",   None,  _("Do not perform actions, just print history."),                                  ),
-      ("f", "force",     False, _("Force reinitializing flow. [init]"),                                            ),
-      ("r", "rev",       '',    _("Revision to start a new branch from. [start]"),                       _('REV'), ),
+      ("",  "dirty",     False, _("Start a new branch from a dirty workspace, and move all"
+                                  " uncommitted changes to the new branch. [start]"),                              ),
+      ("c", "closed",    False, _("Show normal and closed branches in stream. [list, log]"),                       ),
       ("c", "commit",    False, _("Commit changes before closing the branch. [finish]"),                           ),
       ("d", "date",      '',    _("Record the specified date as commit date. [start, finish, promote]"), _('DATE'),),
+      ("d", "date",      '',    _("Show revisions matching date spec. [log]"),                           _('DATE'),),
+      ("d", "dest",      '',    _("Destination changeset of rebasing. [rebase]"),                        _('REV' ),),
+      ("e", "erase",     False, _("Erase branch after it is merged successfully or aborted. [finish, abort]"),     ),
+      ("F", "file",      [],    _("File to show history of. [log]"),                                     _('FILE'),),
+      ("f", "force",     False, _("Force reinitializing flow. [init]"),                                            ),
+      ("g", "git",       False, _("Use git extended diff format to show patch. [log]"),                            ),
+      ("k", "keyword",   '',    _("Do case-insensitive search for a given text. [log]"),                 _('TEXT'),),
+      ("l", "limit",     '',    _("Limit number of changesets displayed. [log]"),                                  ),
       ("m", "message",   '',    _("Record TEXT as commit message. [start, finish, promote, abort]"),     _('TEXT'),),
-      ("c", "closed",    False, _("Show normal and closed branches in stream. [list]"),                            ),
-      ("F", "file",      [],    _("File to show history of. [log]"),                                     _('FILE'),),
-      ("d", "date",      '',    _("Show revisions matching date spec. [log]"),                           _('DATE'),),
-      ("k", "keyword",   '',    _("Do case-insensitive search for a given text. [log]"),                 _('TEXT'),),
       ("p", "patch",     False, _("Show patch. [log]"),                                                            ),
-      ("g", "git",       False, _("Use git extended diff format to show patch. [log]"),                            ),
-      ("l", "limit",     '',    _("Limit number of changesets displayed. [log]"),                                  ),
+      ("r", "rev",       '',    _("Revision to start a new branch from. [start]"),                       _('REV'), ),
       ("r", "rev",       '',    _("Revision to promote to other branches. [promote]"),                   _('REV'), ),
-      ("d", "dest",      '',    _("Destination changeset of rebasing. [rebase]"),                        _('REV' ),),
       ("s", "onstream",  False, _("Act on stream. [finish, rebase, log, abort]"),                                  ),
+      ("t", "tag",       '',    _("Tag the merging changeset with NAME. [promote]"),                     _('NAME'),),
+      ("u", "user",      '',    _("Use specified user as committer. [init, start, finish, promote]"),    _('USER'),),
+      ("u", "user",      '',    _("Show revisions committed by specified user. [log]"),                  _('USER'),),
      ],
      "hg flow {<stream> [<action> [<arg>]] | <command>} [<option>...]",
      ),
-
-"flow-init" :
-    (flow_init_cmd,
-     [("f", "force",   False, _("Force reinitializing flow."),),
-      ("",  "history", False, _("Print history of hg commands used by this flow command."),),
-      ],
-     ("hg flow-init"),
-     ),
 }