chiark / gitweb /
Allow "branch --create" to be given a tag id
[stgit] / stgit / commands / branch.py
index ccf1f6baefafb0585a0ebfe0f1f8822b249f0dd6..a18ef2ac34960002fdca622c2d0d17098466aa27 100644 (file)
@@ -18,31 +18,40 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 """
 
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 """
 
-import sys, os
+import sys, os, time
 from optparse import OptionParser, make_option
 
 from stgit.commands.common import *
 from stgit.utils import *
 from optparse import OptionParser, make_option
 
 from stgit.commands.common import *
 from stgit.utils import *
-from stgit import stack, git
+from stgit.out import *
+from stgit import stack, git, basedir
 
 
 
 
-help = 'manage development branches'
+help = 'manage patch stacks'
 usage = """%prog [options] branch-name [commit-id]
 
 usage = """%prog [options] branch-name [commit-id]
 
-Create, list, switch between, rename, or delete development branches
+Create, clone, switch between, rename, or delete development branches
 within a git repository.  By default, a single branch called 'master'
 is always created in a new repository.  This subcommand allows you to
 within a git repository.  By default, a single branch called 'master'
 is always created in a new repository.  This subcommand allows you to
-manage several patch series in the same repository.
+manage several patch series in the same repository via GIT branches.
 
 When displaying the branches, the names can be prefixed with
 
 When displaying the branches, the names can be prefixed with
-'s' (StGIT managed) or 'p' (protected)."""
+'s' (StGIT managed) or 'p' (protected).
 
 
+If not given any options, switch to the named branch."""
+
+directory = DirectoryHasRepository()
 options = [make_option('-c', '--create',
                        help = 'create a new development branch',
                        action = 'store_true'),
 options = [make_option('-c', '--create',
                        help = 'create a new development branch',
                        action = 'store_true'),
+           make_option('--clone',
+                       help = 'clone the contents of the current branch',
+                       action = 'store_true'),
            make_option('--delete',
                        help = 'delete an existing development branch',
                        action = 'store_true'),
            make_option('--delete',
                        help = 'delete an existing development branch',
                        action = 'store_true'),
+           make_option('-d', '--description',
+                       help = 'set the branch description'),
            make_option('--force',
                        help = 'force a delete when the series is not empty',
                        action = 'store_true'),
            make_option('--force',
                        help = 'force a delete when the series is not empty',
                        action = 'store_true'),
@@ -50,18 +59,18 @@ options = [make_option('-c', '--create',
                        help = 'list branches contained in this repository',
                        action = 'store_true'),
            make_option('-p', '--protect',
                        help = 'list branches contained in this repository',
                        action = 'store_true'),
            make_option('-p', '--protect',
-                       help = 'prevent "stg pull" from modifying this branch',
+                       help = 'prevent StGIT from modifying this branch',
                        action = 'store_true'),
            make_option('-r', '--rename',
                        help = 'rename an existing development branch',
                        action = 'store_true'),
            make_option('-u', '--unprotect',
                        action = 'store_true'),
            make_option('-r', '--rename',
                        help = 'rename an existing development branch',
                        action = 'store_true'),
            make_option('-u', '--unprotect',
-                       help = 'allow "stg pull" to modify this branch',
+                       help = 'allow StGIT to modify this branch',
                        action = 'store_true')]
 
 
 def __is_current_branch(branch_name):
                        action = 'store_true')]
 
 
 def __is_current_branch(branch_name):
-    return crt_series.get_branch() == branch_name
+    return crt_series.get_name() == branch_name
 
 def __print_branch(branch_name, length):
     initialized = ' '
 
 def __print_branch(branch_name, length):
     initialized = ' '
@@ -76,8 +85,8 @@ def __print_branch(branch_name, length):
         current = '>'
     if branch.get_protected():
         protected = 'p'
         current = '>'
     if branch.get_protected():
         protected = 'p'
-    print current + ' ' + initialized + protected + '\t' + \
-          branch_name.ljust(length) + '  | ' + branch.get_description()
+    out.stdout(current + ' ' + initialized + protected + '\t'
+               + branch_name.ljust(length) + '  | ' + branch.get_description())
 
 def __delete_branch(doomed_name, force = False):
     doomed = stack.Series(doomed_name)
 
 def __delete_branch(doomed_name, force = False):
     doomed = stack.Series(doomed_name)
