chiark / gitweb /
Convert "export" to the new infrastructure
[stgit] / stgit / commands / branch.py
index 56a5fe608634d8af075658f4ffebabbc2698dbaf..50684bb7bc4332f0d578bb1f0e684d48254bcafb 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, re
 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 = DirectoryGotoToplevel()
 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,89 +59,135 @@ 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')]
 
 
                        action = 'store_true')]
 
 
-def is_current_branch(branch_name):
-    return git.get_head_file() == branch_name
+def __is_current_branch(branch_name):
+    return crt_series.get_name() == branch_name
 
 
-def print_branch(branch_name):
+def __print_branch(branch_name, length):
     initialized = ' '
     current = ' '
     protected = ' '
     initialized = ' '
     current = ' '
     protected = ' '
-    if os.path.isdir(os.path.join(git.base_dir, 'patches', branch_name)):
+
+    branch = stack.Series(branch_name)
+
+    if branch.is_initialised():
         initialized = 's'
         initialized = 's'
-    if is_current_branch(branch_name):
+    if __is_current_branch(branch_name):
         current = '>'
         current = '>'
-    if stack.Series(branch_name).get_protected():
+    if branch.get_protected():
         protected = 'p'
         protected = 'p'
-    print '%s %s%s\t%s\t%s' % (current, initialized, protected, branch_name, \
-                               stack.Series(branch_name).get_description())
-
-def delete_branch(doomed_name, force = False):
-    if stack.Series(doomed_name).get_protected():
-        raise CmdException, 'This branch is protected. Delete is not permitted'
+    out.stdout(current + ' ' + initialized + protected + '\t'
+               + branch_name.ljust(length) + '  | ' + branch.get_description())
 
 
-    if is_current_branch(doomed_name) and doomed_name != 'master':
-        git.switch_branch('master')
+def __delete_branch(doomed_name, force = False):
+    doomed = stack.Series(doomed_name)
 
 
-    stack.Series(doomed_name).delete(force)
+    if __is_current_branch(doomed_name):
+        raise CmdException('Cannot delete the current branch')
+    if doomed.get_protected():
+        raise CmdException, 'This branch is protected. Delete is not permitted'
 
 
-    if doomed_name != 'master':
-        git.delete_branch(doomed_name)
+    out.start('Deleting branch "%s"' % doomed_name)
+    doomed.delete(force)
+    out.done()
 
 
-    print 'Branch "%s" has been deleted.' % doomed_name
+def func(parser, options, args):
 
 
-def rename_branch(from_name, to_name):
-    if from_name == 'master':
-        raise CmdException, 'Renaming the master branch is not allowed'
+    if options.create:
 
 
-    to_patchdir = os.path.join(git.base_dir, 'patches', to_name)
-    if os.path.isdir(to_patchdir):
-        raise CmdException, '"%s" already exists' % to_patchdir
-    to_base = os.path.join(git.base_dir, 'refs', 'bases', to_name)
-    if os.path.isfile(to_base):
-        raise CmdException, '"%s" already exists' % to_base
+        if len(args) == 0 or len(args) > 2:
+            parser.error('incorrect number of arguments')
 
 
-    git.rename_branch(from_name, to_name)
+        check_local_changes()
+        check_conflicts()
+        check_head_top_equal(crt_series)
 
 
-    from_patchdir = os.path.join(git.base_dir, 'patches', from_name)
-    if os.path.isdir(from_patchdir):
-        os.rename(from_patchdir, to_patchdir)
-    from_base = os.path.join(git.base_dir, 'refs', 'bases', from_name)
-    if os.path.isfile(from_base):
-        os.rename(from_base, to_base)
+        tree_id = None
+        if len(args) >= 2:
+            parentbranch = None
+            try:
+                branchpoint = git.rev_parse(args[1])
+
+                # parent branch?
+                head_re = re.compile('refs/(heads|remotes)/')
+                ref_re = re.compile(args[1] + '$')
+                for ref in git.all_refs():
+                    if head_re.match(ref) and ref_re.search(ref):
+                        # args[1] is a valid ref from the branchpoint
+                        # setting above
+                        parentbranch = args[1]
+                        break;
+            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
 
 
-    print 'Renamed branch "%s" as "%s".' % (from_name, to_name)
+        stack.Series(args[0]).init(create_at = tree_id,
+                                   parent_remote = parentremote,
+                                   parent_branch = parentbranch)
 
 
-def func(parser, options, args):
+        out.info('Branch "%s" created' % args[0])
+        return
 
 
-    if options.create:
+    elif options.clone:
 
 
-        if len(args) == 0 or len(args) > 2:
+        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')
             parser.error('incorrect number of arguments')
