.hgext/hgflow.py

changeset 253
a1accbd675a0
parent 8
ba72a71f23e6
equal deleted inserted replaced
252:e5f59db6e92a 253:a1accbd675a0
1 """commands to support generalized Driessen's branching model 1 """commands to support generalized Driessen's branching model
2 """ 2 """
3 # License GPL 2.0 3 # License GPL 2.0
4 # 4 #
5 # hgflow.py - Mercurial extension to support generalized Driessen's branching model 5 # hgflow.py - Mercurial extension to support generalized Driessen's branching model
6 # Copyright (C) 2011-2012, Yujie Wu 6 # Copyright (C) 2011-2014, Yujie Wu and others
7 # 7 #
8 # This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License 8 # This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License
9 # as published by the Free Software Foundation; either version 2 of the License or any later version. 9 # as published by the Free Software Foundation; either version 2 of the License or any later version.
10 # 10 #
11 # This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 11 # This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
110 # Refer to an exact string. 110 # Refer to an exact string.
111 ############################################################################################################################### 111 ###############################################################################################################################
112 112
113 113
114 114
115 VERSION = "0.9.7" 115 VERSION = "0.9.8"
116 CONFIG_BASENAME = ".hgflow" 116 CONFIG_BASENAME = ".hgflow"
117 OLD_CONFIG_BASENAME = ".flow" 117 OLD_CONFIG_BASENAME = ".flow"
118 CONFIG_SECTION_BRANCHNAME = "branchname" 118 CONFIG_SECTION_BRANCHNAME = "branchname"
119 STRIP_CHARS = '\'"' 119 STRIP_CHARS = '\'"'
120 120
313 self._via_quiet = False 313 self._via_quiet = False
314 self._dryrun = False 314 self._dryrun = False
315 self._common_opts = {} 315 self._common_opts = {}
316 self._opt_mutator = {} 316 self._opt_mutator = {}
317 317
318 self.reg_option_mutator( "strip", lambda opts : dict( {"rev" : [],}, **opts ) )
319 self.reg_option_mutator( "graft", lambda opts : dict( {"rev" : [], "continue" : False,}, **opts ) )
320 self.reg_option_mutator( "log", lambda opts : dict( {"date" : None, "user" : None, "rev" : None,}, **opts ) )
321
318 322
319 323
320 def __getattr__( self, name ) : 324 def __getattr__( self, name ) :
321 """ 325 """
322 Typical invocation of mercurial commands is in the form: commands.name( ... ). 326 Typical invocation of mercurial commands is in the form: commands.name( ... ).
329 333
330 334
331 def __call__( self, ui, repo, *arg, **kwarg ) : 335 def __call__( self, ui, repo, *arg, **kwarg ) :
332 """ 336 """
333 Invoke the mercurial command and save it as a string into the history. 337 Invoke the mercurial command and save it as a string into the history.
338
334 @raise AbortFlow: Throw exception if the return code of hg command (except C{commit} and C{rebase}) is nonzero. 339 @raise AbortFlow: Throw exception if the return code of hg command (except C{commit} and C{rebase}) is nonzero.
335 """ 340 """
336 self.ui = ui 341 self.ui = ui
337 cmd_str = "hg " + (self._cmd[:-1] if (self._cmd[-1] == "_") else self._cmd) 342 cmd_str = "hg " + (self._cmd[:-1] if (self._cmd[-1] == "_") else self._cmd)
338 arg = self._branch2str( arg ) 343 arg = self._branch2str( arg )
346 elif (cmd == "rebase") : where = extensions.find( "rebase" ) 351 elif (cmd == "rebase") : where = extensions.find( "rebase" )
347 else : where = mercurial.commands 352 else : where = mercurial.commands
348 353
349 kwarg = self._mutate_options( where, self._cmd, kwarg ) 354 kwarg = self._mutate_options( where, self._cmd, kwarg )
350 355
351 for key, value in kwarg.items() : 356 for key, value in sorted( kwarg.items(), reverse = True ) :
352 if (value in [None, ""]) : 357 if (value in [None, "", False]) :
353 continue 358 continue
354 359
355 # If the command is `hg commit --message <commit-hint> --force-editor [other-options...]`, we will drop the 360 # If the command is `hg commit --message <commit-hint> --force-editor [other-options...]`, we will drop the
356 # `--message <commit-hint> --force-editor` part from the command string because `--force-editor` is not a command 361 # `--message <commit-hint> --force-editor` part from the command string because `--force-editor` is not a command
357 # option (it avails only programmatically). 362 # option (it avails only programmatically).
399 404
400 405
401 406
402 def _add_quotes( self, value ) : 407 def _add_quotes( self, value ) :
403 """ 408 """
404 If value is a string that contains space, slash, double quote, and parenthesis (viz: '(' or ')'), then wrap the value 409 If C{value} is a string that contains space, slash, double quote, and parenthesis (viz: '(' or ')'), wrap it with
405 with double quotes and properly escape double-quotes and slashes in the string, returning the treated value. 410 double quotes and properly escape the double-quotes and slashes within, and finally return the modified string.
406 Otherwise, return the value as is. 411 Otherwise, return the value as is.
407 """ 412 """
408 if (isinstance( value, str ) and (1 in [c in value for c in " ()\\\""])) : 413 if (isinstance( value, str ) and (1 in [c in value for c in " ()\\\""])) :
409 new_value = "" 414 new_value = ""
410 for c in value : 415 for c in value :
442 If any common options are valid options of the command, return these options. 447 If any common options are valid options of the command, return these options.
443 448
444 @type where: module 449 @type where: module
445 @param where: Should be `mercurial.commands', or a Mercurial's plugin object. 450 @param where: Should be `mercurial.commands', or a Mercurial's plugin object.
446 @type cmd : C{str} 451 @type cmd : C{str}
447 @param cmd : command name 452 @param cmd : Mercurial command name
448 """ 453 """
449 ret = {} 454 ret = {}
450 if (self._common_opts != {}) : 455 if (self._common_opts != {}) :
451 if (cmd[-1] == "_") : 456 if (cmd[-1] == "_") :
452 cmd = cmd[:-1] 457 cmd = cmd[:-1]
459 464
460 465
461 466
462 def _mutate_options( self, where, cmd, opts ) : 467 def _mutate_options( self, where, cmd, opts ) :
463 """ 468 """
464 469 Call the registered command option mutator for the command and return the modified command options.
470
471 @type where: module
472 @param where: Should be `mercurial.commands', or a Mercurial's plugin object.
473 @type cmd : C{str}
474 @param cmd : Mercurial command name
475 @type opts : C{dict}
476 @param opts : Original command options
477
478 @rtype: C{dict}
465 """ 479 """
466 common_opts = self._filter_common_options( where, cmd ) 480 common_opts = self._filter_common_options( where, cmd )
467 common_opts.update( opts ) 481 common_opts.update( opts )
468 opts = common_opts 482 opts = common_opts
469 mutator = self._opt_mutator.get( cmd ) 483 mutator = self._opt_mutator.get( cmd )
476 490
477 def use_quiet_channel( self, via_quiet = True ) : 491 def use_quiet_channel( self, via_quiet = True ) :
478 """ 492 """
479 Print the history to the I{quiet} channel, where text will be displayed even when user does not specify the 493 Print the history to the I{quiet} channel, where text will be displayed even when user does not specify the
480 C{--verbose} option. 494 C{--verbose} option.
495
496 @type via_quiet: C{bool}
497 @param via_quiet: To turn off using the "quiet" channel for history printing, you can call this function like:
498 C{use_quiet_channel( False )}.
481 """ 499 """
482 self._via_quiet = via_quiet 500 self._via_quiet = via_quiet
483 501
484 502
485 503
486 def use_verbose_channel( self, via_verbose = True ) : 504 def use_verbose_channel( self, via_verbose = True ) :
487 """ 505 """
488 Print the history to the I{verbose} channel, where text will be display only when user specify the C{--verbose} option. 506 Print the history to the I{verbose} channel, where text will be display only when user specify the C{--verbose} option.
507
508 @type via_verbose: C{bool}
509 @param via_verbose: To turn off using the "verbose" channel for history printing (you will be using the "quiet"
510 channel instead), you can call this function like: C{use_verbose_channel( False )}.
489 """ 511 """
490 self._via_quiet = not via_verbose 512 self._via_quiet = not via_verbose
491 513
492 514
493 515
504 526
505 def reg_option_mutator( self, cmd, mutator ) : 527 def reg_option_mutator( self, cmd, mutator ) :
506 """ 528 """
507 Register common options. 529 Register common options.
508 530
509 @type opts: C{dict} 531 @type cmd : C{str}
510 @param opts: Common options. Key = option's flag, value = option's value. 532 @param cmd : Mercurial command name
533 @type mutator: Callable object
534 @param mutator: It will take a C{dict} object as its argument and return another C{dict} object that is supposed to be
535 a mutant of the former. The input object is supposed to be the original hg-command options in form of
536 key-value pairs.
511 """ 537 """
512 self._opt_mutator[cmd] = mutator 538 self._opt_mutator[cmd] = mutator
513 539
514 540
515 541
547 same object as in C{STREAM}. If not, create and return a new C{stream} object. If the new object is not in the standard 573 same object as in C{STREAM}. If not, create and return a new C{stream} object. If the new object is not in the standard
548 streams, an C{AbnormalStream} exception will be thrown. One can catch the exception and call its C{stream} method to 574 streams, an C{AbnormalStream} exception will be thrown. One can catch the exception and call its C{stream} method to
549 get the object. 575 get the object.
550 576
551 @type name : C{str} 577 @type name : C{str}
552 @param name : Name of the stream 578 @param name : Name of the stream. It can be a complex stream name, e.g., "develop/spring:release".
553 @type check: C{boolean} 579 @type check: C{boolean}
554 @param check: If true and the stream is not a standard one, the function will check if the trunk of the stream exists 580 @param check: If true and the stream is not a standard one, the function will check if the trunk of the stream exists
555 or not and (if exists) open or not. 581 or not and (if exists) open or not.
556 582
557 @raise AbortFlow : When C{check} is true and the trunk of the stream doesn't exist or is closed 583 @raise AbortFlow : When C{check} is true and the trunk of the stream doesn't exist or is closed
558 @raise AbnormalStream: When the stream is not in any of the standard streams 584 @raise AbnormalStream: When the stream is not in any of the standard streams
559 """ 585 """
586 source = None
587 tokens = name.split( ':' )
588 n = len( tokens )
589 if (n == 2) :
590 source, name = tokens[0], tokens[1]
591 if (n > 2 or not name) :
592 raise AbortFlow( "Invalid stream syntax: '%s'" % stream )
593
560 for e in STREAM.values() : 594 for e in STREAM.values() :
561 if (name == e.name()) : 595 if (name == e.name()) :
562 return e 596 if (source) :
563 597 stream = copy.copy( e )
564 rootstream_name = name.split( '/', 1 )[0] 598 break
565 is_normalstream = True 599 else :
566 if (rootstream_name in STREAM) : 600 return e
567 trunk = name.replace( rootstream_name + '/', STREAM[rootstream_name].prefix(), 1 )
568 stream = Stream( ui, repo, name, trunk = trunk )
569 else : 601 else :
570 stream = Stream( ui, repo, name, trunk = name ) 602 rootstream_name = name.split( '/', 1 )[0]
571 is_normalstream = False 603 is_normalstream = True
572 if (check) : 604
573 try : 605 if (rootstream_name in STREAM) :
574 trunk = stream.trunk() 606 trunk = name.replace( rootstream_name + '/', STREAM[rootstream_name].prefix(), 1 )
575 except error.RepoLookupError : 607 stream = Stream( ui, repo, name, trunk = trunk )
576 misspelling = difflib.get_close_matches( stream.name(), STREAM.keys(), 3, 0.7 ) 608 else :
577 note = "Did you mean: %s?" % " or ".join( misspelling ) if (misspelling) else None 609 stream = Stream( ui, repo, name, trunk = name )
578 raise AbortFlow( "Stream not found: %s" % stream, note = note ) 610 is_normalstream = False
579 if (trunk.is_closed()) : 611
580 raise AbortFlow( "%s has been closed." % stream ) 612 if (check) :
581 if (not is_normalstream) : 613 try :
582 raise AbnormalStream( stream = Stream( ui, repo, name ) ) 614 trunk = stream.trunk()
615 except error.RepoLookupError , e :
616 misspelling = difflib.get_close_matches( stream.name(), STREAM.keys(), 3, 0.7 )
617 note = "Did you mean: %s?" % " or ".join( misspelling ) if (misspelling) else None
618 raise AbortFlow( "Stream not found: %s" % stream, note = note )
619 if (trunk.is_closed()) :
620 raise AbortFlow( "%s has been closed." % stream )
621
622 # It seems we never really mind abnormal streams. So comment this out.
623 #if (not is_normalstream) :
624 # raise AbnormalStream( stream = Stream( ui, repo, name ) )
625
626 if (source) :
627 source = Stream.gen( ui, repo, source, check = True )
628 stream = copy.copy( stream )
629 stream._source = source
630 for i, e in enumerate( stream.destin() ) :
631 if (source in e) :
632 stream._destin[i] = source
633 break
634 else :
635 stream._destin = [source]
636
583 return stream 637 return stream
584 638
585 639
586 640
587 def __init__( self, ui, repo, name, **kwarg ) : 641 def __init__( self, ui, repo, name, **kwarg ) :
666 Return the trunk of this stream. If it has no trunk, return C{None} or the trunk of the source stream depending on the 720 Return the trunk of this stream. If it has no trunk, return C{None} or the trunk of the source stream depending on the
667 C{trace} parameter. 721 C{trace} parameter.
668 722
669 @type trace: C{boolean} 723 @type trace: C{boolean}
670 @param trace: If true and this stream has no trunk, return the trunk of the source stream, and do this recursively 724 @param trace: If true and this stream has no trunk, return the trunk of the source stream, and do this recursively
671 until a trunk is found. If false, this function will return C{None}. 725 until a trunk is found. If false and this stream has no trunk, this function will return C{None}.
672 726
673 @return: A C{Branch} object or C{None} 727 @return: A C{Branch} object or C{None}
674 """ 728 """
675 if (self._tcache) : 729 if (self._tcache) :
676 return self._tcache 730 return self._tcache
677 731
678 trunk = Branch( self.ui, self.repo, self._trunk ) if (self._trunk) else None 732 trunk = Branch( self.ui, self.repo, self._trunk ) if (self._trunk) else None
679 if (not trunk and trace) : 733 if (not trunk and trace) :
680 return self.source().trunk( True ) 734 return self.source().trunk( True )
681 self._tcache = trunk 735 self._tcache = trunk
682 return trunk 736 return trunk
749 closed branches in this stream; if C{"all"}, returns all open and closed branches in this stream. 803 closed branches in this stream; if C{"all"}, returns all open and closed branches in this stream.
750 """ 804 """
751 if (openclosed not in ["open", "closed", "all",]) : 805 if (openclosed not in ["open", "closed", "all",]) :
752 raise ValueError( "Invalid value for `openclosed` parameter: %s" % openclosed ) 806 raise ValueError( "Invalid value for `openclosed` parameter: %s" % openclosed )
753 807
808 all_branches = []
754 if (openclosed == "open") : 809 if (openclosed == "open") :
755 all_branches = [Branch( self.ui, self.repo, head[0] ) for branch_fullname, head in self.repo.branchmap().items() 810 for branch_fullname, heads in self.repo.branchmap().items() :
756 if (not self.repo[head[0]].extra().get( "close", False ) )] 811 all_branches += [Branch( self.ui, self.repo, head ) for head in heads
812 if (not self.repo[head].extra().get( "close", False ))]
757 elif (openclosed == "closed") : 813 elif (openclosed == "closed") :
758 all_branches = [Branch( self.ui, self.repo, head[0] ) for branch_fullname, head in self.repo.branchmap().items() 814 for branch_fullname, heads in self.repo.branchmap().items() :
759 if (self.repo[head[0]].extra().get( "close", False ) )] 815 all_branches += [Branch( self.ui, self.repo, head ) for head in heads
816 if (self.repo[head].extra().get( "close", False ))]
760 else : 817 else :
761 all_branches = [Branch( self.ui, self.repo, head[0] ) for branch_fullname, head in self.repo.branchmap().items()] 818 for branch_fullname, heads in self.repo.branchmap().items() :
819 all_branches += [Branch( self.ui, self.repo, head ) for head in heads]
762 820
763 return sorted( [e for e in all_branches if (e in self)] ) 821 return sorted( [e for e in all_branches if (e in self)] )
764 822
765 823
766 824
809 """ 867 """
810 Return the basename relative to the C{stream}. If C{stream} is C{None}, return the shortest possible basename (will 868 Return the basename relative to the C{stream}. If C{stream} is C{None}, return the shortest possible basename (will
811 not contain any '/'s). 869 not contain any '/'s).
812 Return the string "trunk" if this branch is the trunk of the C{stream}. 870 Return the string "trunk" if this branch is the trunk of the C{stream}.
813 871
814 @type stream: C{Stream} or C{None} 872 @type stream: C{Stream} or C{None}
815 @param stream: Stream to which the basename is relative 873 @param stream: Stream to which the basename is relative
874 @type should_quote: C{bool}
875 @param should_quote: The returned string will be wrapped with single quotes ' if this parameter's value is true.
816 """ 876 """
817 if (stream) : 877 if (stream) :
818 if (self._fullname == stream._trunk) : 878 if (self._fullname == stream._trunk) :
819 return "trunk" 879 return "trunk"
820 ret = self._fullname[len( stream.prefix() ):] 880 ret = self._fullname[len( stream.prefix() ):]
821 else : 881 else :
822 ret = self._fullname.rsplit( '/', 1 )[-1] 882 ret = self._fullname.rsplit( '/', 1 )[-1]
823 if (should_quote) : 883 if (should_quote) :
824 ret = "'%s'" % ret 884 ret = "'%s'" % ret
825 return ret 885 return ret
826 886
887
888
889 def rev_node( self ) :
890 """
891 Return a string showing this branch's head's revision number and short node ID in the format of "<rev>:<node-ID>",
892 e.g., "9:db14bf692069".
893 """
894 return "%s:%s" % (self.ctx.rev(), short( self.ctx.node() ),)
895
827 896
828 897
829 def is_closed( self ) : 898 def is_closed( self ) :
830 """ 899 """
831 Return true if this branch is closed; or false if it is open. 900 Return true if this branch is closed; or false if it is open.
889 STREAM = {} # key = stream name, value = `Stream` object. Will be set by `Flow.__init__`. 958 STREAM = {} # key = stream name, value = `Stream` object. Will be set by `Flow.__init__`.
890 959
891 960
892 961
893 class Flow( object ) : 962 class Flow( object ) :
963
964 ACTION_NAME = ["start", "finish", "push", "publish", "pull", "list", "log", "abort", "promote", "rebase", "rename",]
965
894 def __init__( self, ui, repo, init = False ) : 966 def __init__( self, ui, repo, init = False ) :
895 """ 967 """
896 Construct a C{Flow} instance that will execute the workflow. 968 Construct a C{Flow} instance that will execute the workflow.
897 Construction will fail if the C{flow} extension has not been initialized for the repository. 969 Construction will fail if the C{flow} extension has not been initialized for the repository.
898 A warning message will be issued if the repository has uncommitted changes. 970 A warning message will be issued if the repository has uncommitted changes.
944 hotfix = cfg.get( CONFIG_SECTION_BRANCHNAME, "hotfix" ) 1016 hotfix = cfg.get( CONFIG_SECTION_BRANCHNAME, "hotfix" )
945 support = cfg.get( CONFIG_SECTION_BRANCHNAME, "support" ) 1017 support = cfg.get( CONFIG_SECTION_BRANCHNAME, "support" )
946 except Exception, e : 1018 except Exception, e :
947 self._error( str( e ) ) 1019 self._error( str( e ) )
948 self._error( "Flow has not been initialized properly for this repository." ) 1020 self._error( "Flow has not been initialized properly for this repository." )
949 self._note ( "You can use command `hg flow init -f` to reinitialize for this repository.", via_quiet = True ) 1021 self._note ( "You can use command `hg flow init -f` to reinitialize for this repository.",
1022 via_quiet = True )
950 sys.exit( 1 ) 1023 sys.exit( 1 )
951 else : 1024 else :
952 self._error( "Flow has not been initialized for this repository: %s file is missing." % CONFIG_BASENAME ) 1025 self._error( "Flow has not been initialized for this repository: %s file is missing." % CONFIG_BASENAME )
953 self._note ( "You can use command `hg flow init` to initialize for this repository.", via_quiet = True ) 1026 self._note ( "You can use command `hg flow init` to initialize for this repository.", via_quiet = True )
954 sys.exit( 1 ) 1027 sys.exit( 1 )
1140 C{hg import <patch_filename> --no-commit} 1213 C{hg import <patch_filename> --no-commit}
1141 C{hg qdelete <patchname>} 1214 C{hg qdelete <patchname>}
1142 where <patchname> follows the pattern: flow/<branch_fullname>.pch, which was previously created by flow's shelving. 1215 where <patchname> follows the pattern: flow/<branch_fullname>.pch, which was previously created by flow's shelving.
1143 1216
1144 @type basename: C{str} 1217 @type basename: C{str}
1145 @param basename: Base name of the shelved patch file. Default is the name of current workspace branch. 1218 @param basename: Basename of the path of the shelved patch file. Default is the name of current workspace branch.
1146 """ 1219 """
1147 if (self.autoshelve or kwarg.get( "force" )) : 1220 if (self.autoshelve or kwarg.get( "force" )) :
1148 basename = basename if (basename) else self.curr_workspace.fullname() 1221 basename = basename if (basename) else self.curr_workspace.fullname()
1149 shelve_name = "flow/" + basename + ".pch" 1222 shelve_name = "flow/" + basename + ".pch"
1150 patch_fname = self.repo.join( "patches/" + shelve_name ) 1223 patch_fname = self.repo.join( "patches/" + shelve_name )
1173 @param openclosed: If C{"open"}, return all open branches; if C{"closed"}, return all closed branches; if C{"all"}, 1246 @param openclosed: If C{"open"}, return all open branches; if C{"closed"}, return all closed branches; if C{"all"},
1174 return all branches. 1247 return all branches.
1175 """ 1248 """
1176 if (openclosed not in ["open", "closed", "all",]) : 1249 if (openclosed not in ["open", "closed", "all",]) :
1177 raise ValueError( "Invalid value for openclosed parameter: %s" % openclosed ) 1250 raise ValueError( "Invalid value for openclosed parameter: %s" % openclosed )
1251
1252 all_branches = []
1178 if (openclosed == "open") : 1253 if (openclosed == "open") :
1179 all_branches = [Branch( self.ui, self.repo, head[0] ) for branch_fullname, head in self.repo.branchmap().items() 1254 for branch_fullname, heads in self.repo.branchmap().items() :
1180 if (not self.repo[head[0]].extra().get( "close", False ) )] 1255 all_branches += [Branch( self.ui, self.repo, head ) for head in heads
1256 if (not self.repo[head].extra().get( "close", False ))]
1181 elif (openclosed == "closed") : 1257 elif (openclosed == "closed") :
1182 all_branches = [Branch( self.ui, self.repo, head[0] ) for branch_fullname, head in self.repo.branchmap().items() 1258 for branch_fullname, heads in self.repo.branchmap().items() :
1183 if (self.repo[head[0]].extra().get( "close", False ))] 1259 all_branches += [Branch( self.ui, self.repo, head ) for head in heads
1260 if (self.repo[head].extra().get( "close", False ))]
1184 else : 1261 else :
1185 all_branches = [Branch( self.ui, self.repo, head[0] ) for branch_fullname, head in self.repo.branchmap().items()] 1262 for branch_fullname, heads in self.repo.branchmap().items() :
1263 all_branches += [Branch( self.ui, self.repo, head ) for head in heads]
1264
1186 return all_branches 1265 return all_branches
1187 1266
1188 1267
1189 1268
1190 def _find_branch( self, fullname ) : 1269 def _find_branch( self, fullname ) :
1191 """ 1270 """
1192 Return true if a branch C{fullname} is open. 1271 Try to find a branch of name: C{fullname}. If it exists, return a C{Branch} object of this branch and a boolean value
1272 indicating if it's open (C{True}) or closed (C{False}). If it does not exists, return C{(None, None)}.
1193 1273
1194 @type fullname: C{str} 1274 @type fullname: C{str}
1195 @param fullname: Fullname of branch that you want to know whether it is open 1275 @param fullname: Fullname of the branch to find
1196 """ 1276 """
1197 try : 1277 try :
1198 Branch( self.ui, self.repo, fullname ) 1278 branch = Branch( self.ui, self.repo, fullname )
1199 return True 1279 return branch, branch.is_open()
1200 except error.RepoLookupError : 1280 except error.RepoLookupError :
1201 return False 1281 return None, None
1202 1282
1203 1283
1204 1284
1205 def latest_master_tags( self ) : 1285 def latest_master_tags( self ) :
1206 """ 1286 """
1261 try : 1341 try :
1262 basename = arg[1] 1342 basename = arg[1]
1263 except IndexError : 1343 except IndexError :
1264 raise AbortFlow( "You must specify a name for the new branch to start." ) 1344 raise AbortFlow( "You must specify a name for the new branch to start." )
1265 1345
1266 rev = kwarg.pop( "rev", None ) 1346 rev = kwarg.pop( "rev", None )
1267 msg = kwarg.pop( "message", "" ) 1347 msg = kwarg.pop( "message", "" )
1268 dirty = kwarg.pop( "dirty", None ) 1348 dirty = kwarg.pop( "dirty", None )
1269 fullname = stream.get_fullname( basename ) 1349 fullname = stream.get_fullname( basename )
1270 if (self._find_branch( fullname )) : 1350 br, is_open = self._find_branch( fullname )
1271 self._warn( "An open branch named '%s' already exists in %s." % (basename, stream,) ) 1351 if (br) :
1352 self._error( "A branch named '%s' already exists in %s: '%s'." % (basename, stream, fullname,) )
1353 if (not is_open) :
1354 self._note( "Branch '%s' is currently closed." % fullname, via_quiet = True )
1272 else : 1355 else :
1273 shelvedpatch_basename = self.curr_workspace.fullname() 1356 shelvedpatch_basename = self.curr_workspace.fullname()
1274 if (rev is None) : 1357 if (rev is None) :
1275 from_branch = stream.source().trunk() 1358 from_branch = stream.source().trunk()
1276 self._shelve( force = dirty ) 1359 self._shelve( force = dirty )
1341 tags = (", latest tags: %s" % ", ".join( tags )) if (tags) else "" 1424 tags = (", latest tags: %s" % ", ".join( tags )) if (tags) else ""
1342 self._print( "%s trunk: %s%s" % (stream, trunk, tags,) ) 1425 self._print( "%s trunk: %s%s" % (stream, trunk, tags,) )
1343 if (open_branches) : 1426 if (open_branches) :
1344 self._print( "Open %s branches:" % stream ) 1427 self._print( "Open %s branches:" % stream )
1345 for e in open_branches : 1428 for e in open_branches :
1346 marker = "#" if (self._is_shelved( e ) ) else "" 1429 marker = "#" if (self._is_shelved( e ) ) else ""
1347 marker += "*" if (e == self.curr_workspace) else "" 1430 marker += "*" if (e == self.curr_workspace) else ""
1431 marker += " %s" % e.rev_node()
1348 self._print( str( e ) + marker, prefix = " " ) 1432 self._print( str( e ) + marker, prefix = " " )
1349 else : 1433 else :
1350 self._print( "No open %s branches" % stream ) 1434 self._print( "No open %s branches" % stream )
1351 if (kwarg.get( "closed" )) : 1435 if (kwarg.get( "closed" )) :
1352 closed_branches = stream.branches( "closed" ) 1436 closed_branches = stream.branches( "closed" )
1353 if (closed_branches) : 1437 if (closed_branches) :
1354 self._print( "Closed %s branches:" % stream ) 1438 self._print( "Closed %s branches:" % stream )
1355 closed_branches.sort( lambda x, y : y.ctx.rev() - x.ctx.rev() ) 1439 closed_branches.sort( lambda x, y : y.ctx.rev() - x.ctx.rev() )
1356 for e in closed_branches : 1440 for e in closed_branches :
1357 self.ui.write( "%-31s" % e.basename( stream ), label = "branches.closed" ) 1441 self.ui.write( "%-31s" % e.basename( stream ), label = "branches.closed" )
1358 self.ui.write( " %5s:%s" % (e.ctx.rev(), short( e.ctx.node() ),), label = "log.changeset" ) 1442 self.ui.write( " %18s" % e.rev_node(), label = "log.changeset" )
1359 self.ui.write( " %s\n" % util.datestr( e.ctx.date(), format = "%Y-%m-%d %a %H:%M %1" ), 1443 self.ui.write( " %s\n" % util.datestr( e.ctx.date(), format = "%Y-%m-%d %a %H:%M %1" ),
1360 label = "log.date" ) 1444 label = "log.date" )
1361 bn = str( e ) 1445 bn = str( e )
1362 p1 = e.ctx 1446 p1 = e.ctx
1363 while (p1.branch() == bn) : 1447 while (p1.branch() == bn) :
1448 # Just be clear that we have covered Case 2b2. 1532 # Just be clear that we have covered Case 2b2.
1449 pass 1533 pass
1450 # At this point, `branch` must be existent. 1534 # At this point, `branch` must be existent.
1451 branches = [branch,] 1535 branches = [branch,]
1452 1536
1453 # OK. We have to explicitly specify the date, rev, and user arguments to prevent mercurial python APIs from crashing. 1537 opts = {"branch" : branches,}
1454 opts = {"date" : None, "rev" : None, "user" : None, "branch" : branches,}
1455 opts.update( kwarg ) 1538 opts.update( kwarg )
1456 self._log( *filenames, **opts ) 1539 self._log( *filenames, **opts )
1457 1540
1458 1541
1459 1542
1462 Abort the workspace branch. 1545 Abort the workspace branch.
1463 1546
1464 @type stream: C{Stream} 1547 @type stream: C{Stream}
1465 @param stream: Stream where the branch which you want to abort is 1548 @param stream: Stream where the branch which you want to abort is
1466 """ 1549 """
1550 arg = arg[1:]
1467 msg = kwarg.pop( "message", "" ) 1551 msg = kwarg.pop( "message", "" )
1468 should_erase = kwarg.pop( "erase", False ) 1552 should_erase = kwarg.pop( "erase", False )
1469 onstream = kwarg.pop( "onstream", False ) 1553 onstream = kwarg.pop( "onstream", False )
1470 curr_workspace = self.curr_workspace 1554 curr_workspace = self.curr_workspace
1471 if (msg) : 1555 if (msg) :
1472 msg = "%s\n" % msg 1556 msg = "%s\n" % msg
1473 if (curr_workspace.is_develop_trunk()) : 1557 if (curr_workspace.is_develop_trunk()) :
1474 raise AbortFlow( "You cannot abort the <develop> trunk." ) 1558 raise AbortFlow( "You cannot abort the <develop> trunk." )
1559 if (arg) :
1560 if (len( arg ) > 1 or curr_workspace.basename() != arg[0]) :
1561 raise AbortFlow( "hgflow intentionally forbids aborting a non-workspace branch." )
1475 if (onstream) : 1562 if (onstream) :
1476 branches = stream.branches() 1563 branches = stream.branches()
1477 if (stream == STREAM["develop"]) : 1564 if (stream == STREAM["develop"]) :
1478 branches.remove( stream.trunk() ) 1565 branches.remove( stream.trunk() )
1479 elif (stream._trunk) : 1566 elif (stream._trunk) :
1480 branches.append( stream.trunk() ) 1567 branches.append( stream.trunk() )
1481 for branch in branches : 1568 for branch in branches :
1482 if (should_erase) : 1569 if (should_erase) :
1483 self._strip( rev = ["branch('%s')" % branch,] ) 1570 self._strip( branch, self.repo.revs( "min(branch('%s'))" % branch )[0] )
1484 else : 1571 else :
1485 self._update( branch ) 1572 self._update( branch )
1486 self._commit( close_branch = True, message = "%s%sAborted %s %s." % 1573 self._commit( close_branch = True, message = "%s%sAborted %s %s." %
1487 (msg, self.msg_prefix, stream, branch.basename( stream, should_quote = True ),) ) 1574 (msg, self.msg_prefix, stream, branch.basename( stream, should_quote = True ),) )
1488 if (self.curr_workspace != self.orig_workspace and self._orig_workspace not in branches) : 1575 if (self.curr_workspace != self.orig_workspace and self._orig_workspace not in branches) :
1495 ) 1582 )
1496 if (curr_workspace not in stream) : 1583 if (curr_workspace not in stream) :
1497 raise AbortFlow( "Your workspace is '%s' branch, which is not in %s." % (curr_workspace, stream,), 1584 raise AbortFlow( "Your workspace is '%s' branch, which is not in %s." % (curr_workspace, stream,),
1498 "To abort a %s branch, you must first update to it." % stream ) 1585 "To abort a %s branch, you must first update to it." % stream )
1499 if (should_erase) : 1586 if (should_erase) :
1500 self._strip( rev = ["branch('%s')" % curr_workspace,] ) 1587 self._strip( curr_workspace, self.repo.revs( "min(branch('%s'))" % curr_workspace )[0] )
1501 else : 1588 else :
1502 self._commit( close_branch = True, message = "%s%sAborted %s '%s'." % 1589 self._commit( close_branch = True, message = "%s%sAborted %s '%s'." %
1503 (msg, self.msg_prefix, stream, curr_workspace.basename( stream ),) ) 1590 (msg, self.msg_prefix, stream, curr_workspace.basename( stream ),) )
1504 self._update( stream.trunk( trace = True ) ) 1591 self._update( stream.trunk( trace = True ) )
1505 self._unshelve() 1592 self._unshelve()
1511 Promote the workspace branch to its destination stream(s). If there are uncommitted changes in the current branch, 1598 Promote the workspace branch to its destination stream(s). If there are uncommitted changes in the current branch,
1512 they will be automatically shelved before rebasing and unshelved afterwards. 1599 they will be automatically shelved before rebasing and unshelved afterwards.
1513 1600
1514 @type stream: C{Stream} 1601 @type stream: C{Stream}
1515 @param stream: Stream where the branch which you want to rebase is 1602 @param stream: Stream where the branch which you want to rebase is
1516 @type rev: C{str} 1603 @type rev : C{str}
1517 @param rev: If provided, promote this revision instead of the head. The specified revision must be in the workspace 1604 @param rev : If provided, promote this revision instead of the head. The specified revision must be in the workspace
1518 branch. 1605 branch.
1519 """ 1606 """
1520 rev = kwarg.pop( "rev", None ) 1607 rev = kwarg.pop( "rev", None )
1521 tag_name = kwarg.pop( "tag", None ) 1608 tag_name = kwarg.pop( "tag", None )
1522 message = kwarg.pop( "message", None ) 1609 message = kwarg.pop( "message", None )
1588 Rebase the workspace branch to its parent branch. If there are uncommitted changes in the current branch, they will be 1675 Rebase the workspace branch to its parent branch. If there are uncommitted changes in the current branch, they will be
1589 automatically shelved before rebasing and unshelved afterwards. 1676 automatically shelved before rebasing and unshelved afterwards.
1590 1677
1591 @type stream: C{Stream} 1678 @type stream: C{Stream}
1592 @param stream: Stream where the branch which you want to rebase is 1679 @param stream: Stream where the branch which you want to rebase is
1593 @type dest: C{str} 1680 @type dest : C{str}
1594 @param dest: If provided, use its value as the destination of rebasing. The value must be a changeset of the parent 1681 @param dest : If provided, use its value as the destination of rebasing. The value must be a changeset of the parent
1595 branch, otherwise it will trigger an error. If not provided, use the tip of the parent branch as the 1682 branch, otherwise it will trigger an error. If not provided, use the tip of the parent branch as the
1596 destination of rebasing. 1683 destination of rebasing.
1597 """ 1684 """
1598 dest = kwarg.get( "dest" ) 1685 dest = kwarg.get( "dest" )
1599 onstream = kwarg.pop( "onstream", False ) 1686 onstream = kwarg.pop( "onstream", False )
1625 else : 1712 else :
1626 self._check_rebase() 1713 self._check_rebase()
1627 self._shelve() 1714 self._shelve()
1628 self._rebase( base = curr_workspace, dest = dest, keepbranches = True ) 1715 self._rebase( base = curr_workspace, dest = dest, keepbranches = True )
1629 self._unshelve() 1716 self._unshelve()
1717
1718
1719
1720 def _action_rename( self, stream, *arg, **kwarg ) :
1721 """
1722 Rename the workspace branch to a new basename. If there are uncommitted changes in the current branch, they will be
1723 automatically shelved before renaming and unshelved afterwards.
1724 Under the hood this action will create a new branch and copy (or graft) all commits in the workspace branch to the new
1725 branch and then erase the workspace branch.
1726
1727 @type stream: C{Stream}
1728 @param stream: Stream where the branch which you want to rename is
1729 @type to : C{str}
1730 @param to : Its value should be the new basename of the workspace branch.
1731 """
1732 new_branch_name = kwarg.pop( "to", None )
1733 curr_workspace = self.curr_workspace
1734 if (not new_branch_name) :
1735 raise AbortFlow( "Please specify the new base name of this branch via the `-t` option." )
1736 if (curr_workspace not in stream) :
1737 raise AbortFlow( "Your workspace is '%s' branch, which is not in %s." % (curr_workspace, stream,),
1738 "To rename a %s branch, you must first update to it." % stream )
1739 if (curr_workspace.is_trunk( stream )) :
1740 raise AbortFlow( "You cannot rename the trunk of %s." % stream )
1741 if (new_branch_name == curr_workspace.basename( stream )) :
1742 self._warn( "No effects because the supposed new basename turns out to be the same as the current one." )
1743 else :
1744 cfn = curr_workspace.fullname()
1745 brn = "branch(%s)" % cfn
1746 rev = "min(%s)" % brn
1747 ctx = self.repo[self.repo.revs( rev )[0]]
1748 nfn = stream.get_fullname( new_branch_name )
1749 msg = ctx.description()
1750 msg = msg.replace( cfn, nfn )
1751 self._shelve()
1752 self._update( self.repo.revs( "%s^" % rev )[0] )
1753 self._create_branch( nfn, msg, user = ctx.user(), date = util.datestr( ctx.date() ) )
1754 self._graft( curr_workspace, **kwarg )
1755 self._unshelve( cfn )
1756 self._strip( curr_workspace, int( ctx ) )
1630 1757
1631 1758
1632 1759
1633 def _update_workspace( self, stream, branch, verbose = True ) : 1760 def _update_workspace( self, stream, branch, verbose = True ) :
1634 """ 1761 """
1648 else : 1775 else :
1649 self._print( "Update workspace to %s %s." % (stream, branch.basename( stream, should_quote = True ),) ) 1776 self._print( "Update workspace to %s %s." % (stream, branch.basename( stream, should_quote = True ),) )
1650 self._shelve() 1777 self._shelve()
1651 self._update( branch ) 1778 self._update( branch )
1652 self._unshelve() 1779 self._unshelve()
1780 self._print( "Parent of working directory: %s" % branch.rev_node() )
1653 1781
1654 1782
1655 1783
1656 def _action_other( self, stream, *arg, **kwarg ) : 1784 def _action_other( self, stream, *arg, **kwarg ) :
1657 """ 1785 """
1666 branch = stream.get_branch( name ) 1794 branch = stream.get_branch( name )
1667 if (branch.is_closed()) : 1795 if (branch.is_closed()) :
1668 self._warn( "%s '%s' has been closed." % (stream, name,) ) 1796 self._warn( "%s '%s' has been closed." % (stream, name,) )
1669 self._update_workspace( stream, branch ) 1797 self._update_workspace( stream, branch )
1670 except error.RepoLookupError : 1798 except error.RepoLookupError :
1671 misspelling = difflib.get_close_matches( name, ["start", "finish", "push", "publish", "pull", 1799 misspelling = difflib.get_close_matches( name, Flow.ACTION_NAME, 3, 0.7 )
1672 "list", "log", "abort", "promote", "rebase",], 3, 0.7 )
1673 note = ("Did you mean: %s?" % " or ".join( misspelling )) if (misspelling ) else None 1800 note = ("Did you mean: %s?" % " or ".join( misspelling )) if (misspelling ) else None
1674 note = ("Did you mean: finish or abort?") if ("close" == name) else note 1801 note = ("Did you mean: finish or abort?") if ("close" == name) else note
1675 note = ("If you meant to create a new branch called '%s' in %s" % (name, stream,), 1802 if (stream != STREAM["master"]) :
1676 "try command:", " hg flow %s start %s" % (stream.name(), name,),) if (not note) else note 1803 note = ("If you meant to create a new branch called '%s' in %s" % (name, stream,),
1804 "try command:", " hg flow %s start %s" % (stream.name(), name,),) if (not note) else note
1677 raise AbortFlow( "Invalid action or unknown branch in %s: '%s'" % (stream, name,), note = note ) 1805 raise AbortFlow( "Invalid action or unknown branch in %s: '%s'" % (stream, name,), note = note )
1678 1806
1679 1807
1680 1808
1681 def _commit_change( self, opt, commit_hint, is_erasing = False ) : 1809 def _commit_change( self, opt, commit_hint, is_erasing = False ) :
1738 onstream = kwarg.pop( "onstream", False ) 1866 onstream = kwarg.pop( "onstream", False )
1739 should_erase = kwarg.pop( "erase", False ) 1867 should_erase = kwarg.pop( "erase", False )
1740 curr_workspace = self.curr_workspace 1868 curr_workspace = self.curr_workspace
1741 curr_stream = curr_workspace.stream() 1869 curr_stream = curr_workspace.stream()
1742 name = curr_workspace.basename( stream, should_quote = True ) 1870 name = curr_workspace.basename( stream, should_quote = True )
1871 tag_name_orig = tag_name
1743 tag_name = tag_name if (tag_name) else (self.version_prefix + name[1:-1]) 1872 tag_name = tag_name if (tag_name) else (self.version_prefix + name[1:-1])
1744 develop_stream = STREAM["develop"] 1873 develop_stream = STREAM["develop"]
1745 1874
1746 if (should_erase) : 1875 if (should_erase) :
1747 if (onstream ) : raise AbortFlow( "'--erase' cannot be used together with '--onstream'." ) 1876 if (onstream ) : raise AbortFlow( "'--erase' cannot be used together with '--onstream'." )
1795 # This is particularly needed for dry run. 1924 # This is particularly needed for dry run.
1796 if (not is_commit_done and self._has_uncommitted_changes()) : 1925 if (not is_commit_done and self._has_uncommitted_changes()) :
1797 raise AbortFlow( "Cannot finish '%s' branch because it has uncommitted changes." % curr_workspace ) 1926 raise AbortFlow( "Cannot finish '%s' branch because it has uncommitted changes." % curr_workspace )
1798 1927
1799 # For destin streams without trunks, we need to create a branch in each of these destin streams. 1928 # For destin streams without trunks, we need to create a branch in each of these destin streams.
1800 # Each newly created branch will be named after the pattern: <stream-prefix>/<current-branch-basename> and will be from 1929 # Each newly created branch will be from the current branch and named after the pattern:
1801 # the current branch. The current branch will be closed. Note that there is no need to merge the current branch because 1930 # <stream-prefix>/<current-branch-basename>. Afterwards, the current branch will be closed.
1802 # a new branch has been created from it. 1931 # Note that there is no need to merge the current branch because the new branch is created from it.
1803 for s in destin_without_trunk : 1932 for s in destin_without_trunk :
1804 trunk = s.trunk() 1933 trunk = s.trunk()
1805 so_name = "" if ("trunk" == curr_workspace.basename( stream )) else ("/" + curr_workspace.basename( stream )) 1934 so_name = "" if ("trunk" == curr_workspace.basename( stream )) else ("/" + curr_workspace.basename( stream ))
1806 so = Stream( self.ui, self.repo, stream.name() + so_name, trunk = curr_workspace.fullname() ) 1935 so = Stream ( self.ui, self.repo, stream.name() + so_name, trunk = curr_workspace.fullname() )
1807 so = "%s:%s" % (so.name(), s.name(),) 1936 so = Stream.gen( self.ui, self.repo, "%s:%s" % (so.name(), s.name(),), check = True )
1808 basename = curr_workspace.basename() 1937 basename = curr_workspace.basename()
1809 self.action( so, "start", basename ) 1938 self.action( so, "start", basename )
1810 final_branch = s.get_fullname( basename ) 1939 final_branch = s.get_fullname( basename )
1811 1940
1812 if (destin_with_trunk or destin_without_trunk) : 1941 if (destin_with_trunk or destin_without_trunk) :
1819 self._update( curr_workspace ) 1948 self._update( curr_workspace )
1820 self._commit( close_branch = True, message = "%sClosed %s %s." % (self.msg_prefix, stream, name,), **kwarg ) 1949 self._commit( close_branch = True, message = "%sClosed %s %s." % (self.msg_prefix, stream, name,), **kwarg )
1821 final_branch = STREAM["master"].trunk() 1950 final_branch = STREAM["master"].trunk()
1822 else : 1951 else :
1823 self._print( "All open branches in %s are finished and merged to its trunk." % stream ) 1952 self._print( "All open branches in %s are finished and merged to its trunk." % stream )
1953
1954 if (tag_name_orig and (STREAM["master"] not in destin_with_trunk)) :
1955 self._warn( "You specified a tag name, but it has effect only when the workspace branch is merged to <master>." )
1956
1824 for s in destin_with_trunk : 1957 for s in destin_with_trunk :
1825 trunk = s.trunk() 1958 trunk = s.trunk()
1826 self._update( trunk ) 1959 self._update( trunk )
1827 self._merge ( curr_workspace ) 1960 self._merge ( curr_workspace )
1828 self._commit( message = "%sMerged %s %s to %s ('%s')." % (self.msg_prefix, stream, name, s, trunk,), **kwarg ) 1961 self._commit( message = "%sMerged %s %s to %s ('%s')." % (self.msg_prefix, stream, name, s, trunk,), **kwarg )
1829 if (s == STREAM["master"]) : 1962 if (s == STREAM["master"]) :
1830 self._tag( tag_name ) 1963 self._tag( tag_name, force = True )
1831 elif (s in develop_stream and s is not develop_stream) : 1964 elif (s in develop_stream and s is not develop_stream) :
1832 tr_stream = trunk.stream() 1965 tr_stream = trunk.stream()
1833 for ss in tr_stream.destin() : 1966 for ss in tr_stream.destin() :
1834 if (ss == develop_stream) : 1967 if (ss == develop_stream) :
1835 dvtrunk = develop_stream.trunk() 1968 dvtrunk = develop_stream.trunk()
1844 rev = "p1(.)" 1977 rev = "p1(.)"
1845 rev = mercurial.scmutil.revsingle( self.repo, rev ).rev() 1978 rev = mercurial.scmutil.revsingle( self.repo, rev ).rev()
1846 self._update( "tip" ) 1979 self._update( "tip" )
1847 self._update( rev ) 1980 self._update( rev )
1848 self._revert( rev = "-1", all = True ) 1981 self._revert( rev = "-1", all = True )
1849 self._strip ( rev = ["branch('%s')" % curr_workspace,] ) 1982 self._strip ( curr_workspace, self.repo.revs( "min(branch('%s'))" % curr_workspace )[0] )
1850 self._commit( message = message, **kwarg ) 1983 self._commit( message = message, **kwarg )
1851 self._unshelve() 1984 self._unshelve()
1852 1985
1853 1986
1854 1987
1878 "list" : self._action_list, 2011 "list" : self._action_list,
1879 "log" : self._action_log, 2012 "log" : self._action_log,
1880 "abort" : self._action_abort, 2013 "abort" : self._action_abort,
1881 "promote" : self._action_promote, 2014 "promote" : self._action_promote,
1882 "rebase" : self._action_rebase, 2015 "rebase" : self._action_rebase,
2016 "rename" : self._action_rename,
1883 "other" : self._action_other, 2017 "other" : self._action_other,
1884 } 2018 }
1885 2019
1886 custom_action_func = kwarg.pop( "action_func", {} ) 2020 custom_action_func = kwarg.pop( "action_func", {} )
1887 action_func.update( custom_action_func ) 2021 action_func.update( custom_action_func )
1891 2025
1892 2026
1893 def action( self, stream, *arg, **kwarg ) : 2027 def action( self, stream, *arg, **kwarg ) :
1894 """ 2028 """
1895 Execute action on the stream. 2029 Execute action on the stream.
2030
2031 @type stream: C{Stream}
2032 @param stream: Stream where we will execute the action
1896 """ 2033 """
1897 if (len( arg ) > 0) : 2034 if (len( arg ) > 0) :
1898 action = arg[0] 2035 action = arg[0]
1899 if (stream == STREAM["master"]) : 2036 if (stream == STREAM["master"]) :
1900 if (action in ["start", "finish", "abort", "rebase",]) : 2037 if (action in ["start", "finish", "abort", "rebase",]) :
1901 raise AbortFlow( "Invalid action for <master>" ) 2038 raise AbortFlow( "Invalid action for <master>" )
1902 else : 2039 else :
1903 self._update_workspace( stream, stream.trunk(), verbose = False ) 2040 trunk = stream.trunk()
2041 self._update_workspace( stream, trunk, verbose = False )
1904 self._execute_action( stream, *arg, **kwarg ) 2042 self._execute_action( stream, *arg, **kwarg )
1905 2043
1906 2044
1907 2045
1908 def print_version( self, *arg, **kwarg ) : 2046 def print_version( self, *arg, **kwarg ) :
1931 """ 2069 """
1932 self._print( "Currently open branches:" ) 2070 self._print( "Currently open branches:" )
1933 curr_workspace = self.curr_workspace 2071 curr_workspace = self.curr_workspace
1934 stream_names = ["master", "develop", "feature", "release", "hotfix", "support",] 2072 stream_names = ["master", "develop", "feature", "release", "hotfix", "support",]
1935 all_branches = self._branches() 2073 all_branches = self._branches()
2074 name_branches = {}
2075 for branch in all_branches :
2076 name_branches.setdefault( branch.fullname(), [] ).append( branch )
2077 name_branches = sorted( name_branches.items() )
1936 for sn in stream_names : 2078 for sn in stream_names :
1937 stream = STREAM[sn] 2079 stream = STREAM[sn]
1938 trunk = stream.trunk() 2080 trunk = stream.trunk()
1939 open_branches_in_stream = [] 2081 open_branches_in_stream = []
1940 remaining_branches = [] 2082 for name, heads in name_branches :
1941 for e in all_branches : 2083 e = heads[0]
1942 if (e in stream) : 2084 if (e in stream) :
1943 open_branches_in_stream.append( e ) 2085 open_branches_in_stream.append( e )
1944 else :
1945 remaining_branches.append( e )
1946 all_branches = remaining_branches
1947 if (trunk is None and not open_branches_in_stream) : 2086 if (trunk is None and not open_branches_in_stream) :
1948 continue 2087 continue
1949 self._print( "%-9s: " % stream, newline = False ) 2088 self._print( "%-9s: " % stream, newline = False )
1950 if (trunk) : 2089 if (trunk) :
1951 marker = "#" if (self._is_shelved( trunk )) else "" 2090 marker = "#" if (self._is_shelved( trunk )) else ""
1958 for e in open_branches_in_stream : 2097 for e in open_branches_in_stream :
1959 marker = "#" if (self._is_shelved( e )) else "" 2098 marker = "#" if (self._is_shelved( e )) else ""
1960 marker += "*" if (e == curr_workspace ) else "" 2099 marker += "*" if (e == curr_workspace ) else ""
1961 self.ui.write( "%s%s " % (e, marker,) ) 2100 self.ui.write( "%s%s " % (e, marker,) )
1962 self.ui.write( "\n" ) 2101 self.ui.write( "\n" )
1963 2102 if (sum( [len( heads ) - 1 for name, heads in name_branches] )) :
2103 self._print( "\n", newline = False )
2104 self._print( "Multihead branches:" )
2105 for name, heads in name_branches :
2106 if (len( heads ) > 1) :
2107 self._print( " %s" % name )
2108 for head in heads :
2109 self._print( " %s" % head.rev_node() )
2110
1964 2111
1965 2112
1966 def init( self, *arg, **kwarg ) : 2113 def init( self, *arg, **kwarg ) :
1967 """ 2114 """
1968 Initialize flow. 2115 Initialize flow.
2035 if (branch == workspace) : 2182 if (branch == workspace) :
2036 self._warn( " " + branch.fullname() + " (active)" ) 2183 self._warn( " " + branch.fullname() + " (active)" )
2037 else : 2184 else :
2038 self._warn( " %s" % branch.fullname() ) 2185 self._warn( " %s" % branch.fullname() )
2039 print 2186 print
2040 2187
2041 # 'status' method returns a 7-member tuple: 2188 # 'status' method returns a 7-member tuple:
2042 # 0 modified, 1 added, 2 removed, 3 deleted, 4 unknown(?), 5 ignored, and 6 clean 2189 # 0 modified, 1 added, 2 removed, 3 deleted, 4 unknown(?), 5 ignored, and 6 clean
2043 orig_repo_status = self.repo.status()[:4] 2190 orig_repo_status = self.repo.status()[:4]
2044 for e in orig_repo_status : 2191 for e in orig_repo_status :
2045 try : 2192 try :
2098 "release = %s" % release_stream, 2245 "release = %s" % release_stream,
2099 "hotfix = %s" % hotfix_stream, 2246 "hotfix = %s" % hotfix_stream,
2100 "support = %s" % support_stream,] 2247 "support = %s" % support_stream,]
2101 def write_config() : 2248 def write_config() :
2102 # Writes the configuration in the current branch. 2249 # Writes the configuration in the current branch.
2103 with open( config_fname, "w" ) as fh : 2250 if (not commands.dryrun()) :
2104 print >> fh, "\n".join( cfg_contents ) 2251 with open( config_fname, "w" ) as fh :
2252 print >> fh, "\n".join( cfg_contents )
2105 repo_status = self.repo.status( unknown = True ) 2253 repo_status = self.repo.status( unknown = True )
2106 if (CONFIG_BASENAME in repo_status[0]) : 2254 if (CONFIG_BASENAME in repo_status[0]) :
2107 self._commit( config_fname, message = "flow initialization: Modified configuration file." ) 2255 self._commit( config_fname, message = "flow initialization: Modified configuration file." )
2108 elif (CONFIG_BASENAME in repo_status[4]) : 2256 elif (CONFIG_BASENAME in repo_status[4]) :
2109 self._add ( config_fname ) 2257 self._add ( config_fname )
2110 self._commit( config_fname, message = "flow initialization: Added configuration file." ) 2258 self._commit( config_fname, message = "flow initialization: Added configuration file." )
2111 2259
2112 write_config() 2260 write_config()
2113 2261
2262 master_trunk, is_open = self._find_branch( master_stream )
2263 if (master_trunk and not is_open) :
2264 self._warn( "Branch \"%s\" is currently closed." % master_stream )
2265 self._warn( "Will reopen and use it as <master> trunk." )
2266 branches.append( master_trunk )
2267
2268 develop_trunk, is_open = self._find_branch( develop_stream )
2269 if (develop_trunk and not is_open) :
2270 self._warn( "Branch \"%s\" is currently closed." % develop_stream )
2271 self._warn( "Will reopen and use it as <develop> trunk." )
2272 branches.append( develop_trunk )
2273
2114 # Writes the configuration in all the other branches. 2274 # Writes the configuration in all the other branches.
2115 self.autoshelve = True 2275 self.autoshelve = True
2116 self._shelve() 2276 self._shelve()
2117 2277
2118 if (len( branches ) > 1) : 2278 if (len( branches ) > 1) :
2121 self._update( branch ) 2281 self._update( branch )
2122 write_config() 2282 write_config()
2123 self._update( workspace ) 2283 self._update( workspace )
2124 2284
2125 # Creates 'master' and 'develop' streams if they don't yet exist. 2285 # Creates 'master' and 'develop' streams if they don't yet exist.
2126 if (not self._find_branch( master_stream )) : 2286 if (master_trunk is None) :
2127 self._create_branch( master_stream, "flow initialization: Created <master> trunk: %s." % master_stream ) 2287 self._create_branch( master_stream, "flow initialization: Created <master> trunk: %s." % master_stream )
2128 if (not self._find_branch( develop_stream )) : 2288 if (develop_trunk is None) :
2129 self._create_branch( develop_stream, "flow initialization: Created <develop> trunk: %s." % develop_stream ) 2289 self._create_branch( develop_stream, "flow initialization: Created <develop> trunk: %s." % develop_stream )
2290
2130 self._update( workspace ) 2291 self._update( workspace )
2131 self._unshelve() 2292 self._unshelve()
2132 2293
2133 2294
2134 2295
2135 def upgrade( self, *arg, **kwarg ) : 2296 def upgrade( self, *arg, **kwarg ) :
2136 """ 2297 """
2137 Upgrade older version to the latest version. 2298 Upgrade older version to the latest version.
2138 """ 2299 """
2165 - pull Pull from the remote repository and update workspace branch. 2326 - pull Pull from the remote repository and update workspace branch.
2166 - list List all open branches in the stream. 2327 - list List all open branches in the stream.
2167 - log Show revision history of branch. 2328 - log Show revision history of branch.
2168 - promote Merge workspace to other branches. (not closing any branches.) 2329 - promote Merge workspace to other branches. (not closing any branches.)
2169 - rebase Rebase workspace branch to its parent branch. 2330 - rebase Rebase workspace branch to its parent branch.
2331 - rename Rename workspace branch to a new basename.
2170 - abort Abort branch. Close branch without merging. 2332 - abort Abort branch. Close branch without merging.
2171 2333
2172 If no action is specified by user, the action will default to `list`. If a 2334 If no action is specified by user, the action will default to `list`. If a
2173 branch name (instead of action) is given after the stream name, Flow will 2335 branch name (instead of action) is given after the stream name, Flow will
2174 switch the current workspace to the branch. 2336 switch the current workspace to the branch.
2218 2380
2219 try : 2381 try :
2220 # Constructs a `Stream' objects. 2382 # Constructs a `Stream' objects.
2221 # This will also check the validity of the part of user's input that is supposed to specify a stream. 2383 # This will also check the validity of the part of user's input that is supposed to specify a stream.
2222 if (isinstance( stream, str )) : 2384 if (isinstance( stream, str )) :
2223 source = None 2385 stream = Stream.gen( ui, repo, stream, check = True )
2224 tokens = stream.split( ':' ) 2386
2225 n = len( tokens )
2226 if (n == 2) :
2227 source, stream = tokens[0], tokens[1]
2228 if (n > 2 or not stream) :
2229 raise AbortFlow( "Invalid stream syntax: '%s'" % stream )
2230 try :
2231 stream = Stream.gen( ui, repo, stream, check = True )
2232 except AbnormalStream, e :
2233 stream = e.stream()
2234 if (source) :
2235 try :
2236 source = Stream.gen( ui, repo, source, check = True )
2237 except AbnormalStream, e :
2238 source = e.stream()
2239 stream = copy.copy( stream )
2240 stream._source = source
2241 for i, e in enumerate( stream.destin() ) :
2242 if (source in e ) :
2243 stream._destin[i] = source
2244 break
2245 else :
2246 stream._destin = [source]
2247
2248 # Checks the options for all commands and actions. 2387 # Checks the options for all commands and actions.
2249 kwarg = _getopt( ui, cmd, kwarg ) 2388 kwarg = _getopt( ui, cmd, kwarg )
2250 stamp = kwarg.pop( "stamp", None ) 2389 stamp = kwarg.pop( "stamp", None )
2251 if (stamp) : 2390 if (stamp) :
2252 def stamp_commit_message( opts ) : 2391 def stamp_commit_message( opts ) :
2262 except AbortFlow, e : 2401 except AbortFlow, e :
2263 errmsg = e.error_message() 2402 errmsg = e.error_message()
2264 _error( ui, *errmsg ) 2403 _error( ui, *errmsg )
2265 if (getattr( e, "note", None )) : 2404 if (getattr( e, "note", None )) :
2266 _note( ui, *((e.note,) if (isinstance( e.note, str )) else e.note), via_quiet = True ) 2405 _note( ui, *((e.note,) if (isinstance( e.note, str )) else e.note), via_quiet = True )
2406 elif (errmsg[0].startswith( "Stream not found" )) :
2407 misspelling = difflib.get_close_matches( stream, ["init", "upgrade", "unshelve", "help", "version",], 3, 0.7 )
2408 note = ("Did you mean the command: %s?" % " or ".join( misspelling )) if (misspelling ) else None
2409 note = ("Did you mean the command: init?") if ("install" == stream) else note
2410 note = ("Did you mean the command: upgrade?") if ("update" == stream) else note
2411 if (note) :
2412 _note( ui, note, via_quiet = True )
2413
2267 if (ui.tracebackflag) : 2414 if (ui.tracebackflag) :
2268 if (hasattr( e, "traceback" )) : 2415 if (hasattr( e, "traceback" )) :
2269 ei = e.traceback 2416 ei = e.traceback
2270 sys.excepthook( ei[0], ei[1], ei[2] ) 2417 sys.excepthook( ei[0], ei[1], ei[2] )
2271 print 2418 print
2308 "@deprecated" : """ 2455 "@deprecated" : """
2309 The following item has been deprecated in this release and will be removed in 2456 The following item has been deprecated in this release and will be removed in
2310 the future: 2457 the future:
2311 * [hgflow] The '[hgflow]' section name in hg's configuration file has been 2458 * [hgflow] The '[hgflow]' section name in hg's configuration file has been
2312 renamed to '[flow]'. 2459 renamed to '[flow]'.
2460 * Syntax: hg flow <stream> finish {{{<tag-name>}}}
2461 Replacement syntax: hg flow <stream> finish {{{-t <tag-name>}}}
2462 Any positional arguments for `finish` will be considered as
2463 error.
2313 """, 2464 """,
2314 2465
2315 "@examples" : """ 2466 "@examples" : """
2316 {{{> hg flow}}} 2467 {{{> hg flow}}}
2317 flow: Currently open branches: 2468 flow: Currently open branches:
2610 option: 2761 option:
2611 -p --stamp TEXT Append TEXT to all commit messages. 2762 -p --stamp TEXT Append TEXT to all commit messages.
2612 2763
2613 The workspace branch must be in <stream>. If the destination revision is not 2764 The workspace branch must be in <stream>. If the destination revision is not
2614 specified, it will default to the source branch of the workspace branch. 2765 specified, it will default to the source branch of the workspace branch.
2766 """,
2767
2768 "@version" : """
2769 Show version of the flow extension.
2770
2771 syntax:
2772 {{{hg flow version}}}
2773 """,
2774
2775 "@rename" : """
2776 Rename the workspace branch to a new basename. Under the hood, this action
2777 will create a new branch with the new basename and copy/graft the commits in
2778 the workspace branch to the new branch, and then erase the workspace branch.
2779 All the user, date, and commit-message information will be copied to the
2780 new branch.
2781
2782 N.B.: The workspace branch should be a simple linear branch. This means:
2783 (1) It has not merged to other branches;
2784 (2) It has no subbranches;
2785 (3) No other branches has merged to this branch.
2786
2787 syntax:
2788 {{{hg flow <stream> rename [-t <basename>]}}}
2789
2790 option:
2791 -t --to NAME Rename the basename of the workspace branch to NAME.
2792
2793 The workspace branch must be in <stream>.
2615 """, 2794 """,
2616 2795
2617 "@version" : """ 2796 "@version" : """
2618 Show version of the flow extension. 2797 Show version of the flow extension.
2619 2798
2801 _print( self.ui, """Supported topics are the following: 2980 _print( self.ui, """Supported topics are the following:
2802 @all - Show detailed help for all supported topics. 2981 @all - Show detailed help for all supported topics.
2803 @<stream> - Show help about a particular stream, e.g., @feature, @master. 2982 @<stream> - Show help about a particular stream, e.g., @feature, @master.
2804 @<action> - Show help about an action, e.g., @finish, @log. 2983 @<action> - Show help about an action, e.g., @finish, @log.
2805 @<command> - Show help about a command, e.g., @help, @unshelve. 2984 @<command> - Show help about a command, e.g., @help, @unshelve.
2806 @<option> - Show help about an option, e.g., @-F, @--history. 2985 @terms - Show explanations of terminologies used in hgflow.
2807 @examples - Show a few command examples. 2986 @examples - Show a few command examples.
2808 @deprecated - Show a list of deprecated features. 2987 @deprecated - Show a list of deprecated features.
2809 """ ) 2988 """ )
2810 2989
2811 2990
2818 "list" : ("closed",), 2997 "list" : ("closed",),
2819 "log" : ("file", "date", "user", "keyword", "patch", "git", "limit", "graph", "closed", "onstream",), 2998 "log" : ("file", "date", "user", "keyword", "patch", "git", "limit", "graph", "closed", "onstream",),
2820 "abort" : ("erase", "message", "stamp", "onstream",), 2999 "abort" : ("erase", "message", "stamp", "onstream",),
2821 "promote" : ("rev", "message", "stamp", "tag", "date", "user", "onstream",), 3000 "promote" : ("rev", "message", "stamp", "tag", "date", "user", "onstream",),
2822 "rebase" : ("dest", "onstream", "stamp",), 3001 "rebase" : ("dest", "onstream", "stamp",),
3002 "rename" : ("to",),
2823 } 3003 }
2824 3004
2825 OPT_CONFLICT = { 3005 OPT_CONFLICT = {
2826 "dest" : ("-d", '', ), # (short-form-of-option, default-value,) 3006 "dest" : ("-d", '', ), # (short-form-of-option, default-value,)
2827 "date" : ("-d", '', ), 3007 "date" : ("-d", '', ),
2828 "default" : ("-d", False,), 3008 "default" : ("-d", False,),
2829 "closed" : ("-c", False,), 3009 "closed" : ("-c", False,),
2830 "commit" : ("-c", False,), 3010 "commit" : ("-c", False,),
2831 "stamp" : ("-p", '' ), 3011 "stamp" : ("-p", '' ),
2832 "patch" : ("-p", False ), 3012 "patch" : ("-p", False ),
3013 "tag" : ("-t", '' ),
3014 "to" : ("-t", '' ),
2833 } 3015 }
2834 3016
2835 def _getopt( ui, key, opt ) : 3017 def _getopt( ui, key, opt ) :
2836 """ 3018 """
2837 Return user-specified options. 3019 Return user-specified options.
2882 bad_opt = [e.replace( "_", "-" ) for e in bad_opt] 3064 bad_opt = [e.replace( "_", "-" ) for e in bad_opt]
2883 if (key is None) : 3065 if (key is None) :
2884 raise AbortFlow( "Unrecognized option%s for `hg flow`: %s." % 3066 raise AbortFlow( "Unrecognized option%s for `hg flow`: %s." %
2885 ("" if (len( bad_opt ) == 1) else "s", "--" + (", --".join( bad_opt )),), 3067 ("" if (len( bad_opt ) == 1) else "s", "--" + (", --".join( bad_opt )),),
2886 note = "`hg flow` should take no options." ) 3068 note = "`hg flow` should take no options." )
2887 else : 3069 elif (key in Flow.ACTION_NAME) :
2888 raise AbortFlow( "Unrecognized option%s for `%s`: %s." % 3070 raise AbortFlow( "Unrecognized option%s for `%s`: %s." %
2889 ("" if (len( bad_opt ) == 1) else "s", key, "--" + (", --".join( bad_opt )),), 3071 ("" if (len( bad_opt ) == 1) else "s", key, "--" + (", --".join( bad_opt )),),
2890 note = "Execute `hg flow help @%s` to see available options for `%s`." % (key, key,) ) 3072 note = "Execute `hg flow help @%s` to see available options for `%s`." % (key, key,) )
3073 else :
3074 raise AbortFlow( "Unrecognized option%s: %s." %
3075 ("" if (len( bad_opt ) == 1) else "s", "--" + (", --".join( bad_opt )),) )
2891 3076
2892 return ret 3077 return ret
2893 3078
2894 3079
2895 3080
2919 ("r", "rev", '', _("Revision to start a new branch from. [start]"), _('REV'), ), 3104 ("r", "rev", '', _("Revision to start a new branch from. [start]"), _('REV'), ),
2920 ("r", "rev", '', _("Revision to promote to other branches. [promote]"), _('REV'), ), 3105 ("r", "rev", '', _("Revision to promote to other branches. [promote]"), _('REV'), ),
2921 ("s", "onstream", False, _("Act on stream. [finish, rebase, log, abort]"), ), 3106 ("s", "onstream", False, _("Act on stream. [finish, rebase, log, abort]"), ),
2922 ("t", "tag", '', _("Tag the merging changeset with NAME. [promote]"), _('NAME'),), 3107 ("t", "tag", '', _("Tag the merging changeset with NAME. [promote]"), _('NAME'),),
2923 ("t", "tag", '', _("Tag the <master> trunk with NAME after merging. [finish]"), _('NAME'),), 3108 ("t", "tag", '', _("Tag the <master> trunk with NAME after merging. [finish]"), _('NAME'),),
3109 ("t", "to", '', _("Rename the branch to NAME. [rename]"), _('NAME'),),
2924 ("u", "user", '', _("Use specified user as committer. [init, upgrade, start, finish," 3110 ("u", "user", '', _("Use specified user as committer. [init, upgrade, start, finish,"
2925 " promote]"), _('USER'),), 3111 " promote]"), _('USER'),),
2926 ("u", "user", '', _("Show revisions committed by specified user. [log]"), _('USER'),), 3112 ("u", "user", '', _("Show revisions committed by specified user. [log]"), _('USER'),),
2927 ], 3113 ],
2928 "hg flow {<stream> [<action> [<arg>]] | <command>} [<option>...]", 3114 "hg flow {<stream> [<action> [<arg>]] | <command>} [<option>...]",

mercurial