@@ -85,23 +94,14 @@ def __delete_branch(doomed_name, force = False):
     if doomed.get_protected():
         raise CmdException, 'This branch is protected. Delete is not permitted'
 
     if doomed.get_protected():
         raise CmdException, 'This branch is protected. Delete is not permitted'
 
-    print 'Deleting branch "%s"...' % doomed_name,
-    sys.stdout.flush()
+    out.start('Deleting branch "%s"' % doomed_name)
 
     if __is_current_branch(doomed_name):
 
     if __is_current_branch(doomed_name):
-        check_local_changes()
-        check_conflicts()
-        check_head_top_equal()
-
-        if doomed_name != 'master':
-            git.switch_branch('master')
+        raise CmdException('Cannot delete the current branch')
 
     doomed.delete(force)
 
 
     doomed.delete(force)
 
-    if doomed_name != 'master':
-        git.delete_branch(doomed_name)
-
-    print 'done'
+    out.done()
 
 def func(parser, options, args):
 
 
 def func(parser, options, args):
 
@@ -112,16 +112,82 @@ def func(parser, options, args):
 
         check_local_changes()
         check_conflicts()
 
         check_local_changes()
         check_conflicts()
-        check_head_top_equal()
+        check_head_top_equal(crt_series)
 
         tree_id = None
 
         tree_id = None
-        if len(args) == 2:
-            tree_id = args[1]
+        if len(args) >= 2:
+            parentbranch = None
+            try:
+                branchpoint = git.rev_parse(args[1])
+
+                # first, look for branchpoint in well-known branch namespaces
+                for namespace in ('refs/heads/', 'remotes/'):
+                    # check if branchpoint exists in namespace
+                    try:
+                        maybehead = git.rev_parse(namespace + args[1])
+                    except git.GitException:
+                        maybehead = None
+
+                    # check if git resolved branchpoint to this namespace
+                    if maybehead and branchpoint == maybehead:
+                        # we are for sure referring to a branch
+                        parentbranch = namespace + args[1]
+
+            except git.GitException:
+                # should use a more specific exception to catch only
+                # non-git refs ?
+                out.info('Don\'t know how to determine parent branch'
+                         ' from "%s"' % args[1])
+                # exception in branch = rev_parse() leaves branchpoint unbound
+                branchpoint = None
+
+            tree_id = git_id(crt_series, branchpoint or args[1])
+
+            if parentbranch:
+                out.info('Recording "%s" as parent branch' % parentbranch)
+            else:
+                out.info('Don\'t know how to determine parent branch'
+                         ' from "%s"' % args[1])                
+        else:
+            # branch stack off current branch
+            parentbranch = git.get_head_file()
+
+        if parentbranch:
+            parentremote = git.identify_remote(parentbranch)
+            if parentremote:
+                out.info('Using remote "%s" to pull parent from'
+                         % parentremote)
+            else:
+                out.info('Recording as a local branch')
+        else:
+            # no known parent branch, can't guess the remote
+            parentremote = None
+
+        stack.Series(args[0]).init(create_at = tree_id,
+                                   parent_remote = parentremote,
+                                   parent_branch = parentbranch)
+
+        out.info('Branch "%s" created' % args[0])
+        return
+
+    elif options.clone:
+
+        if len(args) == 0:
+            clone = crt_series.get_name() + \
+                    time.strftime('-%C%y%m%d-%H%M%S')
+        elif len(args) == 1:
+            clone = args[0]
+        else:
+            parser.error('incorrect number of arguments')
+
+        check_local_changes()
+        check_conflicts()
+        check_head_top_equal(crt_series)
 
 
-        git.create_branch(args[0], tree_id)
-        stack.Series(args[0]).init()
+        out.start('Cloning current branch to "%s"' % clone)
+        crt_series.clone(clone)
+        out.done()
 
 
-        print 'Branch "%s" created.' % args[0]
         return
 
     elif options.delete:
         return
 
     elif options.delete:
