Have 'stg branch --create' record parent information.
[stgit] / stgit / commands / branch.py
index 8420ec6..f074d47 100644 (file)
@@ -18,28 +18,36 @@ 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 import stack, git, basedir
 
 
 help = 'manage development branches'
 usage = """%prog [options] branch-name [commit-id]
 
 
 
 help = 'manage development branches'
 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."""
 
 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('--convert',
+                       help = 'switch between old and new format branches',
+                       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'),
@@ -60,62 +68,48 @@ options = [make_option('-c', '--create',
                        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_branch() == branch_name
 
 
-def print_branch(branch_name):
+def __print_branch(branch_name, length):
     initialized = ' '
     current = ' '
     protected = ' '
 
     branch = stack.Series(branch_name)
 
     initialized = ' '
     current = ' '
     protected = ' '
 
     branch = stack.Series(branch_name)
 
-    if os.path.isdir(os.path.join(git.base_dir, 'patches', branch_name)):
+    if branch.is_initialised():
         initialized = 's'
         initialized = 's'
-    if is_current_branch(branch_name):
+    if __is_current_branch(branch_name):
         current = '>'
     if branch.get_protected():
         protected = 'p'
         current = '>'
     if branch.get_protected():
         protected = 'p'
-    print '%s %s%s\t%s\t%s' % (current, initialized, protected, branch_name, \
-                               branch.get_description())
+    print current + ' ' + initialized + protected + '\t' + \
+          branch_name.ljust(length) + '  | ' + branch.get_description()
 
 
-def delete_branch(doomed_name, force = False):
+def __delete_branch(doomed_name, force = False):
     doomed = stack.Series(doomed_name)
 
     if doomed.get_protected():
         raise CmdException, 'This branch is protected. Delete is not permitted'
 
     doomed = stack.Series(doomed_name)
 
     if doomed.get_protected():
         raise CmdException, 'This branch is protected. Delete is not permitted'
 
-    if is_current_branch(doomed_name) and doomed_name != 'master':
-        git.switch_branch('master')
+    print 'Deleting branch "%s"...' % doomed_name,
+    sys.stdout.flush()
+
+    if __is_current_branch(doomed_name):
+        check_local_changes()
+        check_conflicts()
+        check_head_top_equal()
+
+        if doomed_name != 'master':
+            git.switch_branch('master')
 
     doomed.delete(force)
 
     if doomed_name != 'master':
         git.delete_branch(doomed_name)
 
 
     doomed.delete(force)
 
     if doomed_name != 'master':
         git.delete_branch(doomed_name)
 
-    print 'Branch "%s" has been deleted.' % doomed_name
-
-def rename_branch(from_name, to_name):
-    if from_name == 'master':
-        raise CmdException, 'Renaming the master branch is not allowed'
-
-    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
-
-    git.rename_branch(from_name, to_name)
-
-    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)
-
-    print 'Renamed branch "%s" as "%s".' % (from_name, to_name)
+    print 'done'
 
 def func(parser, options, args):
 
 
 def func(parser, options, args):
 
@@ -123,21 +117,85 @@ def func(parser, options, args):
 
         if len(args) == 0 or len(args) > 2:
             parser.error('incorrect number of arguments')
 
         if len(args) == 0 or len(args) > 2:
             parser.error('incorrect number of arguments')
+
+        check_local_changes()
+        check_conflicts()
+        check_head_top_equal()
+
         tree_id = None
         tree_id = None
-        if len(args) == 2:
-            tree_id = args[1]
+        if len(args) >= 2:
+            try:
+                if git.rev_parse(args[1]) == git.rev_parse('refs/heads/' + args[1]):
+                    # we are for sure refering to a branch
+                    parentbranch = 'refs/heads/' + args[1]
+                    print 'Recording "%s" as parent branch.' % parentbranch
+                elif git.rev_parse(args[1]) and re.search('/', args[1]):
+                    # FIXME: should the test be more strict ?
+                    parentbranch = args[1]
+                else:
+                    # Note: this includes refs to StGIT patches
+                    print 'Don\'t know how to determine parent branch from "%s".' % args[1]
+                    parentbranch = None
+            except git.GitException:
+                # should use a more specific exception to catch only non-git refs ?
+                print 'Don\'t know how to determine parent branch from "%s".' % args[1]
+                parentbranch = None
+
+            tree_id = git_id(args[1])
+        else:
+            # branch stack off current branch
+            parentbranch = git.get_head_file()
+
+        if parentbranch:
+            parentremote = git.identify_remote(parentbranch)
+            if parentremote:
+                print 'Using "%s" remote to pull parent from.' % parentremote
+            else:
+                print 'Not identified a remote to pull parent from.'
+        else:
+            parentremote = None
 
 
-        git.create_branch(args[0], tree_id)
-        stack.Series(args[0]).init()
+        stack.Series(args[0]).init(create_at = tree_id,
+                                   parent_remote = parentremote,
+                                   parent_branch = parentbranch)
 
         print 'Branch "%s" created.' % args[0]
         return
 
 
         print 'Branch "%s" created.' % args[0]
         return
 
+    elif options.clone:
+
+        if len(args) == 0:
+            clone = crt_series.get_branch() + \
+                    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()
+
+        print 'Cloning current branch to "%s"...' % clone,
+        sys.stdout.flush()
+        crt_series.clone(clone)
+        print 'done'
+
+        return
+
+    elif options.convert:
+
+        if len(args) != 0:
+            parser.error('incorrect number of arguments')
+
+        crt_series.convert()
+        return
+
     elif options.delete:
 
         if len(args) != 1:
             parser.error('incorrect number of arguments')
     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:
@@ -145,30 +203,38 @@ 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 = []
+        basepath = os.path.join(basedir.get(), 'refs', 'heads')
+        for path, files, dirs in walk_tree(basepath):
+            branches += [os.path.join(path, f) for f in files]
         branches.sort()
 
         branches.sort()
 
-        print 'Available branches:'
-        for i in branches:
-            print_branch(i)
+        if branches:
+            print 'Available branches:'
+            max_len = max([len(i) for i in branches])
+            for i in branches:
+                __print_branch(i, max_len)
+        else:
+            print '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_branch()
         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
 
 
-        print 'Protecting branch "%s"...' % branch,
+        print 'Protecting branch "%s"...' % branch_name,
         sys.stdout.flush()
         sys.stdout.flush()
-        stack.Series(branch).protect()
+        branch.protect()
         print 'done'
 
         return
         print 'done'
 
         return
@@ -177,33 +243,46 @@ def func(parser, options, args):
 
         if len(args) != 2:
             parser.error('incorrect number of arguments')
 
         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])
+
+        print 'Renamed branch "%s" as "%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_branch()
         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
 
 
-        print 'Unprotecting branch "%s"...' % branch,
+        print 'Unprotecting branch "%s"...' % branch_name,
         sys.stdout.flush()
         sys.stdout.flush()
-        stack.Series(branch).unprotect()
+        branch.unprotect()
         print 'done'
 
         return
 
     elif len(args) == 1:
 
         print 'done'
 
         return
 
     elif len(args) == 1:
 
-        if args[0] == git.get_head_file():
-            raise CmdException, 'Branch "%s" is already the current branch' % args[0]
+        if __is_current_branch(args[0]):
+            raise CmdException, 'Branch "%s" is already the current branch' \
+                  % args[0]
+
+        check_local_changes()
+        check_conflicts()
+        check_head_top_equal()
 
         print 'Switching to branch "%s"...' % args[0],
         sys.stdout.flush()
 
         print 'Switching to branch "%s"...' % args[0],
         sys.stdout.flush()
@@ -217,4 +296,4 @@ 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')
 
-    print git.get_head_file()
+    print crt_series.get_branch()