5 Copyright (C) 2005, Chuck Lever <cel@netapp.com>
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License version 2 as
9 published by the Free Software Foundation.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 import sys, os, time, re
22 from optparse import OptionParser, make_option
24 from stgit.commands.common import *
25 from stgit.utils import *
26 from stgit.out import *
27 from stgit import stack, git, basedir
30 help = 'manage patch stacks'
31 usage = """%prog [options] branch-name [commit-id]
33 Create, clone, switch between, rename, or delete development branches
34 within a git repository. By default, a single branch called 'master'
35 is always created in a new repository. This subcommand allows you to
36 manage several patch series in the same repository via GIT branches.
38 When displaying the branches, the names can be prefixed with
39 's' (StGIT managed) or 'p' (protected).
41 If not given any options, switch to the named branch."""
43 directory = DirectoryGotoToplevel()
44 options = [make_option('-c', '--create',
45 help = 'create a new development branch',
46 action = 'store_true'),
47 make_option('--clone',
48 help = 'clone the contents of the current branch',
49 action = 'store_true'),
50 make_option('--delete',
51 help = 'delete an existing development branch',
52 action = 'store_true'),
53 make_option('-d', '--description',
54 help = 'set the branch description'),
55 make_option('--force',
56 help = 'force a delete when the series is not empty',
57 action = 'store_true'),
58 make_option('-l', '--list',
59 help = 'list branches contained in this repository',
60 action = 'store_true'),
61 make_option('-p', '--protect',
62 help = 'prevent StGIT from modifying this branch',
63 action = 'store_true'),
64 make_option('-r', '--rename',
65 help = 'rename an existing development branch',
66 action = 'store_true'),
67 make_option('-u', '--unprotect',
68 help = 'allow StGIT to modify this branch',
69 action = 'store_true')]
72 def __is_current_branch(branch_name):
73 return crt_series.get_name() == branch_name
75 def __print_branch(branch_name, length):
80 branch = stack.Series(branch_name)
82 if branch.is_initialised():
84 if __is_current_branch(branch_name):
86 if branch.get_protected():
88 out.stdout(current + ' ' + initialized + protected + '\t'
89 + branch_name.ljust(length) + ' | ' + branch.get_description())
91 def __delete_branch(doomed_name, force = False):
92 doomed = stack.Series(doomed_name)
94 if doomed.get_protected():
95 raise CmdException, 'This branch is protected. Delete is not permitted'
97 out.start('Deleting branch "%s"' % doomed_name)
99 if __is_current_branch(doomed_name):
100 raise CmdException('Cannot delete the current branch')
106 def func(parser, options, args):
110 if len(args) == 0 or len(args) > 2:
111 parser.error('incorrect number of arguments')
113 check_local_changes()
115 check_head_top_equal(crt_series)
121 branchpoint = git.rev_parse(args[1])
124 head_re = re.compile('refs/(heads|remotes)/')
125 ref_re = re.compile(args[1] + '$')
126 for ref in git.all_refs():
127 if head_re.match(ref) and ref_re.search(ref):
128 # args[1] is a valid ref from the branchpoint
130 parentbranch = args[1]
132 except git.GitException:
133 # should use a more specific exception to catch only
135 out.info('Don\'t know how to determine parent branch'
136 ' from "%s"' % args[1])
137 # exception in branch = rev_parse() leaves branchpoint unbound
140 tree_id = git_id(crt_series, branchpoint or args[1])
143 out.info('Recording "%s" as parent branch' % parentbranch)
145 out.info('Don\'t know how to determine parent branch'
146 ' from "%s"' % args[1])
148 # branch stack off current branch
149 parentbranch = git.get_head_file()
152 parentremote = git.identify_remote(parentbranch)
154 out.info('Using remote "%s" to pull parent from'
157 out.info('Recording as a local branch')
159 # no known parent branch, can't guess the remote
162 stack.Series(args[0]).init(create_at = tree_id,
163 parent_remote = parentremote,
164 parent_branch = parentbranch)
166 out.info('Branch "%s" created' % args[0])
172 clone = crt_series.get_name() + \
173 time.strftime('-%C%y%m%d-%H%M%S')
177 parser.error('incorrect number of arguments')
179 check_local_changes()
181 check_head_top_equal(crt_series)
183 out.start('Cloning current branch to "%s"' % clone)
184 crt_series.clone(clone)
192 parser.error('incorrect number of arguments')
193 __delete_branch(args[0], options.force)
199 parser.error('incorrect number of arguments')
201 branches = git.get_heads()
205 out.info('Available branches:')
206 max_len = max([len(i) for i in branches])
208 __print_branch(i, max_len)
210 out.info('No branches')
213 elif options.protect:
216 branch_name = crt_series.get_name()
218 branch_name = args[0]
220 parser.error('incorrect number of arguments')
221 branch = stack.Series(branch_name)
223 if not branch.is_initialised():
224 raise CmdException, 'Branch "%s" is not controlled by StGIT' \
227 out.start('Protecting branch "%s"' % branch_name)
236 parser.error('incorrect number of arguments')
238 if __is_current_branch(args[0]):
239 raise CmdException, 'Renaming the current branch is not supported'
241 stack.Series(args[0]).rename(args[1])
243 out.info('Renamed branch "%s" to "%s"' % (args[0], args[1]))
247 elif options.unprotect:
250 branch_name = crt_series.get_name()
252 branch_name = args[0]
254 parser.error('incorrect number of arguments')
255 branch = stack.Series(branch_name)
257 if not branch.is_initialised():
258 raise CmdException, 'Branch "%s" is not controlled by StGIT' \
261 out.info('Unprotecting branch "%s"' % branch_name)
267 elif options.description is not None:
270 branch_name = crt_series.get_name()
272 branch_name = args[0]
274 parser.error('incorrect number of arguments')
275 branch = stack.Series(branch_name)
277 if not branch.is_initialised():
278 raise CmdException, 'Branch "%s" is not controlled by StGIT' \
281 branch.set_description(options.description)
287 if __is_current_branch(args[0]):
288 raise CmdException, 'Branch "%s" is already the current branch' \
291 check_local_changes()
293 check_head_top_equal(crt_series)
295 out.start('Switching to branch "%s"' % args[0])
296 git.switch_branch(args[0])
300 # default action: print the current branch
302 parser.error('incorrect number of arguments')
304 print crt_series.get_name()