@@ -136,19 +202,22 @@ def func(parser, options, args):
         if len(args) != 0:
             parser.error('incorrect number of arguments')
 
         if len(args) != 0:
             parser.error('incorrect number of arguments')
 
-        branches = os.listdir(os.path.join(git.get_base_dir(), 'refs', 'heads'))
+        branches = git.get_heads()
         branches.sort()
         branches.sort()
-        max_len = max([len(i) for i in branches])
 
 
-        print 'Available branches:'
-        for i in branches:
-            __print_branch(i, max_len)
+        if branches:
+            out.info('Available branches:')
+            max_len = max([len(i) for i in branches])
+            for i in branches:
+                __print_branch(i, max_len)
+        else:
+            out.info('No branches')
         return
 
     elif options.protect:
 
         if len(args) == 0:
         return
 
     elif options.protect:
 
         if len(args) == 0:
-            branch_name = crt_series.get_branch()
+            branch_name = crt_series.get_name()
         elif len(args) == 1:
             branch_name = args[0]
         else:
         elif len(args) == 1:
             branch_name = args[0]
         else:
@@ -159,10 +228,9 @@ def func(parser, options, args):
             raise CmdException, 'Branch "%s" is not controlled by StGIT' \
                   % branch_name
 
             raise CmdException, 'Branch "%s" is not controlled by StGIT' \
                   % branch_name
 
-        print 'Protecting branch "%s"...' % branch_name,
-        sys.stdout.flush()
+        out.start('Protecting branch "%s"' % branch_name)
         branch.protect()
         branch.protect()
-        print 'done'
+        out.done()
 
         return
 
 
         return
 
@@ -176,14 +244,14 @@ def func(parser, options, args):
 
         stack.Series(args[0]).rename(args[1])
 
 
         stack.Series(args[0]).rename(args[1])
 
-        print 'Renamed branch "%s" as "%s".' % (args[0], args[1])
+        out.info('Renamed branch "%s" to "%s"' % (args[0], args[1]))
 
         return
 
     elif options.unprotect:
 
         if len(args) == 0:
 
         return
 
     elif options.unprotect:
 
         if len(args) == 0:
-            branch_name = crt_series.get_branch()
+            branch_name = crt_series.get_name()
         elif len(args) == 1:
             branch_name = args[0]
         else:
         elif len(args) == 1:
             branch_name = args[0]
         else:
@@ -194,10 +262,27 @@ def func(parser, options, args):
             raise CmdException, 'Branch "%s" is not controlled by StGIT' \
                   % branch_name
 
             raise CmdException, 'Branch "%s" is not controlled by StGIT' \
                   % branch_name
 
-        print 'Unprotecting branch "%s"...' % branch_name,
-        sys.stdout.flush()
+        out.info('Unprotecting branch "%s"' % branch_name)
         branch.unprotect()
         branch.unprotect()
-        print 'done'
+        out.done()
+
+        return
+
+    elif options.description is not None:
+
+        if len(args) == 0:
+            branch_name = crt_series.get_name()
+        elif len(args) == 1:
+            branch_name = args[0]
+        else:
+            parser.error('incorrect number of arguments')
+        branch = stack.Series(branch_name)
+
+        if not branch.is_initialised():
+            raise CmdException, 'Branch "%s" is not controlled by StGIT' \
+                  % branch_name
+
+        branch.set_description(options.description)
 
         return
 
 
         return
 
@@ -209,18 +294,15 @@ def func(parser, options, args):
 
         check_local_changes()
         check_conflicts()
 
         check_local_changes()
         check_conflicts()
-        check_head_top_equal()
-
-        print 'Switching to branch "%s"...' % args[0],
-        sys.stdout.flush()
+        check_head_top_equal(crt_series)
 
 
+        out.start('Switching to branch "%s"' % args[0])
         git.switch_branch(args[0])
         git.switch_branch(args[0])
-
-        print 'done'
+        out.done()
         return
 
     # default action: print the current branch
     if len(args) != 0:
         parser.error('incorrect number of arguments')
 
         return
 
     # default action: print the current branch
     if len(args) != 0:
         parser.error('incorrect number of arguments')
 
-    print crt_series.get_branch()
+    print crt_series.get_name()