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
22 from optparse import OptionParser, make_option
24 from stgit.commands.common import *
25 from stgit.utils import *
26 from stgit import stack, git, basedir
29 help = 'manage patch stacks'
30 usage = """%prog [options] branch-name [commit-id]
32 Create, clone, switch between, rename, or delete development branches
33 within a git repository. By default, a single branch called 'master'
34 is always created in a new repository. This subcommand allows you to
35 manage several patch series in the same repository via GIT branches.
37 When displaying the branches, the names can be prefixed with
38 's' (StGIT managed) or 'p' (protected).
40 If not given any options, switch to the named branch."""
42 options = [make_option('-c', '--create',
43 help = 'create a new development branch',
44 action = 'store_true'),
45 make_option('--clone',
46 help = 'clone the contents of the current branch',
47 action = 'store_true'),
48 make_option('--delete',
49 help = 'delete an existing development branch',
50 action = 'store_true'),
51 make_option('-d', '--description',
52 help = 'set the branch description'),
53 make_option('--force',
54 help = 'force a delete when the series is not empty',
55 action = 'store_true'),
56 make_option('-l', '--list',
57 help = 'list branches contained in this repository',
58 action = 'store_true'),
59 make_option('-p', '--protect',
60 help = 'prevent StGIT from modifying this branch',
61 action = 'store_true'),
62 make_option('-r', '--rename',
63 help = 'rename an existing development branch',
64 action = 'store_true'),
65 make_option('-u', '--unprotect',
66 help = 'allow StGIT to modify this branch',
67 action = 'store_true')]
70 def __is_current_branch(branch_name):
71 return crt_series.get_name() == branch_name
73 def __print_branch(branch_name, length):
78 branch = stack.Series(branch_name)
80 if branch.is_initialised():
82 if __is_current_branch(branch_name):
84 if branch.get_protected():
86 out.stdout(current + ' ' + initialized + protected + '\t'
87 + branch_name.ljust(length) + ' | ' + branch.get_description())
89 def __delete_branch(doomed_name, force = False):
90 doomed = stack.Series(doomed_name)
92 if doomed.get_protected():
93 raise CmdException, 'This branch is protected. Delete is not permitted'
95 out.start('Deleting branch "%s"' % doomed_name)
97 if __is_current_branch(doomed_name):
100 check_head_top_equal()
102 if doomed_name != 'master':
103 git.switch_branch('master')
107 if doomed_name != 'master':
108 git.delete_branch(doomed_name)
112 def func(parser, options, args):
116 if len(args) == 0 or len(args) > 2:
117 parser.error('incorrect number of arguments')
119 check_local_changes()
121 check_head_top_equal()
127 branchpoint = git.rev_parse(args[1])
129 # first, look for branchpoint in well-known branch namespaces
130 for namespace in ('refs/heads/', 'remotes/'):
131 # check if branchpoint exists in namespace
133 maybehead = git.rev_parse(namespace + args[1])
134 except git.GitException:
137 # check if git resolved branchpoint to this namespace
138 if maybehead and branchpoint == maybehead:
139 # we are for sure referring to a branch
140 parentbranch = namespace + args[1]
142 except git.GitException:
143 # should use a more specific exception to catch only
145 out.info('Don\'t know how to determine parent branch'
146 ' from "%s"' % args[1])
147 # exception in branch = rev_parse() leaves branchpoint unbound
150 tree_id = branchpoint or git_id(args[1])
153 out.info('Recording "%s" as parent branch' % parentbranch)
155 out.info('Don\'t know how to determine parent branch'
156 ' from "%s"' % args[1])
158 # branch stack off current branch
159 parentbranch = git.get_head_file()
162 parentremote = git.identify_remote(parentbranch)
164 out.info('Using remote "%s" to pull parent from'
167 out.info('Recording as a local branch')
169 # no known parent branch, can't guess the remote
172 stack.Series(args[0]).init(create_at = tree_id,
173 parent_remote = parentremote,
174 parent_branch = parentbranch)
176 out.info('Branch "%s" created' % args[0])
182 clone = crt_series.get_name() + \
183 time.strftime('-%C%y%m%d-%H%M%S')
187 parser.error('incorrect number of arguments')
189 check_local_changes()
191 check_head_top_equal()
193 out.start('Cloning current branch to "%s"' % clone)
194 crt_series.clone(clone)
202 parser.error('incorrect number of arguments')
203 __delete_branch(args[0], options.force)
209 parser.error('incorrect number of arguments')
212 basepath = os.path.join(basedir.get(), 'refs', 'heads')
213 for path, files, dirs in walk_tree(basepath):
214 branches += [os.path.join(path, f) for f in files]
218 out.info('Available branches:')
219 max_len = max([len(i) for i in branches])
221 __print_branch(i, max_len)
223 out.info('No branches')
226 elif options.protect:
229 branch_name = crt_series.get_name()
231 branch_name = args[0]
233 parser.error('incorrect number of arguments')
234 branch = stack.Series(branch_name)
236 if not branch.is_initialised():
237 raise CmdException, 'Branch "%s" is not controlled by StGIT' \
240 out.start('Protecting branch "%s"' % branch_name)
249 parser.error('incorrect number of arguments')
251 if __is_current_branch(args[0]):
252 raise CmdException, 'Renaming the current branch is not supported'
254 stack.Series(args[0]).rename(args[1])
256 out.info('Renamed branch "%s" to "%s"' % (args[0], args[1]))
260 elif options.unprotect:
263 branch_name = crt_series.get_name()
265 branch_name = args[0]
267 parser.error('incorrect number of arguments')
268 branch = stack.Series(branch_name)
270 if not branch.is_initialised():
271 raise CmdException, 'Branch "%s" is not controlled by StGIT' \
274 out.info('Unprotecting branch "%s"' % branch_name)
280 elif options.description is not None:
283 branch_name = crt_series.get_name()
285 branch_name = args[0]
287 parser.error('incorrect number of arguments')
288 branch = stack.Series(branch_name)
290 if not branch.is_initialised():
291 raise CmdException, 'Branch "%s" is not controlled by StGIT' \
294 branch.set_description(options.description)
300 if __is_current_branch(args[0]):
301 raise CmdException, 'Branch "%s" is already the current branch' \
304 check_local_changes()
306 check_head_top_equal()
308 out.start('Switching to branch "%s"' % args[0])
309 git.switch_branch(args[0])
313 # default action: print the current branch
315 parser.error('incorrect number of arguments')
317 print crt_series.get_name()