blob: c248ce5cc2eeb5500ff2857fba24dc50a53050b4 [file] [log] [blame]
__copyright__ = """
Copyright (C) 2005, Chuck Lever <cel@netapp.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
import sys, os, time, re
from stgit.argparse import opt
from stgit.commands.common import *
from stgit.utils import *
from stgit.out import *
from stgit import argparse, stack, git, basedir
from stgit.lib import log
help = 'Branch operations: switch, list, create, rename, delete, ...'
kind = 'stack'
usage = ['',
'[--merge] [--] <branch>',
'--list',
'--create [--] <new-branch> [<committish>]',
'--clone [--] [<new-branch>]',
'--rename [--] <old-name> <new-name>',
'--protect [--] [<branch>]',
'--unprotect [--] [<branch>]',
'--delete [--force] [--] <branch>',
'--cleanup [--force] [--] [<branch>]',
'--description=<description> [--] [<branch>]']
description = """
Create, clone, switch between, rename, or delete development branches
within a git repository.
'stg branch'::
Display the name of the current branch.
'stg branch' <branch>::
Switch to the given branch."""
args = [argparse.all_branches]
options = [
opt('-l', '--list', action = 'store_true',
short = 'List the branches contained in this repository', long = """
List each branch in the current repository, followed by its
branch description (if any). The current branch is prefixed
with '>'. Branches that have been initialized for StGit (with
linkstg:init[]) are prefixed with 's'. Protected branches are
prefixed with 'p'."""),
opt('-c', '--create', action = 'store_true',
short = 'Create (and switch to) a new branch', long = """
Create (and switch to) a new branch. The new branch is already
initialized as an StGit patch stack, so you do not have to run
linkstg:init[] manually. If you give a committish argument,
the new branch is based there; otherwise, it is based at the
current HEAD.
StGit will try to detect the branch off of which the new
branch is forked, as well as the remote repository from which
that parent branch is taken (if any), so that running
linkstg:pull[] will automatically pull new commits from the
correct branch. It will warn if it cannot guess the parent
branch (e.g. if you do not specify a branch name as
committish)."""),
opt('--clone', action = 'store_true',
short = 'Clone the contents of the current branch', long = """
Clone the current branch, under the name <new-branch> if
specified, or using the current branch's name plus a
timestamp.
The description of the new branch is set to tell it is a clone
of the current branch. The parent information of the new
branch is copied from the current branch."""),
opt('-r', '--rename', action = 'store_true',
short = 'Rename an existing branch'),
opt('-p', '--protect', action = 'store_true',
short = 'Prevent StGit from modifying a branch', long = """
Prevent StGit from modifying a branch -- either the current
one, or one named on the command line."""),
opt('-u', '--unprotect', action = 'store_true',
short = 'Allow StGit to modify a branch', long = """
Allow StGit to modify a branch -- either the current one, or
one named on the command line. This undoes the effect of an
earlier 'stg branch --protect' command."""),
opt('--delete', action = 'store_true',
short = 'Delete a branch', long = """
Delete the named branch. If there are any patches left in the
branch, StGit will refuse to delete it unless you give the
'--force' flag.
A protected branch cannot be deleted; it must be unprotected
first (see '--unprotect' above).
If you delete the current branch, you are switched to the
"master" branch, if it exists."""),
opt('--cleanup', action = 'store_true',
short = 'Clean up the StGit metadata for a branch', long = """
Remove the StGit information for the current or given branch. If there
are patches left in the branch, StGit refuses the operation unless
'--force' is given.
A protected branch cannot be cleaned up; it must be unprotected first
(see '--unprotect' above).
A cleaned up branch can be re-initialised using the 'stg init'
command."""),
opt('-d', '--description', short = 'Set the branch description'),
opt('--merge', action = 'store_true',
short = 'Merge work tree changes into the other branch'),
opt('--force', action = 'store_true',
short = 'Force a delete when the series is not empty')]
directory = DirectoryGotoToplevel(log = False)
def __is_current_branch(branch_name):
return crt_series.get_name() == branch_name
def __print_branch(branch_name, length):
initialized = ' '
current = ' '
protected = ' '
branch = stack.Series(branch_name)
if branch.is_initialised():
initialized = 's'
if __is_current_branch(branch_name):
current = '>'
if branch.get_protected():
protected = 'p'
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)
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'
out.start('Deleting branch "%s"' % doomed_name)
doomed.delete(force)
out.done()
def __cleanup_branch(name, force = False):
branch = stack.Series(name)
if branch.get_protected():
raise CmdExcpetion('This branch is protected. Clean up is not permitted')
out.start('Cleaning up branch "%s"' % name)
branch.delete(force = force, cleanup = True)
out.done()
def func(parser, options, args):
if options.create:
if len(args) == 0 or len(args) > 2:
parser.error('incorrect number of arguments')
check_local_changes()
check_conflicts()
check_head_top_equal(crt_series)
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
stack.Series(args[0]).init(create_at = tree_id,
parent_remote = parentremote,
parent_branch = parentbranch)
out.info('Branch "%s" created' % args[0])
log.compat_log_entry('branch --create')
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)
out.start('Cloning current branch to "%s"' % clone)
crt_series.clone(clone)
out.done()
log.copy_log(log.default_repo(), crt_series.get_name(), clone,
'branch --clone')
return
elif options.delete:
if len(args) != 1:
parser.error('incorrect number of arguments')
__delete_branch(args[0], options.force)
log.delete_log(log.default_repo(), args[0])
return
elif options.cleanup:
if not args:
name = crt_series.get_name()
elif len(args) == 1:
name = args[0]
else:
parser.error('incorrect number of arguments')
__cleanup_branch(name, options.force)
log.delete_log(log.default_repo(), name)
return
elif options.list:
if len(args) != 0:
parser.error('incorrect number of arguments')
branches = set(git.get_heads())
for br in set(branches):
m = re.match(r'^(.*)\.stgit$', br)
if m and m.group(1) in branches:
branches.remove(br)
if branches:
out.info('Available branches:')
max_len = max([len(i) for i in branches])
for i in sorted(branches):
__print_branch(i, max_len)
else:
out.info('No branches')
return
elif options.protect:
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
out.start('Protecting branch "%s"' % branch_name)
branch.protect()
out.done()
return
elif options.rename:
if len(args) != 2:
parser.error('incorrect number of arguments')
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]))
log.rename_log(log.default_repo(), args[0], args[1], 'branch --rename')
return
elif options.unprotect:
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
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)
return
elif len(args) == 1:
if __is_current_branch(args[0]):
raise CmdException, 'Branch "%s" is already the current branch' \
% args[0]
if not options.merge:
check_local_changes()
check_conflicts()
check_head_top_equal(crt_series)
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')
print crt_series.get_name()