Don't try to delete the branch twice
[stgit] / stgit / commands / branch.py
1 """Branch command
2 """
3
4 __copyright__ = """
5 Copyright (C) 2005, Chuck Lever <cel@netapp.com>
6
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.
10
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.
15
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
19 """
20
21 import sys, os, time
22 from optparse import OptionParser, make_option
23
24 from stgit.commands.common import *
25 from stgit.utils import *
26 from stgit.out import *
27 from stgit import stack, git, basedir
28
29
30 help = 'manage patch stacks'
31 usage = """%prog [options] branch-name [commit-id]
32
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.
37
38 When displaying the branches, the names can be prefixed with
39 's' (StGIT managed) or 'p' (protected).
40
41 If not given any options, switch to the named branch."""
42
43 options = [make_option('-c', '--create',
44 help = 'create a new development branch',
45 action = 'store_true'),
46 make_option('--clone',
47 help = 'clone the contents of the current branch',
48 action = 'store_true'),
49 make_option('--delete',
50 help = 'delete an existing development branch',
51 action = 'store_true'),
52 make_option('-d', '--description',
53 help = 'set the branch description'),
54 make_option('--force',
55 help = 'force a delete when the series is not empty',
56 action = 'store_true'),
57 make_option('-l', '--list',
58 help = 'list branches contained in this repository',
59 action = 'store_true'),
60 make_option('-p', '--protect',
61 help = 'prevent StGIT from modifying this branch',
62 action = 'store_true'),
63 make_option('-r', '--rename',
64 help = 'rename an existing development branch',
65 action = 'store_true'),
66 make_option('-u', '--unprotect',
67 help = 'allow StGIT to modify this branch',
68 action = 'store_true')]
69
70
71 def __is_current_branch(branch_name):
72 return crt_series.get_name() == branch_name
73
74 def __print_branch(branch_name, length):
75 initialized = ' '
76 current = ' '
77 protected = ' '
78
79 branch = stack.Series(branch_name)
80
81 if branch.is_initialised():
82 initialized = 's'
83 if __is_current_branch(branch_name):
84 current = '>'
85 if branch.get_protected():
86 protected = 'p'
87 out.stdout(current + ' ' + initialized + protected + '\t'
88 + branch_name.ljust(length) + ' | ' + branch.get_description())
89
90 def __delete_branch(doomed_name, force = False):
91 doomed = stack.Series(doomed_name)
92
93 if doomed.get_protected():
94 raise CmdException, 'This branch is protected. Delete is not permitted'
95
96 out.start('Deleting branch "%s"' % doomed_name)
97
98 if __is_current_branch(doomed_name):
99 raise CmdException('Cannot delete the current branch')
100
101 doomed.delete(force)
102
103 out.done()
104
105 def func(parser, options, args):
106
107 if options.create:
108
109 if len(args) == 0 or len(args) > 2:
110 parser.error('incorrect number of arguments')
111
112 check_local_changes()
113 check_conflicts()
114 check_head_top_equal()
115
116 tree_id = None
117 if len(args) >= 2:
118 parentbranch = None
119 try:
120 branchpoint = git.rev_parse(args[1])
121
122 # first, look for branchpoint in well-known branch namespaces
123 for namespace in ('refs/heads/', 'remotes/'):
124 # check if branchpoint exists in namespace
125 try:
126 maybehead = git.rev_parse(namespace + args[1])
127 except git.GitException:
128 maybehead = None
129
130 # check if git resolved branchpoint to this namespace
131 if maybehead and branchpoint == maybehead:
132 # we are for sure referring to a branch
133 parentbranch = namespace + args[1]
134
135 except git.GitException:
136 # should use a more specific exception to catch only
137 # non-git refs ?
138 out.info('Don\'t know how to determine parent branch'
139 ' from "%s"' % args[1])
140 # exception in branch = rev_parse() leaves branchpoint unbound
141 branchpoint = None
142
143 tree_id = branchpoint or git_id(args[1])
144
145 if parentbranch:
146 out.info('Recording "%s" as parent branch' % parentbranch)
147 else:
148 out.info('Don\'t know how to determine parent branch'
149 ' from "%s"' % args[1])
150 else:
151 # branch stack off current branch
152 parentbranch = git.get_head_file()
153
154 if parentbranch:
155 parentremote = git.identify_remote(parentbranch)
156 if parentremote:
157 out.info('Using remote "%s" to pull parent from'
158 % parentremote)
159 else:
160 out.info('Recording as a local branch')
161 else:
162 # no known parent branch, can't guess the remote
163 parentremote = None
164
165 stack.Series(args[0]).init(create_at = tree_id,
166 parent_remote = parentremote,
167 parent_branch = parentbranch)
168
169 out.info('Branch "%s" created' % args[0])
170 return
171
172 elif options.clone:
173
174 if len(args) == 0:
175 clone = crt_series.get_name() + \
176 time.strftime('-%C%y%m%d-%H%M%S')
177 elif len(args) == 1:
178 clone = args[0]
179 else:
180 parser.error('incorrect number of arguments')
181
182 check_local_changes()
183 check_conflicts()
184 check_head_top_equal()
185
186 out.start('Cloning current branch to "%s"' % clone)
187 crt_series.clone(clone)
188 out.done()
189
190 return
191
192 elif options.delete:
193
194 if len(args) != 1:
195 parser.error('incorrect number of arguments')
196 __delete_branch(args[0], options.force)
197 return
198
199 elif options.list:
200
201 if len(args) != 0:
202 parser.error('incorrect number of arguments')
203
204 branches = git.get_heads()
205 branches.sort()
206
207 if branches:
208 out.info('Available branches:')
209 max_len = max([len(i) for i in branches])
210 for i in branches:
211 __print_branch(i, max_len)
212 else:
213 out.info('No branches')
214 return
215
216 elif options.protect:
217
218 if len(args) == 0:
219 branch_name = crt_series.get_name()
220 elif len(args) == 1:
221 branch_name = args[0]
222 else:
223 parser.error('incorrect number of arguments')
224 branch = stack.Series(branch_name)
225
226 if not branch.is_initialised():
227 raise CmdException, 'Branch "%s" is not controlled by StGIT' \
228 % branch_name
229
230 out.start('Protecting branch "%s"' % branch_name)
231 branch.protect()
232 out.done()
233
234 return
235
236 elif options.rename:
237
238 if len(args) != 2:
239 parser.error('incorrect number of arguments')
240
241 if __is_current_branch(args[0]):
242 raise CmdException, 'Renaming the current branch is not supported'
243
244 stack.Series(args[0]).rename(args[1])
245
246 out.info('Renamed branch "%s" to "%s"' % (args[0], args[1]))
247
248 return
249
250 elif options.unprotect:
251
252 if len(args) == 0:
253 branch_name = crt_series.get_name()
254 elif len(args) == 1:
255 branch_name = args[0]
256 else:
257 parser.error('incorrect number of arguments')
258 branch = stack.Series(branch_name)
259
260 if not branch.is_initialised():
261 raise CmdException, 'Branch "%s" is not controlled by StGIT' \
262 % branch_name
263
264 out.info('Unprotecting branch "%s"' % branch_name)
265 branch.unprotect()
266 out.done()
267
268 return
269
270 elif options.description is not None:
271
272 if len(args) == 0:
273 branch_name = crt_series.get_name()
274 elif len(args) == 1:
275 branch_name = args[0]
276 else:
277 parser.error('incorrect number of arguments')
278 branch = stack.Series(branch_name)
279
280 if not branch.is_initialised():
281 raise CmdException, 'Branch "%s" is not controlled by StGIT' \
282 % branch_name
283
284 branch.set_description(options.description)
285
286 return
287
288 elif len(args) == 1:
289
290 if __is_current_branch(args[0]):
291 raise CmdException, 'Branch "%s" is already the current branch' \
292 % args[0]
293
294 check_local_changes()
295 check_conflicts()
296 check_head_top_equal()
297
298 out.start('Switching to branch "%s"' % args[0])
299 git.switch_branch(args[0])
300 out.done()
301 return
302
303 # default action: print the current branch
304 if len(args) != 0:
305 parser.error('incorrect number of arguments')
306
307 print crt_series.get_name()