.hgext/hgflow.py

changeset 8
ba72a71f23e6
child 253
a1accbd675a0
equal deleted inserted replaced
7:7e6e6ff35c4d 8:ba72a71f23e6
1 """commands to support generalized Driessen's branching model
2 """
3 # License GPL 2.0
4 #
5 # hgflow.py - Mercurial extension to support generalized Driessen's branching model
6 # Copyright (C) 2011-2012, Yujie Wu
7 #
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.
10 #
11 # This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
12 # of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
13
14
15
16 import os
17 import sys
18 import copy
19 import difflib
20 import mercurial
21
22 from mercurial import util, extensions, error, config
23 from mercurial.node import short
24 from mercurial.i18n import _
25
26
27
28 ###############################################################################################################################
29 # Let's use 128 char width.
30 # It is silly to stick to the 80-char rule.
31 #
32 #
33 # Terminologies <== Read before you got confused
34 # - branch type
35 # We distinguish the following types of branches: master, develop, feature, release, hotfix, support.
36 # We should assume any branch is of one of these types.
37 #
38 # - stream
39 # The entire set of branches of the same type. Stream is not branch, it is a set. Stream is not a type, it is a set.
40 # Stream is a set of branches.
41 #
42 # - substream
43 # A subset of branches in a stream.
44 #
45 # - trunk
46 # Trunk is a special branch. A stream can optionally have a trunk, but only one trunk at most. For example, master and
47 # develop streams each has a trunk, whereas feature, release, hotfix, and support streams don't.
48 # If a stream has a trunk, all branches in the stream normally should diverge from the trunk and later merge to the trunk
49 # when the branches are closed.
50 # Trunk is a relative concept. A trunk of a stream may be a regular branch of another stream. (The former stream will be
51 # a substream of the latter.)
52 #
53 # - source
54 # Source is an attribute of stream. The source of a stream refers to the parent stream where branches in the current stream
55 # are created from. Most commonly, source of a stream is the stream itself. But this is not always the case, for example,
56 # the sources of release and feature streams are the develop stream.
57 #
58 # - destin
59 # Destin is another attribute of stream. The destin of a stream refers to the stream(s) where branches in the current
60 # stream will merge to. Most commonly, destin of a stream is the stream itself. But this is not always the case, for
61 # example, the destin of release is the develop and the master streams.
62 #
63 # - branch name
64 # Use this term carefully since it is potentially ambiguious.
65 # Try using this term to refer to fullname (see below).
66 #
67 # - fullname
68 # Branch name as recognized by the SCM, e.g., feature/enhance_log.
69 # Prefer this term to 'branch name'.
70 #
71 # - basename
72 # Branch name recognized by flow, but not necessarily by SCM, e.g., enhanced_log (with prefix 'feature/' dropped).
73 #
74 # - name
75 # Use this term carefully since it is potentially ambiguious.
76 # This term should be a synonym of basename (see above). Try using it only as place holders, such as
77 # <hotfix-prefix>/<name>.
78 #
79 # - flow action
80 # Refer to action on a specified stream, e.g., hg flow feature start, where 'start' is an action.
81 #
82 # - flow command
83 # Refer to other actions than those on a stream, e.g., hg flow unshelve, where 'unshelve' is a command.
84 #
85 # - hg command
86 # Refer to command not from flow extension.
87 #
88 # - workflow
89 # Refer to the process of executing a sequence of hg commands.
90 #
91 # - history
92 # Refer to a sequence of hg commands that has been executed.
93 #
94 # Notations
95 # - <stream>
96 # Examples: <feature>, <hotfix>. These denote the corresponding streams. When you refer to a stream, e.g., feature stream,
97 # use '<feature>' (or more verbosely 'feature stream'), instead of '<feature> stream', because '<feature>' already means
98 # stream.
99 #
100 # - <stream> branch
101 # Example: a <feature> branch. This phrase refers a branch in <feature>. Do not use 'a feature branch' to mean a branch in
102 # <feature> because the word 'feature' there should take its usual meaning as in English, which doesn't necessarily mean
103 # the feature stream.
104 #
105 # - `text`
106 # Example, `hg flow feature start <name>`. The text wrapped by the ` (apostrophe) symbols should be a piece of code or
107 # shell command, which could contain placeholders to be replaced by actual values.
108 #
109 # - 'text' or "text"
110 # Refer to an exact string.
111 ###############################################################################################################################
112
113
114
115 VERSION = "0.9.7"
116 CONFIG_BASENAME = ".hgflow"
117 OLD_CONFIG_BASENAME = ".flow"
118 CONFIG_SECTION_BRANCHNAME = "branchname"
119 STRIP_CHARS = '\'"'
120
121
122 colortable = {"flow.error" : "red bold",
123 "flow.warn" : "magenta bold",
124 "flow.note" : "cyan",
125 "flow.help.topic" : "yellow",
126 "flow.help.code" : "green bold",
127 }
128
129
130
131 def _print( ui, *arg, **kwarg ) :
132 """
133 Customized print function
134
135 This function prints messages with the prefix: C{flow: }. Multiple messages can be printed in one call.
136 See I{Example I} below.
137
138 @type ui: C{mercurial.ui}
139 @param ui: Mercurial user interface object
140 @type warning: C{bool}
141 @param warning: If set to true, messages will be written to C{stderr} using the C{ui.warn} function.
142 @type note: C{bool}
143 @param note: If set to true, messages will be written to C{stdout} using the C{ui.note} function. The messages will be
144 visible only when user turns on C{--verbose}.
145 By default, both L{warning} and L{note} are set to false, and messages will be written to C{stdout}.
146 @type prefix: C{None} or C{str}
147 @param prefix: Add a customized prefix before every message. See I{Example II}.
148 @type newline: C{bool}
149 @param newline: If set to false, each message will be written without newline suffix. Default value is true.
150
151 I{Example I}:
152
153 >>> _print( ui, "message1", "message2" )
154 flow: message1
155 flow: message2
156
157 I{Example II}:
158
159 >>> _print( ui, "message1", "message2", prefix = "warning: " )
160 flow: warning: message1
161 flow: warning: message2
162
163 I{Example III}:
164
165 >>> _print( ui, "message1", "message2", inline = False )
166 flow: message1message2
167 """
168 printer = ui.warn if (kwarg.get( "warning" )) else (ui.note if (kwarg.get( "note" )) else ui.write)
169 indent = kwarg.get( "indent", "" )
170 prefix = kwarg.get( "prefix", "" )
171 newline = kwarg.get( "newline" )
172 newline = "\n" if (newline or newline is None) else ""
173 for e in arg :
174 printer( ui.config( "flow", "prefix", "flow: " ).strip( STRIP_CHARS ) + prefix + indent )
175 printer( e + newline, label = kwarg.get( "label", "" ) )
176
177
178
179 def _warn( ui, *arg, **kwarg ) :
180 """
181 Print messages to C{stderr}. Each message will be prefixed with C{flow: warning: }.
182
183 This function is a thin wrapper of L{_print}. See document of the later for usage detail.
184
185 Customized prefix will be appended after C{flow: warning: }.
186
187 I{Example}:
188
189 >>> _warn( ui, "message1", "message2", prefix = "prefix_" )
190 flow: warning: prefix_message1
191 flow: warning: prefix_message2
192 """
193 kwarg["warn" ] = True
194 kwarg["label" ] = kwarg.get( "label", "flow.warn" )
195 kwarg["prefix"] = "warning: " + kwarg.get( "prefix", "" )
196 _print( ui, *arg, **kwarg )
197
198
199
200 def _error( ui, *arg, **kwarg ) :
201 """
202 Print messages to C{stderr}. Each message will be prefixed with C{flow: error: }.
203
204 This function is a thin wrapper of L{_print}. See document of the later for usage detail.
205
206 Customized prefix will be appended after C{flow: error: }.
207
208 I{Example}:
209
210 >>> _error( ui, "message1", "message2", prefix = "prefix_" )
211 flow: error: prefix_message1
212 flow: error: prefix_message2
213 """
214 kwarg["warn" ] = True
215 kwarg["label" ] = kwarg.get( "label", "flow.error" )
216 kwarg["prefix"] = "error: " + kwarg.get( "prefix", "" )
217 _print( ui, *arg, **kwarg )
218
219
220
221 def _note( ui, *arg, **kwarg ) :
222 """
223 Print messages to C{stout}. Each message will be prefixed with C{flow: note: }. The messages will be displayed only when
224 user turns on C{--verbose}. If you want to print message without C{--verbose}, include an argument C{via_quiet = True} in
225 the call to this function.
226
227 This function is a thin wrapper of L{_print}. See document of the later for usage detail.
228
229 Customized prefix will be appended after C{flow: note: }.
230
231 I{Example}:
232
233 >>> _note( ui, "message1", "message2", prefix = "prefix_" )
234 flow: note: prefix_message1
235 flow: note: prefix_message2
236 """
237 if (kwarg.get( "via_quiet")) :
238 kwarg["note"] = not kwarg["via_quiet"]
239 del kwarg["via_quiet"]
240 else :
241 kwarg["note"] = True
242 kwarg["label" ] = kwarg.get( "label", "flow.note" )
243 kwarg["prefix"] = "note: " + kwarg.get( "prefix", "" )
244 _print( ui, *arg, **kwarg )
245
246
247
248 class AbortFlow( Exception ) :
249 """
250 Throw an instance of this exception whenever we have to abort the flow command.
251 """
252 def __init__( self, *arg, **kwarg ) :
253 """
254 Accept one or more error messages in C{str} as the arguments.
255 """
256 Exception.__init__( self, "Aborted hg flow command." )
257 self._msg = arg
258 for k in kwarg :
259 self.__dict__[k] = kwarg[k]
260
261
262
263 def error_message( self ) :
264 """
265 Returns a list of error messages in C{str}.
266 """
267 return self._msg
268
269
270
271 class AbnormalStream( Exception ) :
272 """
273 Throw an instance of this exception if the stream does not belong to any one of C{<master>}, C{<develop>}, C{<feature>},
274 C{<release>}, C{<hotfix>}, and C{<support>}.
275 """
276 def __init__( self, message = "", stream = None ) :
277 """
278 Accept one error message. You can also pass the C{Stream} object, which can be retrieved later via the C{stream}
279 method.
280 """
281 Exception.__init__( self, message )
282 self._stream = stream
283
284
285
286 def stream( self ) :
287 """
288 Return the C{Stream} object.
289 """
290 return self._stream
291
292
293
294 class Commands( object ) :
295 """
296 Wrapper class of C{mercurial.commands} with ability of recording command history.
297
298 I{Example:}
299
300 >>> commands = Commands()
301 >>> commands.commit( ui, repo, ... )
302 >>> commands.update( ui, repo, ... )
303 >>> commands.print_history()
304 flow: note: Hg command history:
305 flow: note: hg commit --message "flow: Closed release 0.7." --close_branch
306 flow: note: hg update default
307 """
308
309 def __init__( self ) :
310 self.ui = None
311 self._cmd = None
312 self._cmd_history = []
313 self._via_quiet = False
314 self._dryrun = False
315 self._common_opts = {}
316 self._opt_mutator = {}
317
318
319
320 def __getattr__( self, name ) :
321 """
322 Typical invocation of mercurial commands is in the form: commands.name( ... ).
323 We only need to save the command name here, leaving execution of the command to the L{__call__} function.
324 """
325 if (name[0] != "_") :
326 self._cmd = name
327 return self
328
329
330
331 def __call__( self, ui, repo, *arg, **kwarg ) :
332 """
333 Invoke the mercurial command and save it as a string into the history.
334 @raise AbortFlow: Throw exception if the return code of hg command (except C{commit} and C{rebase}) is nonzero.
335 """
336 self.ui = ui
337 cmd_str = "hg " + (self._cmd[:-1] if (self._cmd[-1] == "_") else self._cmd)
338 arg = self._branch2str( arg )
339 kwarg = self._branch2str( kwarg )
340 cmd = self._cmd
341
342 if (cmd[0] == "q") :
343 where = extensions.find( "mq" )
344 cmd = cmd[1:]
345 elif (cmd == "strip" ) : where = extensions.find( "mq" )
346 elif (cmd == "rebase") : where = extensions.find( "rebase" )
347 else : where = mercurial.commands
348
349 kwarg = self._mutate_options( where, self._cmd, kwarg )
350
351 for key, value in kwarg.items() :
352 if (value in [None, ""]) :
353 continue
354
355 # 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
357 # option (it avails only programmatically).
358 if (cmd == "commit" and (key in ["message", "force_editor",]) and "force_editor" in kwarg) :
359 continue
360
361 new_key = ""
362 for e in key :
363 new_key += "-" if (e == "_") else e
364 key = new_key
365 value = [self._add_quotes( e ) for e in value] if (isinstance( value, list )) else self._add_quotes( value )
366
367 if (isinstance( value, bool )) :
368 cmd_str = "%s --%s" % (cmd_str, key,)
369 elif (isinstance( value, list )) :
370 for e in value :
371 cmd_str = "%s --%s %s" % (cmd_str, key, str( e ),)
372 else :
373 cmd_str = "%s --%s %s" % (cmd_str, key, str( value ),)
374 for e in arg :
375 cmd_str = '%s %s ' % (cmd_str, self._add_quotes( str( e ) ),)
376
377 self._cmd_history.append( cmd_str )
378
379 if (self._dryrun) :
380 return
381
382 try :
383 # Ever since 2.8 the "strip" command has been moved out of the "mq" module to a new module of its own. Issue#56
384 if ("strip" == cmd) :
385 from mercurial import __version__
386 if (__version__.version > "2.8") :
387 where = extensions.find( "strip" )
388 cmd = "stripcmd"
389
390 ret = None
391 ret = getattr( where, cmd )( ui, repo, *arg, **kwarg )
392 except Exception, e :
393 raise AbortFlow( "Hg command failed: %s" % cmd_str, "abort: %s\n" % str( e ), traceback = sys.exc_info() )
394
395 if (ret and cmd not in ["commit", "rebase",]) :
396 # We have to make some exceptions, where nonzero return codes don't mean error. Issue#55
397 if ((ret, cmd,) not in [(1, "push",),]) :
398 raise AbortFlow( "Hg command failed: %s" % cmd_str, "abort: Nonzero return code from hg command\n" )
399
400
401
402 def _add_quotes( self, value ) :
403 """
404 If value is a string that contains space, slash, double quote, and parenthesis (viz: '(' or ')'), then wrap the value
405 with double quotes and properly escape double-quotes and slashes in the string, returning the treated value.
406 Otherwise, return the value as is.
407 """
408 if (isinstance( value, str ) and (1 in [c in value for c in " ()\\\""])) :
409 new_value = ""
410 for c in value :
411 if (c == "\\") : new_value += "\\\\"
412 elif (c == '"' ) : new_value += "\""
413 else : new_value += c
414 value = '"%s"' % new_value
415 return value
416
417
418
419 def _branch2str( self, value ) :
420 """
421 If C{value} is a C{Branch} object, return its fullname (C{str}); if it is not, return the object itself. Do this
422 recursively if C{value} is a C{tuple}, or C{list}, or C{dict} object.
423 """
424 if (isinstance( value, Branch )) :
425 return value.fullname()
426 if (isinstance( value, (list, tuple,) )) :
427 new_value = []
428 for e in value :
429 new_value.append( self._branch2str( e ) )
430 return new_value
431 if (isinstance( value, dict )) :
432 new_value = {}
433 for k, v in value.items() :
434 new_value[k] = self._branch2str( v )
435 return new_value
436 return value
437
438
439
440 def _filter_common_options( self, where, cmd ) :
441 """
442 If any common options are valid options of the command, return these options.
443
444 @type where: module
445 @param where: Should be `mercurial.commands', or a Mercurial's plugin object.
446 @type cmd : C{str}
447 @param cmd : command name
448 """
449 ret = {}
450 if (self._common_opts != {}) :
451 if (cmd[-1] == "_") :
452 cmd = cmd[:-1]
453 junk, table = mercurial.cmdutil.findcmd( cmd, where.table if hasattr( where, "table" ) else where.cmdtable )
454 opts = [e[1] for e in table[1]]
455 for e in self._common_opts :
456 if (e in opts) :
457 ret[e] = self._common_opts[e]
458 return ret
459
460
461
462 def _mutate_options( self, where, cmd, opts ) :
463 """
464
465 """
466 common_opts = self._filter_common_options( where, cmd )
467 common_opts.update( opts )
468 opts = common_opts
469 mutator = self._opt_mutator.get( cmd )
470 if (mutator) :
471 opts = mutator( opts )
472
473 return opts
474
475
476
477 def use_quiet_channel( self, via_quiet = True ) :
478 """
479 Print the history to the I{quiet} channel, where text will be displayed even when user does not specify the
480 C{--verbose} option.
481 """
482 self._via_quiet = via_quiet
483
484
485
486 def use_verbose_channel( self, via_verbose = True ) :
487 """
488 Print the history to the I{verbose} channel, where text will be display only when user specify the C{--verbose} option.
489 """
490 self._via_quiet = not via_verbose
491
492
493
494 def reg_common_options( self, opts ) :
495 """
496 Register common options.
497
498 @type opts: C{dict}
499 @param opts: Common options. Key = option's flag, value = option's value.
500 """
501 self._common_opts.update( opts )
502
503
504
505 def reg_option_mutator( self, cmd, mutator ) :
506 """
507 Register common options.
508
509 @type opts: C{dict}
510 @param opts: Common options. Key = option's flag, value = option's value.
511 """
512 self._opt_mutator[cmd] = mutator
513
514
515
516 def dryrun( self, switch = None ) :
517 """
518 Switch the dry-run mode.
519
520 @type switch: C{boolean} or C{None}
521 @param switch: Switch on dry-run mode if C{switch = True}, off if C{switch = False}. If C{switch} is C{None}, just
522 return the current state of dry-run mode.
523 """
524 if (switch is None) :
525 return self._dryrun
526 self._dryrun = switch
527
528
529
530 def print_history( self ) :
531 """
532 Print the command history using the L{_note} function.
533 """
534 if (self.ui) :
535 _note( self.ui, "Hg command history:", via_quiet = self._via_quiet )
536 for e in self._cmd_history :
537 _note( self.ui, e, prefix = " ", via_quiet = self._via_quiet )
538
539
540
541 class Stream( object ) :
542 @staticmethod
543 def gen( ui, repo, name, check = False ) :
544 """
545 Given the name of a stream, return a C{Stream} object.
546 If the name is that of one of the standard streams: master, develop, feature, release, hotfix, and support, return the
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
548 streams, an C{AbnormalStream} exception will be thrown. One can catch the exception and call its C{stream} method to
549 get the object.
550
551 @type name : C{str}
552 @param name : Name of the stream
553 @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
555 or not and (if exists) open or not.
556
557 @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
559 """
560 for e in STREAM.values() :
561 if (name == e.name()) :
562 return e
563
564 rootstream_name = name.split( '/', 1 )[0]
565 is_normalstream = True
566 if (rootstream_name in STREAM) :
567 trunk = name.replace( rootstream_name + '/', STREAM[rootstream_name].prefix(), 1 )
568 stream = Stream( ui, repo, name, trunk = trunk )
569 else :
570 stream = Stream( ui, repo, name, trunk = name )
571 is_normalstream = False
572 if (check) :
573 try :
574 trunk = stream.trunk()
575 except error.RepoLookupError :
576 misspelling = difflib.get_close_matches( stream.name(), STREAM.keys(), 3, 0.7 )
577 note = "Did you mean: %s?" % " or ".join( misspelling ) if (misspelling) else None
578 raise AbortFlow( "Stream not found: %s" % stream, note = note )
579 if (trunk.is_closed()) :
580 raise AbortFlow( "%s has been closed." % stream )
581 if (not is_normalstream) :
582 raise AbnormalStream( stream = Stream( ui, repo, name ) )
583 return stream
584
585
586
587 def __init__( self, ui, repo, name, **kwarg ) :
588 """
589 Create a new C{Stream} object.
590
591 @type name : C{str}
592 @param name : Name of the new stream
593 @type trunk : C{str} or C{None}
594 @param trunk : Fullname of the trunk of the stream, or C{None}
595 @type prefix: C{str}
596 @param prefix: Name prefix of branches in this stream. If not specified, it will default to C{trunk + '/'} (if C{trunk}
597 is not C{None}), or C{name + '/'} if (C{trunk} is C{None}).
598 @type source: C{Stream}
599 @param source: Stream where branches in this stream will be created from
600 @type destin: C{list} of C{Stream} objects
601 @param destin: Streams where branches in this stream will merge to when being finished
602 """
603 self.ui = ui
604 self.repo = repo
605
606 self._name = name
607 self._trunk = kwarg.get( "trunk" )
608 self._prefix = kwarg.get( "prefix" )
609 self._source = kwarg.get( "source", self )
610 self._destin = kwarg.get( "destin", [self._source,] )
611 self._tcache = None # Caches `Branch' object of the trunk because construction of a `Branch' object is very slow.
612
613 if (self._prefix is None) :
614 if (self._trunk) :
615 self._prefix = self._trunk + '/'
616 else :
617 self._prefix = self._name + '/'
618
619
620
621 def __str__( self ) :
622 """
623 Return a string: '<stream-name>'.
624 """
625 return "<%s>" % self._name
626
627
628
629 def __cmp__( self, rhs ) :
630 """
631 Compare streams by comparing their names as strings.
632 """
633 lhs = self._name
634 rhs = rhs ._name
635 return -1 if (lhs < rhs) else (1 if (lhs > rhs) else 0)
636
637
638
639 def __contains__( self, stranch ) :
640 """
641 Return true if the C{stanch} is in this stream.
642
643 @type stranch: C{Stream} or C{Branch}
644 @param srranch: Stream or branch which you want to test if it is in this stream
645 """
646 if (isinstance( stranch, Branch )) :
647 if (stranch._fullname == self._trunk) :
648 return True
649 return stranch._fullname.startswith( self.prefix() )
650 elif (isinstance( stranch, Stream )) :
651 return stranch.prefix().startswith( self.prefix() )
652 return str( stranch ).startswith( self.prefix() )
653
654
655
656 def name( self ) :
657 """
658 Return the name of this stream.
659 """
660 return self._name
661
662
663
664 def trunk( self, trace = False ) :
665 """
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
667 C{trace} parameter.
668
669 @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
671 until a trunk is found. If false, this function will return C{None}.
672
673 @return: A C{Branch} object or C{None}
674 """
675 if (self._tcache) :
676 return self._tcache
677
678 trunk = Branch( self.ui, self.repo, self._trunk ) if (self._trunk) else None
679 if (not trunk and trace) :
680 return self.source().trunk( True )
681 self._tcache = trunk
682 return trunk
683
684
685
686 def prefix( self ) :
687 """
688 Return the branch name prefix of this stream.
689
690 @return: C{str}
691 """
692 return self._prefix
693
694
695
696 def source( self ) :
697 """
698 Return the source stream.
699
700 @return: C{Stream}
701 """
702 return self._source
703
704
705
706 def destin( self ) :
707 """
708 Return a list of streams where branches in this stream will merge to when finished.
709
710 @return: C{Stream}
711 """
712 return self._destin
713
714
715
716 def get_fullname( self, branch_basename ) :
717 """
718 Return the fullname of a branch.
719
720 @type branch_basename: C{str}
721 @param branch_basename: Basename of a branch in this stream
722
723 @return: C{str}
724 """
725 return self._prefix + branch_basename
726
727
728
729 def get_branch( self, branch_basename ) :
730 """
731 Create and return a new C{Branch} object with the given basename.
732
733 @type branch_basename: C{str}
734 @param branch_basename: Basename of a branch in this stream
735
736 @return: C{Branch}
737 """
738 return Branch( self.ui, self.repo, self.get_fullname( branch_basename ) )
739
740
741
742 def branches( self, openclosed = "open" ) :
743 """
744 Return a list of branches in this stream. The list does not include the trunk.
745 The returned list is sorted per branch name.
746
747 @type openclosed: C{str}, must be one of "open", "closed", and "all".
748 @param openclosed: If the value is C{"open"}, return all open branches in this stream; if C{"closed"}, return all
749 closed branches in this stream; if C{"all"}, returns all open and closed branches in this stream.
750 """
751 if (openclosed not in ["open", "closed", "all",]) :
752 raise ValueError( "Invalid value for `openclosed` parameter: %s" % openclosed )
753
754 if (openclosed == "open") :
755 all_branches = [Branch( self.ui, self.repo, head[0] ) for branch_fullname, head in self.repo.branchmap().items()
756 if (not self.repo[head[0]].extra().get( "close", False ) )]
757 elif (openclosed == "closed") :
758 all_branches = [Branch( self.ui, self.repo, head[0] ) for branch_fullname, head in self.repo.branchmap().items()
759 if (self.repo[head[0]].extra().get( "close", False ) )]
760 else :
761 all_branches = [Branch( self.ui, self.repo, head[0] ) for branch_fullname, head in self.repo.branchmap().items()]
762
763 return sorted( [e for e in all_branches if (e in self)] )
764
765
766
767 class Branch( object ) :
768 def __init__( self, ui, repo, rev = None ) :
769 """
770 Create a C{Branch} object with the given C{rev}.
771 """
772 self.ui = ui
773 self.repo = repo
774 self.ctx = repo[rev] # `repo[rev]' is slow when there are tens of thousands of named branches.
775
776 self._fullname = str( self.ctx.branch() )
777
778
779
780 def __str__( self ) :
781 """
782 Return the fullname of this branch.
783 """
784 return self._fullname
785
786
787
788 def __cmp__( self, rhs ) :
789 """
790 Compare two C{Branch} object by comparing their fullnames.
791 """
792 if (rhs is None) :
793 return 1
794 lhs = self._fullname
795 rhs = rhs ._fullname
796 return -1 if (lhs < rhs) else (1 if (lhs > rhs) else 0)
797
798
799
800 def fullname( self ) :
801 """
802 Return the fullname of this branch.
803 """
804 return self._fullname
805
806
807
808 def basename( self, stream = None, should_quote = False ) :
809 """
810 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).
812 Return the string "trunk" if this branch is the trunk of the C{stream}.
813
814 @type stream: C{Stream} or C{None}
815 @param stream: Stream to which the basename is relative
816 """
817 if (stream) :
818 if (self._fullname == stream._trunk) :
819 return "trunk"
820 ret = self._fullname[len( stream.prefix() ):]
821 else :
822 ret = self._fullname.rsplit( '/', 1 )[-1]
823 if (should_quote) :
824 ret = "'%s'" % ret
825 return ret
826
827
828
829 def is_closed( self ) :
830 """
831 Return true if this branch is closed; or false if it is open.
832 """
833 extra = self.ctx.extra()
834 try :
835 return extra["close"]
836 except KeyError :
837 return False
838
839
840
841 def is_open( self ) :
842 """
843 Return true if this branch is open; or false if it is closed.
844 """
845 return not self.is_closed()
846
847
848
849 def is_develop_trunk( self ) :
850 """
851 Return true if this branch is the trunk of C{<develop>}.
852 """
853 return STREAM["develop"]._trunk == self._fullname
854
855
856
857 def is_master_trunk( self ) :
858 """
859 Return true if this branch is the trunk of C{<master>}.
860 """
861 return STREAM["master"]._trunk == self._fullname
862
863
864
865 def is_trunk( self, stream ) :
866 """
867 Return true if this branch is the trunk of the C{stream}.
868 """
869 return stream._trunk == self._fullname
870
871
872
873 def stream( self ) :
874 """
875 Return the stream that this branch belongs to.
876 """
877 name = self._fullname
878 for stream in STREAM.values() :
879 if (name == stream._trunk) :
880 return stream
881 if (name.startswith( stream.prefix() )) :
882 name = name.replace( stream.prefix(), stream.name() + '/' )
883 break
884 return Stream.gen( self.ui, self.repo, name.rsplit( '/', 1 )[0] )
885
886
887
888 commands = Commands()
889 STREAM = {} # key = stream name, value = `Stream` object. Will be set by `Flow.__init__`.
890
891
892
893 class Flow( object ) :
894 def __init__( self, ui, repo, init = False ) :
895 """
896 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.
898 A warning message will be issued if the repository has uncommitted changes.
899
900 @type init: C{boolean}
901 @param init: If true, a C{Flow} object will be constructed for initialization of hgflow. Such constructed object does
902 not supply all functionalities and is only meant to execute the `hg flow init` command.
903 """
904 self.ui = ui
905 self.repo = repo
906
907 self.autoshelve = False
908 self.warn_uncommitted = True
909 self.msg_prefix = "flow: "
910 self.version_prefix = "v"
911 self.orig_workspace = Branch( ui, repo )
912 self.curr_workspace = self.orig_workspace # May be changed whenever `hg update` command is executed.
913 self.orig_dir = os.getcwd()
914 self._dryrun_shelve = set()
915
916 if (init) : return
917
918 config_fname = os.path.join( self.repo.root, CONFIG_BASENAME )
919 if (os.path.isfile( config_fname )) :
920 cfg = config.config()
921 cfg.read( config_fname )
922 try :
923 master = cfg.get( CONFIG_SECTION_BRANCHNAME, "master" )
924 develop = cfg.get( CONFIG_SECTION_BRANCHNAME, "develop" )
925 feature = cfg.get( CONFIG_SECTION_BRANCHNAME, "feature" )
926 release = cfg.get( CONFIG_SECTION_BRANCHNAME, "release" )
927 hotfix = cfg.get( CONFIG_SECTION_BRANCHNAME, "hotfix" )
928 support = cfg.get( CONFIG_SECTION_BRANCHNAME, "support" )
929 except Exception, e :
930 self._error( str( e ) )
931 self._error( "Flow has not been initialized properly for this repository." )
932 self._note ( "You can use command `hg flow init -f` to reinitialize for this repository.", via_quiet = True )
933 sys.exit( 1 )
934 else :
935 old_config_fname = os.path.join( self.repo.root, OLD_CONFIG_BASENAME )
936 if (os.path.isfile( old_config_fname )) :
937 cfg = config.config()
938 cfg.read( old_config_fname )
939 try :
940 master = cfg.get( CONFIG_SECTION_BRANCHNAME, "master" )
941 develop = cfg.get( CONFIG_SECTION_BRANCHNAME, "develop" )
942 feature = cfg.get( CONFIG_SECTION_BRANCHNAME, "feature" )
943 release = cfg.get( CONFIG_SECTION_BRANCHNAME, "release" )
944 hotfix = cfg.get( CONFIG_SECTION_BRANCHNAME, "hotfix" )
945 support = cfg.get( CONFIG_SECTION_BRANCHNAME, "support" )
946 except Exception, e :
947 self._error( str( e ) )
948 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 )
950 sys.exit( 1 )
951 else :
952 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 )
954 sys.exit( 1 )
955
956 global STREAM
957 STREAM["master" ] = Stream( ui, repo, "master", trunk = master )
958 STREAM["develop"] = Stream( ui, repo, "develop", trunk = develop )
959 STREAM["feature"] = Stream( ui, repo, "feature", prefix = feature, source = STREAM["develop"] )
960 STREAM["release"] = Stream( ui, repo, "release", prefix = release, source = STREAM["develop"] )
961 STREAM["hotfix" ] = Stream( ui, repo, "hotfix", prefix = hotfix, source = STREAM["master" ] )
962 STREAM["support"] = Stream( ui, repo, "support", prefix = support, source = STREAM["master" ], destin = [] )
963
964 STREAM["develop"]._destin.append( STREAM["release"] )
965 STREAM["release"]._destin.append( STREAM["master" ] )
966 STREAM["hotfix" ]._destin.append( STREAM["develop"] )
967
968 if (ui.has_section( "hgflow" )) :
969 self._warn( "The [hgflow] section in hg configuration file is deprecated." )
970 self._warn( "Please replace the section name from [hgflow] to [flow]." )
971 self.autoshelve = ui.configbool( "hgflow", "autoshelve", self.autoshelve )
972 self.warn_uncommitted = ui.configbool( "hgflow", "warn_uncommitted", self.warn_uncommitted )
973 if (ui.has_section( "flow" )) :
974 self.autoshelve = ui.configbool( "flow", "autoshelve", self.autoshelve )
975 self.warn_uncommitted = ui.configbool( "flow", "warn_uncommitted", self.warn_uncommitted )
976 self.msg_prefix = ui.config ( "flow", "prefix", self.msg_prefix ).strip( STRIP_CHARS )
977 self.version_prefix = ui.config ( "flow", "version_prefix", self.version_prefix ).strip( STRIP_CHARS )
978 if (self._has_uncommitted_changes() and self.warn_uncommitted) :
979 self._warn( "Your workspace has uncommitted changes." )
980
981 # We'd better temporarily change the current directory to the root of the repository at the beginning.
982 # This is to avoid the problem that the CWD might be gone after switching to a different branch. (Issue#14)
983 # We will change it back to the original directory when the hgflow command exits.
984 os.chdir( self.repo.root )
985 # __init__
986
987
988
989 def __getattr__( self, name ) :
990 """
991 Execute mercurial command of name C{name[1:]}.
992
993 @type name: C{str}
994 @param name: Should be a mercurial command name prefixed with one underscore. For example, to call C{commit} command,
995 use C{self._commit}.
996 """
997 if (name[0] == "_") :
998 cmd = getattr( commands, name[1:] )
999 def func( *arg, **kwarg ) :
1000 cmd( self.ui, self.repo, *arg, **kwarg )
1001 return func
1002 raise AttributeError( "%s instance has no attribute '%s'" % (self.__class__, name,) )
1003
1004
1005
1006 def _update( self, rev, *arg, **kwarg ) :
1007 """
1008 Intercept the call to `hg update` command. We need to keep track of the branch of the workspace.
1009
1010 @type rev: C{str} or C{mercurial.changectx}
1011 @param rev: Revision to which the workspace will update
1012 """
1013 try :
1014 old_workspace_ctx = self.curr_workspace.ctx
1015 self.curr_workspace = rev if (isinstance( rev, Branch )) else Branch( self.ui, self.repo, rev )
1016 except error.RepoLookupError, e :
1017 if (commands.dryrun()) :
1018 commands.update( self.ui, self.repo, rev, *arg, **kwarg )
1019 else :
1020 raise e
1021
1022 if (old_workspace_ctx != self.curr_workspace.ctx) :
1023 commands.update( self.ui, self.repo, rev, *arg, **kwarg )
1024
1025
1026
1027 def _print( self, *arg, **kwarg ) :
1028 """
1029 Thin wrapper of the global C{_print} function
1030 """
1031 _print( self.ui, *arg, **kwarg )
1032
1033
1034
1035 def _warn( self, *arg, **kwarg ) :
1036 """
1037 Thin wrapper of the global C{_warn} function
1038 """
1039 _warn( self.ui, *arg, **kwarg )
1040
1041
1042
1043 def _error( self, *arg, **kwarg ) :
1044 """
1045 Thin wrapper of the global C{_error} function
1046 """
1047 _error( self.ui, *arg, **kwarg )
1048
1049
1050
1051 def _note( self, *arg, **kwarg ) :
1052 """
1053 Thin wrapper of the global C{_note} function
1054 """
1055 _note( self.ui, *arg, **kwarg )
1056
1057
1058
1059 def _check_rebase( self ) :
1060 """
1061 Check if 'rebase' extension is activated. If not, raise an 'AbortFlow' exception.
1062
1063 @raise AbortFlow: When 'rebase' extension is not found
1064 """
1065 try :
1066 extensions.find( "rebase" )
1067 except KeyError :
1068 raise AbortFlow( "Cannot rebase without 'rebase' extension." )
1069
1070
1071
1072 def _check_mq( self ) :
1073 """
1074 Check if 'mq' extension is activated. If not, raise an 'AbortFlow' exception.
1075
1076 @raise AbortFlow: When 'mq' extension is not found
1077 """
1078 try :
1079 extensions.find( "mq" )
1080 except KeyError :
1081 raise AbortFlow( "Cannot shelve/unshelve changes without 'mq' extension." )
1082
1083
1084
1085 def _check_strip( self ) :
1086 """
1087 The 'strip' command comes with the 'mq' extension.
1088 Check if 'mq' extension is activated. If not, raise an 'AbortFlow' exception.
1089
1090 @raise AbortFlow: When 'mq' extension is not found
1091 """
1092 try :
1093 extensions.find( "mq" )
1094 except KeyError :
1095 raise AbortFlow( "Cannot use 'strip' command without 'mq' extension." )
1096
1097
1098
1099 def _is_shelved( self, branch ) :
1100 """
1101 Return true if the given branch has been shelved.
1102
1103 @type branch: C{Branch}
1104 @param branch: Branch to test if it has shelved changes
1105 """
1106 shelve_name = "flow/" + branch.fullname() + ".pch"
1107 patch_fname = self.repo.join( "patches/" + shelve_name )
1108 return os.path.isfile( patch_fname )
1109
1110
1111
1112 def _shelve( self, *arg, **kwarg ) :
1113 """
1114 Shelve workspace if C{self.autoshelve} is C{True}.
1115
1116 This function utilizes the C{mq} extension to achieve shelving. Bascially, it calls the following C{mq} commands:
1117 C{hg qnew <patchname> --currentuser --currentdate -m "Shelved changes"}
1118 C{hg qpop}
1119 where <patchname> follows the pattern: flow/<branch_fullname>.pch
1120 The two commands will give us a patch file that later will be used to unshelve the change.
1121 """
1122 if (self.autoshelve or kwarg.get( "force" )) :
1123 if (self._has_uncommitted_changes()) :
1124 shelve_name = "flow/" + self.curr_workspace.fullname() + ".pch"
1125 if (commands.dryrun()) :
1126 # For dry run, adds the name of the shelved item into `self._dryrun_shelve'.
1127 # This is for generating correct dry run history for the unshelving operation.
1128 self._dryrun_shelve.add( shelve_name )
1129 self._check_mq()
1130 self._qnew( shelve_name, currentuser = True, currentdate = True, message = "Shelved changes" )
1131 self._qpop()
1132
1133
1134
1135 def _unshelve( self, basename = None, **kwarg ) :
1136 """
1137 Unshelve the previously shelved changes to the workspace if C{self.autoshelve} is C{True}.
1138
1139 This function needs the C{mq} extension to achieve unshelving. Bascially, it calls the following commands:
1140 C{hg import <patch_filename> --no-commit}
1141 C{hg qdelete <patchname>}
1142 where <patchname> follows the pattern: flow/<branch_fullname>.pch, which was previously created by flow's shelving.
1143
1144 @type basename: C{str}
1145 @param basename: Base name of the shelved patch file. Default is the name of current workspace branch.
1146 """
1147 if (self.autoshelve or kwarg.get( "force" )) :
1148 basename = basename if (basename) else self.curr_workspace.fullname()
1149 shelve_name = "flow/" + basename + ".pch"
1150 patch_fname = self.repo.join( "patches/" + shelve_name )
1151 if (os.path.isfile( patch_fname ) or (shelve_name in self._dryrun_shelve)) :
1152 self._check_mq()
1153 self._import_( patch_fname, no_commit = True, base = "", strip = 1 )
1154 self._qdelete( shelve_name )
1155 if (commands.dryrun()) :
1156 self._dryrun_shelve.discard( shelve_name )
1157
1158
1159
1160 def _has_uncommitted_changes( self ) :
1161 """
1162 Return true if any tracked file is modified, or added, or removed, or deleted.
1163 """
1164 return any( self.repo.status() )
1165
1166
1167
1168 def _branches( self, openclosed = "open" ) :
1169 """
1170 Return a list of branches.
1171
1172 @type openclosed: C{str}, "open", "closed", and "all"
1173 @param openclosed: If C{"open"}, return all open branches; if C{"closed"}, return all closed branches; if C{"all"},
1174 return all branches.
1175 """
1176 if (openclosed not in ["open", "closed", "all",]) :
1177 raise ValueError( "Invalid value for openclosed parameter: %s" % openclosed )
1178 if (openclosed == "open") :
1179 all_branches = [Branch( self.ui, self.repo, head[0] ) for branch_fullname, head in self.repo.branchmap().items()
1180 if (not self.repo[head[0]].extra().get( "close", False ) )]
1181 elif (openclosed == "closed") :
1182 all_branches = [Branch( self.ui, self.repo, head[0] ) for branch_fullname, head in self.repo.branchmap().items()
1183 if (self.repo[head[0]].extra().get( "close", False ))]
1184 else :
1185 all_branches = [Branch( self.ui, self.repo, head[0] ) for branch_fullname, head in self.repo.branchmap().items()]
1186 return all_branches
1187
1188
1189
1190 def _find_branch( self, fullname ) :
1191 """
1192 Return true if a branch C{fullname} is open.
1193
1194 @type fullname: C{str}
1195 @param fullname: Fullname of branch that you want to know whether it is open
1196 """
1197 try :
1198 Branch( self.ui, self.repo, fullname )
1199 return True
1200 except error.RepoLookupError :
1201 return False
1202
1203
1204
1205 def latest_master_tags( self ) :
1206 """
1207 Return the latest tag of C{<master>} branch.
1208 """
1209 trunk = STREAM["master"].trunk()
1210 trunk_fullname = trunk.fullname()
1211 master_context = trunk.ctx
1212 while (master_context) :
1213 tags = master_context.tags()
1214 try :
1215 tags.remove( "tip" )
1216 except ValueError :
1217 pass
1218 if (tags) :
1219 return tags
1220 parents = master_context.parents()
1221 master_context = None
1222 for e in parents :
1223 if (trunk_fullname == e.branch()) :
1224 master_context = e
1225 break
1226 return []
1227
1228
1229
1230 def _create_branch( self, fullname, message, from_branch = None, **kwarg ) :
1231 """
1232 Create a new branch and commit the change.
1233
1234 @type fullname: C{str}
1235 @param fullname: Fullname of the new branch
1236 @type message: C{str}
1237 @param message: Commit message
1238 @type from_branch: C{Branch}
1239 @param from_branch: Parent branch of the new branch
1240 """
1241 if (from_branch and self.curr_workspace != from_branch) :
1242 self._update( from_branch )
1243 self._branch( fullname )
1244 self._commit( message = message, **kwarg )
1245 if (commands.dryrun()) :
1246 # Makes a fake new branch.
1247 self.curr_workspace = Branch( self.ui, self.repo )
1248 self.curr_workspace._fullname = fullname
1249 else :
1250 self.curr_workspace = Branch( self.ui, self.repo, fullname )
1251
1252
1253
1254 def _action_start( self, stream, *arg, **kwarg ) :
1255 """
1256 Conduct the I{start} action for the given stream. A new branch in the stream will be created.
1257
1258 @type stream: C{Stream}
1259 @param stream: Stream where you want to start a new branch
1260 """
1261 try :
1262 basename = arg[1]
1263 except IndexError :
1264 raise AbortFlow( "You must specify a name for the new branch to start." )
1265
1266 rev = kwarg.pop( "rev", None )
1267 msg = kwarg.pop( "message", "" )
1268 dirty = kwarg.pop( "dirty", None )
1269 fullname = stream.get_fullname( basename )
1270 if (self._find_branch( fullname )) :
1271 self._warn( "An open branch named '%s' already exists in %s." % (basename, stream,) )
1272 else :
1273 shelvedpatch_basename = self.curr_workspace.fullname()
1274 if (rev is None) :
1275 from_branch = stream.source().trunk()
1276 self._shelve( force = dirty )
1277 self._update( from_branch )
1278 else :
1279 from_branch = Branch( self.ui, self.repo, rev )
1280 if (from_branch._fullname != stream.source()._trunk) :
1281 raise AbortFlow( "Revision %s is not in the source stream of %s." % (rev, stream,) )
1282 self._shelve( force = dirty )
1283 self._update( rev = rev )
1284 if (msg) :
1285 msg = "%s\n" % msg
1286 self._create_branch( fullname, "%s%sCreated branch '%s'." % (msg, self.msg_prefix, fullname,), **kwarg )
1287 if (dirty) :
1288 self._unshelve( shelvedpatch_basename, force = dirty )
1289
1290
1291
1292 def _action_push( self, stream, *arg, **kwarg ) :
1293 """
1294 Conduct the I{push} action for the given stream. The workspace branch will be pushed to the remote repository.
1295
1296 @type stream: C{Stream}
1297 @param stream: Stream where you want to push the workspace branch
1298 """
1299 if (self.curr_workspace in stream) :
1300 self._push( new_branch = True, branch = [self.curr_workspace.fullname(),] )
1301 else :
1302 raise AbortFlow( "Your workspace is '%s' branch, which is not in %s." % (self.curr_workspace, stream,),
1303 "To push a %s branch, you must first update to it." % stream )
1304
1305
1306
1307 def _action_pull( self, stream, *arg, **kwarg ) :
1308 """
1309 Conduct the I{pull} action for the given stream. The workspace branch will be updated with changes pulled from the
1310 remote repository.
1311
1312 @type stream: C{Stream}
1313 @param stream: Stream where you want to pull for the workspace branch
1314 """
1315 try :
1316 branch = stream.get_fullname( arg[1] )
1317 except IndexError :
1318 branch = self.curr_workspace
1319 if (branch not in stream) :
1320 raise AbortFlow( "Your workspace is '%s' branch, which is not in %s." % (branch, stream,),
1321 "To pull a %s branch, you must first update to it." % stream )
1322
1323 self._pull( update = True, branch = [branch,] )
1324
1325
1326
1327 def _action_list( self, stream, *arg, **kwarg ) :
1328 """
1329 Print all open branches in the given stream.
1330
1331 @type stream: C{Stream}
1332 @param stream: Stream of which you want to display open branches
1333 """
1334 # Lists all open branches in this stream.
1335 open_branches = stream.branches()
1336 trunk = stream.trunk()
1337 if (trunk) :
1338 tags = ""
1339 if (stream == STREAM["master"]) :
1340 tags = self.latest_master_tags()
1341 tags = (", latest tags: %s" % ", ".join( tags )) if (tags) else ""
1342 self._print( "%s trunk: %s%s" % (stream, trunk, tags,) )
1343 if (open_branches) :
1344 self._print( "Open %s branches:" % stream )
1345 for e in open_branches :
1346 marker = "#" if (self._is_shelved( e ) ) else ""
1347 marker += "*" if (e == self.curr_workspace) else ""
1348 self._print( str( e ) + marker, prefix = " " )
1349 else :
1350 self._print( "No open %s branches" % stream )
1351 if (kwarg.get( "closed" )) :
1352 closed_branches = stream.branches( "closed" )
1353 if (closed_branches) :
1354 self._print( "Closed %s branches:" % stream )
1355 closed_branches.sort( lambda x, y : y.ctx.rev() - x.ctx.rev() )
1356 for e in closed_branches :
1357 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" )
1359 self.ui.write( " %s\n" % util.datestr( e.ctx.date(), format = "%Y-%m-%d %a %H:%M %1" ),
1360 label = "log.date" )
1361 bn = str( e )
1362 p1 = e.ctx
1363 while (p1.branch() == bn) :
1364 e = p1
1365 p1 = e.p1()
1366 description = e.description()
1367 msg_prefix = ("flow: ", "hgflow: ", "hg flow,", self.msg_prefix or "#@$(&*^$",)
1368 if (not (description.startswith( msg_prefix ))) :
1369 lines = [e.strip() for e in description.split( "\n" )]
1370 self.ui.note( " description: %s\n" % lines[0] )
1371 for line in lines[1:] :
1372 if (not (line.startswith( msg_prefix ))) :
1373 self.ui.note( " %s\n" % lines[0] )
1374 self.ui.note( "\n" )
1375 else :
1376 self._print( "No closed %s branches" % stream )
1377
1378
1379
1380 def _action_log( self, stream, *arg, **kwarg ) :
1381 """
1382 Show revision history of the specified branch.
1383
1384 @type stream: C{Stream},
1385 @param stream: Stream where the specified branch is
1386 """
1387 # User may specify a file with a relative path name. Since CWD has changed to the repository's root dir when the
1388 # `Flow' object was constructed, we need to restore the original dir to get the correct path name of the file.
1389 os.chdir( self.orig_dir )
1390 filenames = kwarg.pop( "file", [] )
1391 onstream = kwarg.pop( "onstream", False )
1392 closed = kwarg.pop( "closed", False )
1393 if (onstream) :
1394 filenames.extend( arg[1:] )
1395 branches = stream.branches( "all" if (closed) else "open" )
1396 if (stream._trunk) :
1397 branches.append( stream._trunk )
1398 else :
1399 # Case 1: hg flow <stream> log <basename>
1400 # - Shows the log of the "<stream>/<basename>" branch.
1401 # Case 2: hg flow <stream> log
1402 # - Case 2a: <stream> does not have a trunk
1403 # - Shows the log of the current workspace, which should be a branch in <stream>.
1404 # - Case 2b: <stream> has a trunk
1405 # - Case 2b1: Current workspace is a branch in <stream>.
1406 # - Shows the log of the current workspace.
1407 # - Case 2b2: Current workspace is not a branch in <stream>.
1408 # - Shows the log of <stream>'s trunk.
1409 # Case 3: hg flow <stream> log <filename>
1410 # - This case can be overriden by Case 1. Namely, if the <filename> happens to be the same as the
1411 # <basename>, the latter will take precedence.
1412 # - Case 3a: The current workspace is in <stream>
1413 # - Show the log of <filename> in the current workspace branch.
1414 # - Case 3b: The current workspace is not in <stream>, and <stream> has a trunk.
1415 # - Show the log of <filename> in <stream>'s trunk.
1416 # - Case 3c: The current workspace is not in <stream>, and <stream> has no trunk.
1417 # - Error
1418 try :
1419 branch = stream.get_branch( arg[1] )
1420 # Case 1
1421 except error.RepoLookupError :
1422 filenames.append( arg[1] )
1423 if (self.curr_workspace in stream) :
1424 # Case 3a
1425 branch = self.curr_workspace
1426 else :
1427 branch = stream.trunk()
1428 if (not branch) :
1429 # Case 3c
1430 raise AbortFlow( "Cannot determine branch in %s. Please be more specific." % stream )
1431 else :
1432 # Case 3b
1433 # Just be clear that we have covered Case 2b2.
1434 pass
1435 except IndexError :
1436 branch = stream.trunk()
1437 if (not branch) :
1438 # Case 2a
1439 branch = self.curr_workspace
1440 if (branch not in stream) :
1441 raise AbortFlow( "Your workspace is '%s' branch, which is not in %s." % (branch, stream,),
1442 "To show log of a %s branch, you must also specify its name." % stream )
1443 elif (self.curr_workspace in stream) :
1444 # Case 2b1
1445 branch = self.curr_workspace
1446 else :
1447 # Case 2b2
1448 # Just be clear that we have covered Case 2b2.
1449 pass
1450 # At this point, `branch` must be existent.
1451 branches = [branch,]
1452
1453 # OK. We have to explicitly specify the date, rev, and user arguments to prevent mercurial python APIs from crashing.
1454 opts = {"date" : None, "rev" : None, "user" : None, "branch" : branches,}
1455 opts.update( kwarg )
1456 self._log( *filenames, **opts )
1457
1458
1459
1460 def _action_abort( self, stream, *arg, **kwarg ) :
1461 """
1462 Abort the workspace branch.
1463
1464 @type stream: C{Stream}
1465 @param stream: Stream where the branch which you want to abort is
1466 """
1467 msg = kwarg.pop( "message", "" )
1468 should_erase = kwarg.pop( "erase", False )
1469 onstream = kwarg.pop( "onstream", False )
1470 curr_workspace = self.curr_workspace
1471 if (msg) :
1472 msg = "%s\n" % msg
1473 if (curr_workspace.is_develop_trunk()) :
1474 raise AbortFlow( "You cannot abort the <develop> trunk." )
1475 if (onstream) :
1476 branches = stream.branches()
1477 if (stream == STREAM["develop"]) :
1478 branches.remove( stream.trunk() )
1479 elif (stream._trunk) :
1480 branches.append( stream.trunk() )
1481 for branch in branches :
1482 if (should_erase) :
1483 self._strip( rev = ["branch('%s')" % branch,] )
1484 else :
1485 self._update( branch )
1486 self._commit( close_branch = True, message = "%s%sAborted %s %s." %
1487 (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) :
1489 self._update( self.orig_workspace )
1490 else :
1491 if (curr_workspace.is_trunk( stream )) :
1492 curr_stream = curr_workspace.stream()
1493 raise AbortFlow( "You cannot abort a trunk.",
1494 "To abort '%s' as a branch, use `hg flow %s abort`." % (curr_workspace, curr_stream.name(),)
1495 )
1496 if (curr_workspace not in stream) :
1497 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 )
1499 if (should_erase) :
1500 self._strip( rev = ["branch('%s')" % curr_workspace,] )
1501 else :
1502 self._commit( close_branch = True, message = "%s%sAborted %s '%s'." %
1503 (msg, self.msg_prefix, stream, curr_workspace.basename( stream ),) )
1504 self._update( stream.trunk( trace = True ) )
1505 self._unshelve()
1506
1507
1508
1509 def _action_promote( self, stream, *arg, **kwarg ) :
1510 """
1511 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.
1513
1514 @type stream: C{Stream}
1515 @param stream: Stream where the branch which you want to rebase is
1516 @type rev: C{str}
1517 @param rev: If provided, promote this revision instead of the head. The specified revision must be in the workspace
1518 branch.
1519 """
1520 rev = kwarg.pop( "rev", None )
1521 tag_name = kwarg.pop( "tag", None )
1522 message = kwarg.pop( "message", None )
1523 message = (message + "\n") if (message) else ""
1524 orig_workspace = self.curr_workspace
1525 has_shelved = False
1526
1527 if (orig_workspace not in stream) :
1528 raise AbortFlow( "Your workspace is '%s' branch, which is not in %s." % (orig_workspace, stream,),
1529 "To promote a %s branch, you must first update to it." % stream )
1530
1531 if (rev) :
1532 # Ensures `rev` is in workspace branch.
1533 promoted_branch = Branch( self.ui, self.repo, rev )
1534 promoted_rev = rev
1535 promoted_node = promoted_branch.ctx.node()
1536 if (promoted_branch != orig_workspace) :
1537 raise AbortFlow( "Revision %s is not in workspace branch." % rev )
1538 else :
1539 promoted_branch = orig_workspace
1540 promoted_rev = orig_workspace
1541 promoted_ctx = promoted_branch.ctx
1542 promoted_node = promoted_ctx.node()
1543 # `promoted_node' is `None' if the `promote_ctx' is an instance of `workingctx'.
1544 while (promoted_node is None) :
1545 promoted_ctx = promoted_ctx._parents[0]
1546 promoted_node = promoted_ctx.node()
1547
1548 if (arg[1:]) :
1549 if (not has_shelved) :
1550 self._shelve()
1551 has_shelved = True
1552 for dest in arg[1:] :
1553 self._update( dest )
1554 self._merge ( promoted_rev )
1555 self._commit( message = message + ("%sPromoted %s '%s' (%s) to '%s'." %
1556 (self.msg_prefix, stream, promoted_branch.basename( stream ),
1557 short( promoted_node ), dest,)), **kwarg )
1558 if (tag_name) :
1559 self._tag( tag_name, **kwarg )
1560 else :
1561 destin = [STREAM["master"],] if (STREAM["develop"] == stream) else stream.destin()
1562 for s in destin :
1563 if (s == stream) :
1564 continue
1565 trunk = s.trunk()
1566 if (trunk) :
1567 if (not has_shelved) :
1568 self._shelve()
1569 has_shelved = True
1570 self._update( trunk )
1571 self._merge ( promoted_rev )
1572 self._commit( message = message + ("%sPromoted %s '%s' (%s) to '%s'." %
1573 (self.msg_prefix, stream, promoted_branch.basename( stream ),
1574 short( promoted_node ), trunk,)), **kwarg )
1575 if (tag_name) :
1576 self._tag( tag_name, **kwarg )
1577 else :
1578 self._error( "Cannot determine promote destination." )
1579 return
1580 if (orig_workspace != self.curr_workspace) :
1581 self._update( orig_workspace )
1582 self._unshelve()
1583
1584
1585
1586 def _action_rebase( self, stream, *arg, **kwarg ) :
1587 """
1588 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.
1590
1591 @type stream: C{Stream}
1592 @param stream: Stream where the branch which you want to rebase is
1593 @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
1595 branch, otherwise it will trigger an error. If not provided, use the tip of the parent branch as the
1596 destination of rebasing.
1597 """
1598 dest = kwarg.get( "dest" )
1599 onstream = kwarg.pop( "onstream", False )
1600 if (onstream) :
1601 if (not dest) :
1602 dest = stream.source().trunk( trace = True )
1603 branches = stream.branches()
1604 if (stream == STREAM["develop"]) :
1605 branches.remove( stream.trunk() )
1606 elif (stream._trunk) :
1607 branches.append( stream.trunk() )
1608 self._check_rebase()
1609 self._shelve()
1610 for branch in branches :
1611 if (dest != branch) :
1612 self._rebase( base = branch, dest = dest, keepbranches = True )
1613 self._unshelve()
1614 else :
1615 curr_workspace = self.curr_workspace
1616 if (not dest) :
1617 dest = stream.trunk( trace = True )
1618 if (curr_workspace not in stream) :
1619 raise AbortFlow( "Your workspace is '%s' branch, which is not in %s." % (curr_workspace, stream,),
1620 "To rebase a %s branch, you must first update to it." % stream )
1621 if (curr_workspace.is_develop_trunk()) :
1622 raise AbortFlow( "You cannot rebase the <develop> trunk." )
1623 if (dest == curr_workspace) :
1624 self._warn( "No effects from rebasing a branch to itself" )
1625 else :
1626 self._check_rebase()
1627 self._shelve()
1628 self._rebase( base = curr_workspace, dest = dest, keepbranches = True )
1629 self._unshelve()
1630
1631
1632
1633 def _update_workspace( self, stream, branch, verbose = True ) :
1634 """
1635 Update the workspace to the given branch. Shelving and unshelving will be conducted automatically.
1636
1637 @type stream: C{Stream}
1638 @param stream: Stream where the branch which you are updating the workspace to is
1639 @type branch: C{Branch} or C{None}
1640 @param branch: Branch to update the workspace to. No effects if it is C{None}.
1641 """
1642 if (not branch) :
1643 return
1644
1645 if (branch == self.curr_workspace) :
1646 if (verbose) :
1647 self._print( "You are already in %s %s." % (stream, branch.basename( stream, should_quote = True ),) )
1648 else :
1649 self._print( "Update workspace to %s %s." % (stream, branch.basename( stream, should_quote = True ),) )
1650 self._shelve()
1651 self._update( branch )
1652 self._unshelve()
1653
1654
1655
1656 def _action_other( self, stream, *arg, **kwarg ) :
1657 """
1658 If the action is the name of a branch in the given stream, we will update workspace to that branch; otherwise, the
1659 action is considered as an error.
1660
1661 @type stream: C{Stream}
1662 @param stream: Stream where the branch that we will switch to is
1663 """
1664 try :
1665 name = arg[0]
1666 branch = stream.get_branch( name )
1667 if (branch.is_closed()) :
1668 self._warn( "%s '%s' has been closed." % (stream, name,) )
1669 self._update_workspace( stream, branch )
1670 except error.RepoLookupError :
1671 misspelling = difflib.get_close_matches( name, ["start", "finish", "push", "publish", "pull",
1672 "list", "log", "abort", "promote", "rebase",], 3, 0.7 )
1673 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
1675 note = ("If you meant to create a new branch called '%s' in %s" % (name, stream,),
1676 "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 )
1678
1679
1680
1681 def _commit_change( self, opt, commit_hint, is_erasing = False ) :
1682 """
1683 Commit the changes in the workspace.
1684 Note that this method can potentially mutate C{opt}. Specifically, it will delete the C{commit} and C{message} keys if
1685 they present in C{opt}.
1686
1687 @type opt: C{dict}
1688 @param opt: Option dictionary. Recognizable keys are C{commit} and C{message}. The value of C{commit} should be a
1689 boolean, indicating whether or not to perform committing. The value of C{message} should be a string, which
1690 will be used as the commit message. It is OK for both of the options to be missing. But it would trigger
1691 an error if C{message} is given without C{commit} set to true. There is no special treatment on other
1692 keys, and they will be passed to the C{hg commit} command as is.
1693
1694 @rtype : C{bool}
1695 @return: Return `True' if committing was successfully done, or `False' if it was not.
1696 """
1697 if (opt.get( "commit" )) :
1698 del opt["commit"]
1699 msg = opt.get( "message" )
1700 if (msg is None) :
1701 opt["force_editor"] = True
1702 opt["message"] = "\n\nHG: flow: %s" % commit_hint
1703 self._commit( **opt )
1704 del opt["message"]
1705 if (msg is None) :
1706 del opt["force_editor"]
1707 return True
1708 elif (opt.get( "message" )) :
1709 if (is_erasing) :
1710 del opt["message"]
1711 else :
1712 raise AbortFlow( "Cannot use the specified commit message.", "Did you forget to specify the -c option?" )
1713 return False
1714
1715
1716
1717 def _action_finish( self, stream, *arg, **kwarg ) :
1718 """
1719 Finish a branch in the given stream. The current workspace must be in the branch to be finished, otherwise an error
1720 will be triggered. The default behavior of finish action is the following:
1721 1. close the branch.
1722 2. merge the branch to the C{destin} streams.
1723
1724 @type stream: C{Stream}
1725 @param stream: Stream where the branch that we will finish is
1726 """
1727 try :
1728 tag_name = arg[1]
1729 self._warn( "You just specified the <tag-name> using the deprecated syntax:" )
1730 self._warn( " hg flow <stream> finish <tag-name> [<options>]" )
1731 self._warn( "Try using the new syntax to do that in the future: hg flow <stream> finish -t <tag-name>" )
1732 self._warn( "Note that hgflow intentionally forbids finishing a non-workspace branch." )
1733 except IndexError :
1734 tag_name = None
1735
1736 message = kwarg.get( "message", None )
1737 tag_name = kwarg.pop( "tag", tag_name )
1738 onstream = kwarg.pop( "onstream", False )
1739 should_erase = kwarg.pop( "erase", False )
1740 curr_workspace = self.curr_workspace
1741 curr_stream = curr_workspace.stream()
1742 name = curr_workspace.basename( stream, should_quote = True )
1743 tag_name = tag_name if (tag_name) else (self.version_prefix + name[1:-1])
1744 develop_stream = STREAM["develop"]
1745
1746 if (should_erase) :
1747 if (onstream ) : raise AbortFlow( "'--erase' cannot be used together with '--onstream'." )
1748 if (message is None) : raise AbortFlow( "'--message' is required when '--erase' is used." )
1749 self._check_strip()
1750
1751 if (onstream) :
1752 if (stream in [develop_stream, STREAM["support"], STREAM["hotfix"], STREAM["release"],]) :
1753 raise AbortFlow( "You cannot finish %s." % stream )
1754 branches = stream.branches()
1755 if (stream._trunk) :
1756 substream = Stream.gen( self.ui, self.repo, stream.name() )
1757 for branch in branches :
1758 self._update( branch )
1759 self._action_finish( substream, *arg, **kwarg )
1760 self._update( stream.trunk() )
1761 self._action_finish( stream, *arg, **kwarg )
1762 else :
1763 for branch in branches :
1764 self._update( branch )
1765 self._action_finish( stream, *arg, **kwarg )
1766 return
1767
1768 if (curr_workspace.is_develop_trunk()) :
1769 raise AbortFlow( "You cannot finish the <develop> trunk." )
1770 elif (curr_workspace not in stream) :
1771 raise AbortFlow( "Your workspace is '%s' branch, which is not in %s." % (curr_workspace, stream,),
1772 "To finish a %s branch, you must first update to it." % stream )
1773
1774 # Merges the workspace to its `destin` streams.
1775 destin_with_trunk = []
1776 destin_without_trunk = []
1777 final_branch = None
1778 for s in stream.destin() :
1779 trunk = s.trunk()
1780 if (trunk == curr_workspace) :
1781 pass
1782 elif (trunk) :
1783 destin_with_trunk.append( s )
1784 else :
1785 destin_without_trunk.append( s )
1786
1787 if (should_erase) :
1788 if (len( destin_with_trunk + destin_without_trunk ) > 1) :
1789 raise AbortFlow( "'--erase' cannot be applied to branches with multiple merge destinations." )
1790
1791 # Commits changes (if any) in the current branch.
1792 is_commit_done = self._commit_change( kwarg, "Finishing '%s' branch" % curr_workspace, should_erase )
1793
1794 # If the commit was done successfully, we don't check against uncommitted changes.
1795 # This is particularly needed for dry run.
1796 if (not is_commit_done and self._has_uncommitted_changes()) :
1797 raise AbortFlow( "Cannot finish '%s' branch because it has uncommitted changes." % curr_workspace )
1798
1799 # 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
1801 # the current branch. The current branch will be closed. Note that there is no need to merge the current branch because
1802 # a new branch has been created from it.
1803 for s in destin_without_trunk :
1804 trunk = s.trunk()
1805 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() )
1807 so = "%s:%s" % (so.name(), s.name(),)
1808 basename = curr_workspace.basename()
1809 self.action( so, "start", basename )
1810 final_branch = s.get_fullname( basename )
1811
1812 if (destin_with_trunk or destin_without_trunk) :
1813 # If either list is not empty.
1814 self._update( curr_workspace )
1815 self._commit( close_branch = True, message = "%sClosed %s %s." % (self.msg_prefix, stream, name,), **kwarg )
1816 else :
1817 # If both lists are empty.
1818 if (stream == STREAM["support"]) :
1819 self._update( curr_workspace )
1820 self._commit( close_branch = True, message = "%sClosed %s %s." % (self.msg_prefix, stream, name,), **kwarg )
1821 final_branch = STREAM["master"].trunk()
1822 else :
1823 self._print( "All open branches in %s are finished and merged to its trunk." % stream )
1824 for s in destin_with_trunk :
1825 trunk = s.trunk()
1826 self._update( trunk )
1827 self._merge ( curr_workspace )
1828 self._commit( message = "%sMerged %s %s to %s ('%s')." % (self.msg_prefix, stream, name, s, trunk,), **kwarg )
1829 if (s == STREAM["master"]) :
1830 self._tag( tag_name )
1831 elif (s in develop_stream and s is not develop_stream) :
1832 tr_stream = trunk.stream()
1833 for ss in tr_stream.destin() :
1834 if (ss == develop_stream) :
1835 dvtrunk = develop_stream.trunk()
1836 tr_name = trunk.basename( ss )
1837 self._update( dvtrunk )
1838 self._merge ( trunk )
1839 self._commit( message = "%sMerged <develop/%s:%s> %s to %s ('%s')." %
1840 (self.msg_prefix, tr_name, stream.name(), name, ss, dvtrunk,), **kwarg )
1841 if (final_branch) :
1842 self._update( final_branch )
1843 if (should_erase) :
1844 rev = "p1(.)"
1845 rev = mercurial.scmutil.revsingle( self.repo, rev ).rev()
1846 self._update( "tip" )
1847 self._update( rev )
1848 self._revert( rev = "-1", all = True )
1849 self._strip ( rev = ["branch('%s')" % curr_workspace,] )
1850 self._commit( message = message, **kwarg )
1851 self._unshelve()
1852
1853
1854
1855 def _execute_action( self, stream, *arg, **kwarg ) :
1856 """
1857 Execute an action on the given stream. If no action is specified, the action will default to I{list}
1858 (see L{_action_list}). The default behavior of an action is defined by the C{_action_*} methods. Custom action behavior
1859 can be given through the C{action_func} parameter.
1860
1861 @type stream: C{Stream}
1862 @param stream: Stream where we will execute the action
1863 @type action_func: C{dict}
1864 @param action_func: Custom action methods. Key (C{str}) is action name, and value is a function that define the
1865 behavior of the custom action.
1866 """
1867 try :
1868 action = arg[0]
1869 except IndexError :
1870 action = "list"
1871
1872 action_func = {
1873 "start" : self._action_start,
1874 "finish" : self._action_finish,
1875 "push" : self._action_push,
1876 "publish" : self._action_push,
1877 "pull" : self._action_pull,
1878 "list" : self._action_list,
1879 "log" : self._action_log,
1880 "abort" : self._action_abort,
1881 "promote" : self._action_promote,
1882 "rebase" : self._action_rebase,
1883 "other" : self._action_other,
1884 }
1885
1886 custom_action_func = kwarg.pop( "action_func", {} )
1887 action_func.update( custom_action_func )
1888
1889 return action_func.get( action, self._action_other )( stream, *arg, **kwarg )
1890
1891
1892
1893 def action( self, stream, *arg, **kwarg ) :
1894 """
1895 Execute action on the stream.
1896 """
1897 if (len( arg ) > 0) :
1898 action = arg[0]
1899 if (stream == STREAM["master"]) :
1900 if (action in ["start", "finish", "abort", "rebase",]) :
1901 raise AbortFlow( "Invalid action for <master>" )
1902 else :
1903 self._update_workspace( stream, stream.trunk(), verbose = False )
1904 self._execute_action( stream, *arg, **kwarg )
1905
1906
1907
1908 def print_version( self, *arg, **kwarg ) :
1909 """
1910 Print flow's version and then quit.
1911 """
1912 self._print( "version %s" % VERSION )
1913
1914
1915
1916 def unshelve( self, *arg, **kwarg ) :
1917 """
1918 Unshelve the previously shelved changes.
1919 """
1920 self.autoshelve = True
1921 self._unshelve( *arg, **kwarg )
1922
1923
1924
1925 def print_open_branches( self, *arg, **kwarg ) :
1926 """
1927 Print open branches in each stream.
1928
1929 The currently active branch will be marked with a * symbol. Branches where there are shelved changes will be marked
1930 with a # symbol.
1931 """
1932 self._print( "Currently open branches:" )
1933 curr_workspace = self.curr_workspace
1934 stream_names = ["master", "develop", "feature", "release", "hotfix", "support",]
1935 all_branches = self._branches()
1936 for sn in stream_names :
1937 stream = STREAM[sn]
1938 trunk = stream.trunk()
1939 open_branches_in_stream = []
1940 remaining_branches = []
1941 for e in all_branches :
1942 if (e in stream) :
1943 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) :
1948 continue
1949 self._print( "%-9s: " % stream, newline = False )
1950 if (trunk) :
1951 marker = "#" if (self._is_shelved( trunk )) else ""
1952 marker += "*" if (trunk == curr_workspace ) else ""
1953 self.ui.write( "%s%s " % (trunk, marker,) )
1954 if (trunk in open_branches_in_stream) :
1955 # We need this check because the `trunk' could be closed. See Issue#34.
1956 open_branches_in_stream.remove( trunk )
1957 if (open_branches_in_stream) :
1958 for e in open_branches_in_stream :
1959 marker = "#" if (self._is_shelved( e )) else ""
1960 marker += "*" if (e == curr_workspace ) else ""
1961 self.ui.write( "%s%s " % (e, marker,) )
1962 self.ui.write( "\n" )
1963
1964
1965
1966 def init( self, *arg, **kwarg ) :
1967 """
1968 Initialize flow.
1969 """
1970 config_fname = os.path.join( self.repo.root, CONFIG_BASENAME )
1971 master_stream = "default"
1972 hotfix_stream = "hotfix/"
1973 develop_stream = "develop"
1974 feature_stream = "feature/"
1975 release_stream = "release/"
1976 support_stream = "support/"
1977 has_goodconfig = False
1978
1979 # Fetches existing condition
1980 if (os.path.isfile( config_fname )) :
1981 self._print( "Flow was already initialized for workspace:" )
1982 cfg = config.config()
1983 cfg.read( config_fname )
1984 SECTION = CONFIG_SECTION_BRANCHNAME
1985 try :
1986 master_stream = cfg.get( SECTION, "master" )
1987 develop_stream = cfg.get( SECTION, "develop" )
1988 feature_stream = cfg.get( SECTION, "feature" )
1989 release_stream = cfg.get( SECTION, "release" )
1990 hotfix_stream = cfg.get( SECTION, "hotfix" )
1991 support_stream = cfg.get( SECTION, "support" )
1992 has_goodconfig = True
1993 except ConfigParser.NoSectionError :
1994 self._error( "Section [%s] not found in configuration file: %s" % (SECTION, config_fname,) )
1995 self._error( "Your configuration file is probably in old format or corrupt." )
1996 except ConfigParser.NoOptionError, e :
1997 self._error( "%s" % e )
1998 self._error( "Your configuration file is probably corrupt." )
1999
2000 if (has_goodconfig) :
2001 self._print( "Repository-specific configuration:" )
2002 self._print( "<master> trunk: '%s'" % master_stream, prefix = " " )
2003 self._print( "<develop> trunk: '%s'" % develop_stream, prefix = " " )
2004 self._print( "<feature> branch prefix: '%s'" % feature_stream, prefix = " " )
2005 self._print( "<release> branch prefix: '%s'" % release_stream, prefix = " " )
2006 self._print( "<hotfix> branch prefix: '%s'" % hotfix_stream, prefix = " " )
2007 self._print( "<support> branch prefix: '%s'" % support_stream, prefix = " " )
2008
2009 autoshelve = None
2010 if (self.ui.has_section( "hgflow" ) or self.ui.has_section( "flow" )) :
2011 self._print( "Global configuration:" )
2012 autoshelve = self.ui.configbool( "hgflow", "autoshelve" )
2013 if (self.ui.has_section( "flow" )) :
2014 autoshelve = self.ui.configbool( "flow", "autoshelve" )
2015 if (not (autoshelve is None)) :
2016 self._print( "autoshelve: %s" % ("on" if (autoshelve) else "off"), prefix = " " )
2017
2018 # Shall we continue if there already exists a configuration file?
2019 if (has_goodconfig and not kwarg.get( "force" )) :
2020 return
2021
2022 print
2023 mq = None
2024 try :
2025 mq = extensions.find( "mq" )
2026 except KeyError :
2027 self._warn( "The 'mq' extension is deactivated. You cannot use some features of flow." )
2028 print
2029
2030 workspace = self.curr_workspace
2031 branches = self._branches()
2032 if (len( branches ) > 1) :
2033 self._warn( "You have the following open branches. Will initialize flow for all of them." )
2034 for branch in branches :
2035 if (branch == workspace) :
2036 self._warn( " " + branch.fullname() + " (active)" )
2037 else :
2038 self._warn( " %s" % branch.fullname() )
2039 print
2040
2041 # 'status' method returns a 7-member tuple:
2042 # 0 modified, 1 added, 2 removed, 3 deleted, 4 unknown(?), 5 ignored, and 6 clean
2043 orig_repo_status = self.repo.status()[:4]
2044 for e in orig_repo_status :
2045 try :
2046 e.remove( CONFIG_BASENAME )
2047 except ValueError :
2048 pass
2049
2050 if (any( orig_repo_status )) :
2051 if (len( branches ) > 1 and not mq) :
2052 raise AbortFlow( "Your workspace has uncommitted changes. Cannot initialize flow for all",
2053 " open branches. You can either commit the changes or install the 'mq'",
2054 " extension, and then try again." )
2055
2056 def get_input( stream_name, default ) :
2057 while (True) :
2058 answer = self.ui.prompt( "Branch name for %s stream: [%s]" % (stream_name, default,), default = default )
2059 if (answer.find( ':' ) > -1) :
2060 self._error( "Illegal symbol ':' in branch name" )
2061 else :
2062 return answer
2063
2064 if (not kwarg.get( "default" )) :
2065 master_stream = get_input( "master", master_stream )
2066 develop_stream = get_input( "develop", develop_stream )
2067 feature_stream = get_input( "feature", feature_stream )
2068 release_stream = get_input( "release", release_stream )
2069 hotfix_stream = get_input( "hotfix", hotfix_stream )
2070 support_stream = get_input( "support", support_stream )
2071
2072 if (autoshelve is None) :
2073 self._print( """
2074 When you switch to another branch, flow can automatically shelve uncommitted
2075 changes in workpace right before switching. Later when you switch back, flow can
2076 automatically unshelve the changes to the workspace. This functionality is
2077 called autoshelve. You need the 'mq' extension to use it.""" )
2078 answer = self.ui.prompt( "Do you want to turn it on? [Yes] ", default = "y" )
2079 answer = True if (answer.lower() in ["yes", "y", "",]) else False
2080 if (answer) :
2081 self._print( """
2082 Here is what you need to do:
2083 To turn it on for only this repository, edit your <repository-root>/.hg/hgrc
2084 file by adding the following lines:
2085 [flow]
2086 autoshelve = true
2087 You can turn it on for all of your repositories by doing the same edition to
2088 your $HOME/.hgrc file. To turn it off, just edit the corresponding file and
2089 replace 'true' with 'false'.
2090 """ )
2091 self.ui.prompt( _("Press Enter to continue initialization...") )
2092
2093 # Creates configuration.
2094 cfg_contents = ["[%s]" % CONFIG_SECTION_BRANCHNAME,
2095 "master = %s" % master_stream,
2096 "develop = %s" % develop_stream,
2097 "feature = %s" % feature_stream,
2098 "release = %s" % release_stream,
2099 "hotfix = %s" % hotfix_stream,
2100 "support = %s" % support_stream,]
2101 def write_config() :
2102 # Writes the configuration in the current branch.
2103 with open( config_fname, "w" ) as fh :
2104 print >> fh, "\n".join( cfg_contents )
2105 repo_status = self.repo.status( unknown = True )
2106 if (CONFIG_BASENAME in repo_status[0]) :
2107 self._commit( config_fname, message = "flow initialization: Modified configuration file." )
2108 elif (CONFIG_BASENAME in repo_status[4]) :
2109 self._add ( config_fname )
2110 self._commit( config_fname, message = "flow initialization: Added configuration file." )
2111
2112 write_config()
2113
2114 # Writes the configuration in all the other branches.
2115 self.autoshelve = True
2116 self._shelve()
2117
2118 if (len( branches ) > 1) :
2119 for branch in branches :
2120 if (branch == workspace) : continue
2121 self._update( branch )
2122 write_config()
2123 self._update( workspace )
2124
2125 # Creates 'master' and 'develop' streams if they don't yet exist.
2126 if (not self._find_branch( master_stream )) :
2127 self._create_branch( master_stream, "flow initialization: Created <master> trunk: %s." % master_stream )
2128 if (not self._find_branch( develop_stream )) :
2129 self._create_branch( develop_stream, "flow initialization: Created <develop> trunk: %s." % develop_stream )
2130 self._update( workspace )
2131 self._unshelve()
2132
2133
2134
2135 def upgrade( self, *arg, **kwarg ) :
2136 """
2137 Upgrade older version to the latest version.
2138 """
2139 self._print( "Upgrade flow's configuration file from v0.9.4 (or older) to v0.9.5 (or latter)." )
2140 self._print( "Renaming file '%s' to '%s' in all open branches..." % (OLD_CONFIG_BASENAME, CONFIG_BASENAME,) )
2141 config_fname = os.path.join( self.repo.root, CONFIG_BASENAME )
2142 old_config_fname = os.path.join( self.repo.root, OLD_CONFIG_BASENAME )
2143 workspace = self.curr_workspace
2144 for branch in self._branches() :
2145 self._print( " Branch '%s'..." % branch )
2146 self._update( branch )
2147 if (os.path.isfile( old_config_fname )) :
2148 self._rename( old_config_fname, config_fname, force = True )
2149 self._commit( message = "flow upgrade: Renamed flow's configuration file from '%s' to '%s'." %
2150 (OLD_CONFIG_BASENAME, CONFIG_BASENAME,) )
2151 self._update( workspace )
2152 self._print( "Upgrading done" )
2153
2154
2155
2156 def flow_cmd( ui, repo, cmd = None, *arg, **kwarg ) :
2157 """Flow is a Mercurial extension to support the generalized Driessen's branching model.
2158
2159 actions:
2160
2161 - start Open a new branch in the stream.
2162 - finish Close workspace branch and merge it to destination stream(s).
2163 - push Push workspace branch to the remote repository.
2164 - publish Same as `push`
2165 - pull Pull from the remote repository and update workspace branch.
2166 - list List all open branches in the stream.
2167 - log Show revision history of branch.
2168 - promote Merge workspace to other branches. (not closing any branches.)
2169 - rebase Rebase workspace branch to its parent branch.
2170 - abort Abort branch. Close branch without merging.
2171
2172 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
2174 switch the current workspace to the branch.
2175
2176 commands:
2177
2178 - init Initialize flow.
2179 - unshelve Unshelve the previously shelved changes for workspace branch.
2180 - upgrade Upgrade the configuration file to v0.9.5 or later.
2181 - help Show help for a specific topic. Example: `hg flow help @help`
2182 - version Show flow's version number.
2183 """
2184 # Supresses bookmarks, otherwise if the name of a bookmark happens to be the same as a named branch, hg will use the
2185 # bookmark's revision.
2186 repo._bookmarks = {}
2187
2188 flow = Flow( ui, repo, cmd in ["init", "upgrade", "help",] )
2189 func = {
2190 "init" : flow.init,
2191 "upgrade" : flow.upgrade,
2192 "unshelve" : flow.unshelve,
2193 "help" : Help( ui, repo ).print_help,
2194 "version" : flow.print_version,
2195 None : flow.print_open_branches,
2196 }
2197
2198 commands.use_quiet_channel( kwarg.get( "history" ) )
2199 commands.dryrun ( kwarg.get( "dry_run" ) )
2200
2201 if (kwarg.get( "dry_run" )) :
2202 _print( ui, "This is a dry run." )
2203 commands.use_quiet_channel( True )
2204
2205 # Registers common options (such as "user").
2206 common_opts = {}
2207 for e in ["user",] :
2208 v = kwarg.get( e )
2209 if (v) :
2210 common_opts[e] = v
2211 commands.reg_common_options( common_opts )
2212
2213 # - Up to this point, `cmd' is a name of command or stream, or `None'.
2214 # - We assign `stream' to be a stream name (or `None') and `cmd' to be a name of command or action.
2215 # - When `arg' is a 0-tuple, `cmd' should be "list" as the default action. We use `arg + ("list",)' to ensure we can get
2216 # the first element.
2217 stream, cmd = (None, cmd) if (cmd in func) else (cmd, (arg + ("list",))[0] )
2218
2219 try :
2220 # 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.
2222 if (isinstance( stream, str )) :
2223 source = None
2224 tokens = stream.split( ':' )
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.
2249 kwarg = _getopt( ui, cmd, kwarg )
2250 stamp = kwarg.pop( "stamp", None )
2251 if (stamp) :
2252 def stamp_commit_message( opts ) :
2253 msg = opts["message"]
2254 if (0 > msg.lower().find( stamp.lower() )) :
2255 msg += " %s" % stamp
2256 opts["message"] = msg
2257 return opts
2258 commands.reg_option_mutator( "commit", stamp_commit_message )
2259
2260 func = func.get( cmd, lambda *arg, **kwarg : flow.action( stream, *arg, **kwarg ) )
2261 func( *arg, **kwarg )
2262 except AbortFlow, e :
2263 errmsg = e.error_message()
2264 _error( ui, *errmsg )
2265 if (getattr( e, "note", None )) :
2266 _note( ui, *((e.note,) if (isinstance( e.note, str )) else e.note), via_quiet = True )
2267 if (ui.tracebackflag) :
2268 if (hasattr( e, "traceback" )) :
2269 ei = e.traceback
2270 sys.excepthook( ei[0], ei[1], ei[2] )
2271 print
2272 ei = sys.exc_info()
2273 sys.excepthook( ei[0], ei[1], ei[2] )
2274
2275 commands.print_history()
2276
2277 try :
2278 os.chdir( flow.orig_dir )
2279 except :
2280 _print( ui, "The original dir is gone in file system (probably due to updating branch)." )
2281 _print( ui, "You are now in the root dir of the repository." )
2282
2283
2284
2285 # On Windows, a topic should be wrapped with quotes.
2286 if ("nt" == os.name) :
2287 flow_cmd.__doc__ = flow_cmd.__doc__.replace( "help @help", 'help "@help"' )
2288
2289
2290
2291 class Help( object ) :
2292 """
2293 Online help system
2294 We define all help topics within this class.
2295 We support text effects on help message. See C{colortable} for predefined effects as C{flow.help.*}. To make it easy to use
2296 text effects, we invented a primitive markdown syntax. For now, we support only the C{flow.help.code}, which will be
2297 applied to text wrapped with '{{{' and '}}}'.
2298 """
2299
2300 SHORT_USAGE = """
2301 flow: a Mercurial workflow extension
2302
2303 Usage: {{{hg flow {<stream> [<action> [<arg>...]] | <command>} [<option>...]}}}
2304
2305 """ + flow_cmd.__doc__
2306
2307 TOPIC = {
2308 "@deprecated" : """
2309 The following item has been deprecated in this release and will be removed in
2310 the future:
2311 * [hgflow] The '[hgflow]' section name in hg's configuration file has been
2312 renamed to '[flow]'.
2313 """,
2314
2315 "@examples" : """
2316 {{{> hg flow}}}
2317 flow: Currently open branches:
2318 flow: <master> : default
2319 flow: <develop>: develop develop/0.9#
2320 flow: <feature>: feature/help*
2321 # Show open branches in all streams. The '*' marker indicates the branch which
2322 # the workspace is in, and the '#' marker indicates there are shelved changes
2323 # in the branch.
2324
2325 {{{> hg flow feature finish --history}}}
2326 # Finish the current <feature> branch, and print the history of primitive hg
2327 # commands used by the workflow.
2328
2329 {{{> hg flow develop/0.9:feature start new_v0.9_feature}}}
2330 # Start a new feature branch from the 'develop/0.9' branch.
2331
2332 {{{> hg flow develop/0.9:feature finish --verbose}}}
2333 flow: note: Hg command history:
2334 flow: note: hg commit --message "flow: Closed <feature> 'help'." --close-branch
2335 flow: note: hg update develop/0.9
2336 flow: note: hg merge feature/help
2337 flow: note: hg commit --message "flow: Merged <feature> 'help' to <develop/0.9> ('develop/0.9')."
2338 flow: note: hg update develop
2339 flow: note: hg merge develop/0.9
2340 flow: note: hg commit --message "flow: Merged <develop/0.9:feature> 'help' to <develop> ('develop')."
2341 # Finish the workspace <feature> branch, merging it to 'develop/0.9', which is
2342 # in turn merged to <develop>'s trunk.
2343 """,
2344
2345 "@master" : """
2346 Master stream contains 1 and only 1 branch that has only and all production
2347 revisions (i.e., official releases). New revisions in <master> are created when
2348 a <release> or <hotfix> branch merges into <master>.
2349 The following actions can be applied to <master>: push, publish, pull, list,
2350 and log.
2351 """,
2352
2353 "@develop" : """
2354 Develop stream contains all changes made for future releases. <release> and
2355 <feature> branches are started from <develop> and will be merged to <develop>
2356 when finished. Since version 0.9, user can create branches in <develop>. A
2357 <develop> branch can be used as the source branch to start <release> and
2358 <feature> branches.
2359 """,
2360
2361 "@feature" : """
2362 Feature stream contains branches where new features for future releases are
2363 developed. Branches in <feature> are created from either <develop> or an
2364 existing <feature> branch.
2365 All actions can be applied to <feature> branches. When a <feature> branch is
2366 finished, it will normally be merged into <develop>.
2367 """,
2368
2369 "@release" : """
2370 Release stream contains branches of release candidates. Code in <release> branch
2371 will usually be tested and bug-fixed. Once a <release> branch is graduated from
2372 the testing and bug-fixing process, it will be merged to both <master> and
2373 <develop>.
2374 """,
2375
2376 "@hotfix" : """
2377 Hotfix stream contains branches for fixing bugs in <master>. <hotfix> branches
2378 are started from <master> and once they are finished will be merged to both
2379 <master> and <develop>.
2380 """,
2381
2382 "@support" : """
2383 Support stream contains branches for supporting a previous release. <support>
2384 branches are started from <master> and will never be merged to anywhere. When
2385 finished, they will be simply closed.
2386 """,
2387
2388 "@start" : """
2389 Start a new branch in stream. <feature> and <release> branches are started from
2390 <develop>. <hotfix> and <support> branches are started from <master>.
2391
2392 syntax:
2393 {{{hg flow <stream> start <name> [<option>...]}}}
2394
2395 options:
2396 -r --rev REV Revision to start a new branch from.
2397 -m --message TEXT Record TEXT as commit message when opening new branch.
2398 -p --stamp TEXT Append TEXT to all commit messages.
2399 -d --date DATE Record the specified DATE as commit date.
2400 -u --user USER Use specified USER as committer.
2401 --dirty Start a new branch from current dirty workspace branch and
2402 move all uncommitted changes to the new branch.
2403
2404 The new branch is named after <stream-prefix>/<name>.
2405 """,
2406
2407 "@finish" : """
2408 Finishing a branch in stream means to close the branch and merge the branch to
2409 destination stream(s). <feature> branches will be merged to <develop>, and
2410 <release> and <hotfix> branches will be merged to both <develop> and <master>.
2411 <support> branches will not be merged to anywhere, and they will only be closed.
2412 Note that merging to a non-trunk <develop> branch will cause the <develop>
2413 branch to be merged into the <develop> trunk.
2414
2415 syntax:
2416 {{{hg flow <stream> finish [<option>...]}}}
2417
2418 The workspace branch will be finished. Hgflow intentionally forbids finishing
2419 a branch other than the workspace one, which forces user to update to and
2420 check the branch before finishing it.
2421
2422 The workspace branch must be in the specified <stream>. When the workspace
2423 branch is merged into <master>, a new tag will be added to the corresponding
2424 snapshot in the <master> trunk. User can use the '-t' option to specify the tag
2425 name; if not specified, the tag name will be derived automatically from the
2426 name of the workspace branch by replacing the stream prefix with the
2427 `version_prefix`. The '-t' option has no effect if the workspace branch is not
2428 merged into <master>.
2429
2430 options:
2431 -c --commit Commit changes before closing the branch.
2432 -m --message TEXT Record TEXT as commit message.
2433 -p --stamp TEXT Append TEXT to all commit messages.
2434 -t --tag NAME Tag the snapshot in the <master> trunk with NAME.
2435 -d --date DATE Record the specified DATE as commit date.
2436 -u --user USER Use specified USER as committer.
2437 -e --erase Erase branch after it is merged successfully.
2438
2439 N.B.: RE. '--erase': A branch cannot be erased if it has been previously merged
2440 to other branches, creating nodes that are not erased together with the branch.
2441 """,
2442
2443 "@push" : """
2444 Push the workspace branch to the remote repository.
2445
2446 syntax:
2447 {{{hg flow <stream> push}}}
2448
2449 alternative syntax:
2450 {{{hg flow <stream> publish}}}
2451 The two syntaxes are completely equivalent.
2452
2453 The workspace branch must be in <stream>.
2454 """,
2455
2456 "@publish" : """
2457 Push the workspace branch to the remote repository.
2458
2459 syntax:
2460 {{{hg flow <stream> publish}}}
2461
2462 alternative syntax:
2463 {{{hg flow <stream> push}}}
2464 The two syntaxes are completely equivalent.
2465
2466 The workspace branch must be in <stream>.
2467 """,
2468
2469 "@pull" : """
2470 Pull a branch named after <stream-prefix>/<name> from the remote repository and
2471 update the workspace. If <name> is not specified, it defaults to the workspace
2472 branch.
2473
2474 syntax:
2475 {{{hg flow <stream> pull [<name>]}}}
2476
2477 The pulled branch must be in <stream>.
2478 """,
2479
2480 "@list" : """
2481 List all open branches in <stream>.
2482
2483 syntax:
2484 {{{hg flow <stream> list}}}
2485
2486 alternative syntax:
2487 {{{hg flow <stream>}}}
2488 If <stream> has trunk (e.g., <develop> and <master>), this syntax will update
2489 the workspace to the trunk besides listing all open branches in <stream>. If
2490 <stream> does not have trunk (e.g., <feature>, <release>, <hotfix>, and
2491 <support>), this syntax is completely equivalent to the other one (i.e., only
2492 list all open branches in the stream).
2493
2494 option:
2495 -c --closed Show open and closed branches in <stream>.
2496
2497 example:
2498 {{{> hg flow hotfix list}}}
2499 flow: Open <hotfix> branches:
2500 flow: hotfix/0.9.6#
2501 flow: hotfix/0.9.6/init_-d_option*
2502 # List all currently open branches in <hotfix>. The '*' marker indicates the
2503 # branch which the workspace is in, and the '#' marker indicates that there are
2504 # shelved changes for the branch.
2505 """,
2506
2507 "@log" : """
2508 Show revision history of the specified branch, which must be in <stream>.
2509 syntax:
2510 {{{hg flow <stream> log [<basename>]}}}
2511 where <basename> is of the branch name, e.g., if a branch's name is
2512 'feature/colored_help', its basename relative to <feature> (assuming the
2513 branch name prefix is 'feature/') is 'colored_help'.
2514 If <basename> is missing, it will default to the workspace branch.
2515
2516 Show revision history of a single file in the workspace branch.
2517 syntax:
2518 {{{hg flow <stream> log <filename>}}}
2519 If <filename> happens to be the same as the basename of a branch in <stream>,
2520 it will be recognized as the basename.
2521
2522 alternative syntax:
2523 {{{hg flow <stream> log -F <filename>}}}
2524 Use this syntax to avoid the potential ambiguity with the prior syntax. Also,
2525 you can specify multiple file names to show revision history of these files.
2526
2527 Show revision history of specified files in a designated branch.
2528 syntax:
2529 {{{hg flow <stream> log <basename> -F <filename>}}}
2530
2531 options:
2532 -F --file FILE [+] File to show history of.
2533 -d --date DATE Show revisions matching date spec.
2534 -u --user USER Show revisions committed by USER.
2535 -k --keyword TEXT Do case-insensitive search for a given text.
2536 -p --patch Show patch.
2537 -g --git Use git extended diff format to show patch.
2538 -l --limit VALUE Limit number of changesets displayed.
2539 -c --closed Show closed branches when used together with -s option.
2540
2541 [+] marked option can be specified multiple times.
2542 """,
2543
2544 "@abort" : """
2545 Aborting the workspace branch can be done in two ways:
2546 1. The default way is simply marking the branch as closed so that it will not
2547 show up when you list alive branches, but all changesets in the branch
2548 remain in the repository and you cannot reuse the branch's name for a
2549 different branch.
2550 2. The other way is erasing the branch, in other words, completely deleting the
2551 branch and all changesets in it from the repository. This way is
2552 devastating, but you can clear unneeded changesets and reuse the branch's
2553 name. To abort a branch in this way, you just add the {{{-e}}} option.
2554 N.B.: A branch cannot be erased if you have previously merged it to other
2555 branches that remain in the repository.
2556
2557 syntax:
2558 {{{hg flow <stream> abort [-m <TEXT>] [-e]}}}
2559
2560 options:
2561 -m --message TEXT Record TEXT as commit message when closing branch.
2562 -p --stamp TEXT Append TEXT to all commit messages.
2563 -e --erase Abort branch and erase it.
2564 """,
2565
2566 "@promote" : """
2567 Merge the workspace branch to destination branches. The destination branches,
2568 if omitted, will default to the trunk of the destination stream. The destination
2569 streams of the basic streams are listed as follows:
2570
2571 stream destination
2572 ------------+-----------------------
2573 <feature> <develop>
2574 <develop> <master>
2575 <release> <develop> & <master>
2576 <hotfix> <develop> & <master>
2577 <master> n/a
2578 <support> n/a
2579 natural stream-trunk
2580
2581 syntax:
2582 {{{hg flow <stream> promote [<destination-branch-full-name>...] [<option>...]}}}
2583
2584 The workspace branch must be in <stream>. If the `-r` option is omitted, its
2585 value will default to the head of the workspace branch.
2586
2587 options:
2588 -r --rev REV Revision to promote to other branches.
2589 -m --message TEXT Record TEXT as commit message when promoting branch.
2590 -p --stamp TEXT Append TEXT to all commit messages.
2591 -t --tag NAME Tag the merging changeset with NAME
2592 -d --date DATE Record the specified DATE as commit date.
2593 -u --user USER Use specified USER as committer.
2594
2595 examples:
2596 {{{> hg flow develop promote -t v0.2.0}}}
2597 # Immediately release <develop> trunk's tip into <master>, bypassing <release>.
2598 # What this command exactly does is to promote the <develop> trunk into
2599 # <master> (<master> is <develop>'s default promotion destination, so you don't
2600 # have to spell it out in the command), and then label the <master> snapshot
2601 # with "v0.2.0".
2602 """,
2603
2604 "@rebase" : """
2605 Rebase the workspace branch to the specified revision.
2606
2607 syntax:
2608 {{{hg flow <stream> rebase [-d <rev>]}}}
2609
2610 option:
2611 -p --stamp TEXT Append TEXT to all commit messages.
2612
2613 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.
2615 """,
2616
2617 "@version" : """
2618 Show version of the flow extension.
2619
2620 syntax:
2621 {{{hg flow version}}}
2622 """,
2623
2624 "@init" : """
2625 Initialize the flow extension for the repository. The configuration file:
2626 {{{.hgflow}}} will be written in the root dir of the repository. The file will be
2627 tracked by hg. If you have multiple open branches, the file should be present
2628 and synchronized in all of them -- init command will do this for you
2629 automatically.
2630
2631 syntax:
2632 {{{hg flow init [<option>...]}}}
2633
2634 options:
2635 -f --force Force reinitializing flow.
2636 -u --user USER Use specified USER as committer.
2637 -p --stamp TEXT Append TEXT to all commit messages.
2638 """,
2639
2640 "@upgrade" : """
2641 Upgrade the configuration file from v0.9.4 (or older) to v0.9.5 or later.
2642
2643 syntax:
2644 {{{hg flow upgrade}}}
2645
2646 options:
2647 -u --user USER Use specified USER as committer.
2648 -p --stamp TEXT Append TEXT to all commit messages.
2649 """,
2650
2651 "@unshelve" : """
2652 Unshelve previously shelved changes by hgflow. Sometimes, unshelving is not
2653 automatically executed because workflow is terminated prematurelly. In such
2654 situations, you can always use the unshelve command to manually restore the
2655 shelved changes.
2656
2657 syntax:
2658 {{{hg flow unshelve}}}
2659 """,
2660
2661 "@terms" : """
2662 Concepts:
2663 - stream
2664 The entire set of branches of the same type. Stream is not branch, it is a
2665 set of branches. In general, a stream can contain any number (including zero)
2666 of branches. The master stream is, however, special in that it contains 1 and
2667 only 1 branch. The develop stream contains at least one branch.
2668
2669 - basic streams
2670 Refers to the master, develop, feature, release, hotfix, and support streams
2671 as predefined in Driessen's model.
2672
2673 - natural streams
2674 Refers to a set of branches that diverged from and will merge into the same
2675 branch. The set of branches plus the branch that they diverged from form a
2676 natural stream. The branch that all the other branches in the same natural
2677 stream diverged from and will merge into is the trunk of the natural stream.
2678
2679 - trunk
2680 Trunk is a special branch. A stream can optionally have a trunk, but only one
2681 trunk at most. For example, master and develop streams each has a trunk,
2682 whereas feature, release, hotfix, and support streams don't. And all natural
2683 streams each has a trunk. If a stream has a trunk, all branches in the stream
2684 normally should diverge from the trunk and later merge to the trunk when they
2685 are finished.
2686 Trunk is a relative concept. A trunk of a stream may be a regular branch of
2687 another stream. (The former stream will be called a substream of the latter.)
2688
2689 - source
2690 Source is an attribute of stream. The source of a stream refers to the stream
2691 where branches in the current stream are created from. A stream's source can
2692 be the stream itself. But this is not always the case, for example,
2693 the sources of release and feature streams are the develop stream.
2694
2695 - destin
2696 Destin is another attribute of stream. The destin of a stream refers to the
2697 stream(s) where branches in the current stream will merge to. A stream's
2698 destin can be the stream itself. But this is not always the case,
2699 for example, the destin of release is the develop and the master streams.
2700
2701 - fullname
2702 Branch name as recognized by the SCM, e.g., feature/enhance_log.
2703
2704 - basename
2705 Branch name recognized by flow, but not necessarily by SCM, e.g.,
2706 enhanced_log (with prefix 'feature/' dropped).
2707
2708 - flow action
2709 Refer to action on a specified stream, e.g., hg flow feature start, where
2710 'start' is an action.
2711
2712 - flow command
2713 Commands don't act on a stream, e.g., hg flow unshelve, where 'unshelve'
2714 is a command.
2715
2716 - hg command
2717 Refer to commands not from flow extension.
2718
2719 - workflow
2720 Refer to the process of executing a sequence of hg commands.
2721
2722 - history
2723 Refer to a sequence of executed hg commands.
2724
2725 Notations
2726 - <stream>
2727 Examples: <feature>, <hotfix>. These denote the corresponding streams. When
2728 you refer to a stream, e.g., feature stream, use '<feature>' (or more
2729 verbosely 'feature stream'), instead of '<feature> stream', because
2730 '<feature>' already means stream.
2731
2732 - <stream> branch
2733 Example: a <feature> branch. This phrase refers a branch in <feature>. Do not
2734 use 'a feature branch' to mean a branch in <feature> because the word
2735 'feature' there should take its usual meaning as in English, which doesn't
2736 necessarily mean the feature stream.
2737 """,
2738
2739 "@help" : """
2740 Show online help and then quit. An argument can be optionally given after the
2741 'help' command to specify a particular help topic. Detailed online help is
2742 available for the following topics:
2743 @all - Show detailed help for all supported topics.
2744 @<stream> - Show help about a particular stream, e.g., {{{@feature}}}, {{{@master}}}.
2745 @<action> - Show help about an action, e.g., {{{@finish,}}} {{{@log}}}.
2746 @<command> - Show help about a command, e.g., {{{@help}}}, {{{@unshelve}}}.
2747 @terms - Show explanations of terminologies used in hgflow.
2748 @examples - Show a few command examples.
2749 @deprecated - Show a list of deprecated features.%s""" % \
2750 ("\nOn Windows platform, a topic should be wrapped with quotes, e.g., {{{\"@finish\"}}}." if ("nt" == os.name) else ""),
2751 }
2752
2753 def __init__( self, ui, repo ) :
2754 self.ui = ui
2755 self.repo = repo
2756
2757
2758
2759 def _print( self, s ) :
2760 """
2761 Print text with predefined effects.
2762 @type s: C{str}
2763 @param s: String to be printed
2764 """
2765 import re
2766 code_pattern = re.compile( "{{{.*?}}}" )
2767 last_span = (0, 0,)
2768 for match in code_pattern.finditer( s ) :
2769 span = match.span()
2770 self.ui.write( s[last_span[1]:span[0]] )
2771 self.ui.write( s[span[0] + 3:span[1] - 3], label = "flow.help.code" )
2772 last_span = span
2773 self.ui.write( s[last_span[1]:] )
2774
2775
2776
2777 def print_help( self, topic = None, *arg, **opts ) :
2778 """
2779 Print help information.
2780
2781 @type topic : C{str} or C{None}
2782 @param topic : Help topic
2783 """
2784 if (topic is None) :
2785 self._print( self.SHORT_USAGE )
2786 elif (topic == "@all") :
2787 doc = self.TOPIC.items()
2788 doc.sort()
2789 for t, help in doc :
2790 self.ui.write( "%s" % t, label = "flow.help.topic" )
2791 self._print( "%s\n" % help )
2792 else :
2793 try :
2794 help_content = self.TOPIC[topic]
2795 self.ui.write( "%s" % topic, label = "flow.help.topic" )
2796 self._print( "%s\n" % help_content )
2797 except KeyError :
2798 _error( self.ui, "Unknown topic: %s" % topic )
2799 if (("@" + topic) in self.TOPIC or topic == "all") :
2800 _error( self.ui, "Did you mean '@%s'?" % topic )
2801 _print( self.ui, """Supported topics are the following:
2802 @all - Show detailed help for all supported topics.
2803 @<stream> - Show help about a particular stream, e.g., @feature, @master.
2804 @<action> - Show help about an action, e.g., @finish, @log.
2805 @<command> - Show help about a command, e.g., @help, @unshelve.
2806 @<option> - Show help about an option, e.g., @-F, @--history.
2807 @examples - Show a few command examples.
2808 @deprecated - Show a list of deprecated features.
2809 """ )
2810
2811
2812
2813 OPT_FILTER = {
2814 "init" : ("force", "user", "stamp", "default",),
2815 "upgrade" : ("user", "stamp",),
2816 "start" : ("rev", "message", "stamp", "date", "user", "dirty",),
2817 "finish" : ("commit", "message", "stamp", "tag", "date", "user", "erase", "onstream",),
2818 "list" : ("closed",),
2819 "log" : ("file", "date", "user", "keyword", "patch", "git", "limit", "graph", "closed", "onstream",),
2820 "abort" : ("erase", "message", "stamp", "onstream",),
2821 "promote" : ("rev", "message", "stamp", "tag", "date", "user", "onstream",),
2822 "rebase" : ("dest", "onstream", "stamp",),
2823 }
2824
2825 OPT_CONFLICT = {
2826 "dest" : ("-d", '', ), # (short-form-of-option, default-value,)
2827 "date" : ("-d", '', ),
2828 "default" : ("-d", False,),
2829 "closed" : ("-c", False,),
2830 "commit" : ("-c", False,),
2831 "stamp" : ("-p", '' ),
2832 "patch" : ("-p", False ),
2833 }
2834
2835 def _getopt( ui, key, opt ) :
2836 """
2837 Return user-specified options.
2838
2839 We cannot separate options for different subcommands because of the design of the C{cmdtable}. So ambiguity exists for some
2840 options. For example, the C{-d} option, it means C{dest} for C{rebase} and C{date} for C{finish}. For either of the two
2841 actions, the value of the C{-d} option could be saved in C{dest} or C{date}. In general, we don't know which one.
2842
2843 We have to do a bit of parsing to resolve potential ambiguity. This function is here for that purpose. C{opt} is the raw
2844 option C{dict} from C{hg}. We will reparse it a bit for a particular command or action given by C{key}. The function
2845 returns a C{dict} that contains the option's name and its value.
2846 N.B.:
2847 (1) If the value of an option evaluates to false, the option will be absent in the returned C{dict} object.
2848 (2) This function will mutate and return C{opt}.
2849
2850 @type ui: C{mercurial.ui}
2851 @param ui: Mercurial user interface object
2852 @type key: C{str}
2853 @param key: Command or action for which you are getting the options
2854 @type opt: C{dict}
2855 @param opt: Raw options
2856
2857 @raise AbortFlow: AbortFlow exception will be raised if there is option error.
2858 """
2859 ret = {}
2860 rec_short = [] # A list of recoginized short options
2861 for e in OPT_FILTER.get( key, [] ) :
2862 if (opt.get( e )) :
2863 ret[e] = opt[e]
2864 elif (e in OPT_CONFLICT) :
2865 short_opt, default_value = OPT_CONFLICT[e]
2866 argv = sys.argv
2867 if (short_opt in argv) :
2868 rec_short.append( short_opt )
2869 if (isinstance( default_value, str )) :
2870 index = argv.index( short_opt )
2871 try :
2872 ret[e] = argv[index + 1]
2873 except IndexError :
2874 raise AbortFlow( "Value not found for %s option." % short_opt )
2875 else :
2876 ret[e] = not default_value
2877
2878 bad_opt = [e for e in opt if (e not in (["history", "dry_run"] + ret.keys()) and opt[e])]
2879 bad_opt = [e for e in bad_opt if (e in sys.argv) or (OPT_CONFLICT.get( e, [0,] )[0] not in rec_short)]
2880
2881 if (bad_opt) :
2882 bad_opt = [e.replace( "_", "-" ) for e in bad_opt]
2883 if (key is None) :
2884 raise AbortFlow( "Unrecognized option%s for `hg flow`: %s." %
2885 ("" if (len( bad_opt ) == 1) else "s", "--" + (", --".join( bad_opt )),),
2886 note = "`hg flow` should take no options." )
2887 else :
2888 raise AbortFlow( "Unrecognized option%s for `%s`: %s." %
2889 ("" 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,) )
2891
2892 return ret
2893
2894
2895
2896 cmdtable = {
2897 "flow" :
2898 (flow_cmd,
2899 [("", "history", False, _("Print history of hg commands used in this workflow."), ),
2900 ("", "dry-run", None, _("Do not perform actions, just print history."), ),
2901 ("", "dirty", False, _("Start a new branch from a dirty workspace, and move all"
2902 " uncommitted changes to the new branch. [start]"), ),
2903 ("c", "closed", False, _("Show normal and closed branches in stream. [list, log]"), ),
2904 ("c", "commit", False, _("Commit changes before closing the branch. [finish]"), ),
2905 ("d", "default", False, _("Initialize flow with default configuration. [init]"), ),
2906 ("d", "date", '', _("Record the specified date as commit date. [start, finish, promote]"), _('DATE'),),
2907 ("d", "date", '', _("Show revisions matching date spec. [log]"), _('DATE'),),
2908 ("d", "dest", '', _("Destination changeset of rebasing. [rebase]"), _('REV' ),),
2909 ("e", "erase", False, _("Erase branch after it is merged or aborted successfully. [finish, abort]"), ),
2910 ("F", "file", [], _("File to show history of. [log]"), _('FILE'),),
2911 ("f", "force", False, _("Force reinitializing flow. [init]"), ),
2912 ("g", "git", False, _("Use git extended diff format to show patch. [log]"), ),
2913 ("k", "keyword", '', _("Do case-insensitive search for a given text. [log]"), _('TEXT'),),
2914 ("l", "limit", '', _("Limit number of changesets displayed. [log]"), ),
2915 ("m", "message", '', _("Record TEXT as commit message. [start, finish, promote, abort]"), _('TEXT'),),
2916 ("p", "stamp", '', _("Append TEXT to all commit messages. [init, upgrade, start, finish,"
2917 " promote, rebase, abort]"), _('TEXT'),),
2918 ("p", "patch", False, _("Show patch. [log]"), ),
2919 ("r", "rev", '', _("Revision to start a new branch from. [start]"), _('REV'), ),
2920 ("r", "rev", '', _("Revision to promote to other branches. [promote]"), _('REV'), ),
2921 ("s", "onstream", False, _("Act on stream. [finish, rebase, log, abort]"), ),
2922 ("t", "tag", '', _("Tag the merging changeset with NAME. [promote]"), _('NAME'),),
2923 ("t", "tag", '', _("Tag the <master> trunk with NAME after merging. [finish]"), _('NAME'),),
2924 ("u", "user", '', _("Use specified user as committer. [init, upgrade, start, finish,"
2925 " promote]"), _('USER'),),
2926 ("u", "user", '', _("Show revisions committed by specified user. [log]"), _('USER'),),
2927 ],
2928 "hg flow {<stream> [<action> [<arg>]] | <command>} [<option>...]",
2929 ),
2930 }

mercurial