-        tree_id = None
-        if len(args) == 2:
-            tree_id = args[1]
 
 
-        git.create_branch(args[0], tree_id)
-        stack.Series(args[0]).init()
+        check_local_changes()
+        check_conflicts()
+        check_head_top_equal(crt_series)
+
+        out.start('Cloning current branch to "%s"' % clone)
+        crt_series.clone(clone)
+        out.done()
 
 
-        print 'Branch "%s" created.' % args[0]
         return
 
     elif options.delete:
 
         if len(args) != 1:
             parser.error('incorrect number of arguments')
         return
 
     elif options.delete:
 
         if len(args) != 1:
             parser.error('incorrect number of arguments')
-        delete_branch(args[0], options.force)
+        __delete_branch(args[0], options.force)
         return
 
     elif options.list:
         return
 
     elif options.list:
@@ -140,67 +195,107 @@ 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.base_dir, 'refs', 'heads'))
+        branches = git.get_heads()
         branches.sort()
 
         branches.sort()
 
-        print 'Available branches:'
-        for i in branches:
-            print_branch(i)
+        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 = git.get_head_file()
+            branch_name = crt_series.get_name()
         elif len(args) == 1:
         elif len(args) == 1:
-            branch = args[0]
+            branch_name = args[0]
         else:
             parser.error('incorrect number of arguments')
         else:
             parser.error('incorrect number of arguments')
+        branch = stack.Series(branch_name)
 
 
-        base = os.path.join(git.base_dir, 'refs', 'bases', branch)
-        if not os.path.isfile(base):
-            raise CmdException, 'Branch "%s" is not controlled by StGit' % branch
+        if not branch.is_initialised():
+            raise CmdException, 'Branch "%s" is not controlled by StGIT' \
+                  % branch_name
+
+        out.start('Protecting branch "%s"' % branch_name)
+        branch.protect()
+        out.done()
 
 
-        print 'Protecting branch "%s"...' % branch
-        stack.Series(branch).protect()
         return
 
     elif options.rename:
 
         if len(args) != 2:
             parser.error('incorrect number of arguments')
         return
 
     elif options.rename:
 
         if len(args) != 2:
             parser.error('incorrect number of arguments')
-        rename_branch(args[0], args[1])
+
+        if __is_current_branch(args[0]):
+            raise CmdException, 'Renaming the current branch is not supported'
+
+        stack.Series(args[0]).rename(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 = git.get_head_file()
+            branch_name = crt_series.get_name()
         elif len(args) == 1:
         elif len(args) == 1:
-            branch = args[0]
+            branch_name = args[0]
         else:
             parser.error('incorrect number of arguments')
         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
 
 
-        base = os.path.join(git.base_dir, 'refs', 'bases', branch)
-        if not os.path.isfile(base):
-            raise CmdException, 'Branch "%s" is not controlled by StGit' % branch
+        out.info('Unprotecting branch "%s"' % branch_name)
+        branch.unprotect()
+        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)
 
 
-        print 'Unprotecting branch "%s"...' % branch
-        stack.Series(branch).unprotect()
         return
 
     elif len(args) == 1:
 
         return
 
     elif len(args) == 1:
 
-        print 'Switching to branch "%s"...' % args[0],
-        sys.stdout.flush()
+        if __is_current_branch(args[0]):
+            raise CmdException, 'Branch "%s" is already the current branch' \
+                  % args[0]
 
 
-        git.switch_branch(args[0])
+        check_local_changes()
+        check_conflicts()
+        check_head_top_equal(crt_series)
 
 
-        print 'done'
+        out.start('Switching to branch "%s"' % args[0])
+        git.switch_branch(args[0])
+        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 git.get_head_file()
+    print crt_series.get_name()