Use get/set_name for a stack's name.
[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 import stack, git, basedir
27
28
29 help = 'manage patch stacks'
30 usage = """%prog [options] branch-name [commit-id]
31
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.
36
37 When displaying the branches, the names can be prefixed with
38 's' (StGIT managed) or 'p' (protected).
39
40 If not given any options, switch to the named branch."""
41
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')]
68
69
70 def __is_current_branch(branch_name):
71 return crt_series.get_name() == branch_name
72
73 def __print_branch(branch_name, length):
74 initialized = ' '
75 current = ' '
76 protected = ' '
77
78 branch = stack.Series(branch_name)
79
80 if branch.is_initialised():
81 initialized = 's'
82 if __is_current_branch(branch_name):
83 current = '>'
84 if branch.get_protected():
85 protected = 'p'
86 out.stdout(current + ' ' + initialized + protected + '\t'
87 + branch_name.ljust(length) + ' | ' + branch.get_description())
88
89 def __delete_branch(doomed_name, force = False):
90 doomed = stack.Series(doomed_name)
91
92 if doomed.get_protected():
93 raise CmdException, 'This branch is protected. Delete is not permitted'
94
95 out.start('Deleting branch "%s"' % doomed_name)
96
97 if __is_current_branch(doomed_name):
98 check_local_changes()
99 check_conflicts()
100 check_head_top_equal()
101
102 if doomed_name != 'master':
103 git.switch_branch('master')
104
105 doomed.delete(force)
106
107 if doomed_name != 'master':
108 git.delete_branch(doomed_name)
109
110 out.done()
111
112 def func(parser, options, args):
113
114 if options.create:
115
116 if len(args) == 0 or len(args) > 2:
117 parser.error('incorrect number of arguments')
118
119 check_local_changes()
120 check_conflicts()
121 check_head_top_equal()
122
123 tree_id = None
124 if len(args) >= 2:
125 try:
126 if git.rev_parse(args[1]) == git.rev_parse('refs/heads/' + args[1]):
127 # we are for sure referring to a branch
128 parentbranch = 'refs/heads/' + args[1]
129 out.info('Recording "%s" as parent branch' % parentbranch)
130 elif git.rev_parse(args[1]) and re.search('/', args[1]):
131 # FIXME: should the test be more strict ?
132 parentbranch = args[1]
133 else:
134 # Note: this includes refs to StGIT patches
135 out.info('Don\'t know how to determine parent branch'
136 ' from "%s"' % args[1])
137 parentbranch = None
138 except git.GitException:
139 # should use a more specific exception to catch only
140 # non-git refs ?
141 out.info('Don\'t know how to determine parent branch'
142 ' from "%s"' % args[1])
143 parentbranch = None
144
145 tree_id = git_id(args[1])
146 else:
147 # branch stack off current branch
148 parentbranch = git.get_head_file()
149
150 if parentbranch:
151 parentremote = git.identify_remote(parentbranch)
152 if parentremote:
153 out.info('Using remote "%s" to pull parent from'
154 % parentremote)
155 else:
156 out.info('Recording as a local branch')
157 else:
158 # no known parent branch, can't guess the remote
159 parentremote = None
160
161 stack.Series(args[0]).init(create_at = tree_id,
162 parent_remote = parentremote,
163 parent_branch = parentbranch)
164
165 out.info('Branch "%s" created' % args[0])
166 return
167
168 elif options.clone:
169
170 if len(args) == 0:
171 clone = crt_series.get_name() + \
172 time.strftime('-%C%y%m%d-%H%M%S')
173 elif len(args) == 1:
174 clone = args[0]
175 else:
176 parser.error('incorrect number of arguments')
177
178 check_local_changes()
179 check_conflicts()
180 check_head_top_equal()
181
182 out.start('Cloning current branch to "%s"' % clone)
183 crt_series.clone(clone)
184 out.done()
185
186 return
187
188 elif options.delete:
189
190 if len(args) != 1:
191 parser.error('incorrect number of arguments')
192 __delete_branch(args[0], options.force)
193 return
194
195 elif options.list:
196
197 if len(args) != 0:
198 parser.error('incorrect number of arguments')
199
200 branches = []
201 basepath = os.path.join(basedir.get(), 'refs', 'heads')
202 for path, files, dirs in walk_tree(basepath):
203 branches += [os.path.join(path, f) for f in files]
204 branches.sort()
205
206 if branches:
207 out.info('Available branches:')
208 max_len = max([len(i) for i in branches])
209 for i in branches:
210 __print_branch(i, max_len)
211 else:
212 out.info('No branches')
213 return
214
215 elif options.protect:
216
217 if len(args) == 0:
218 branch_name = crt_series.get_name()
219 elif len(args) == 1:
220 branch_name = args[0]
221 else:
222 parser.error('incorrect number of arguments')
223 branch = stack.Series(branch_name)
224
225 if not branch.is_initialised():
226 raise CmdException, 'Branch "%s" is not controlled by StGIT' \
227 % branch_name
228
229 out.start('Protecting branch "%s"' % branch_name)
230 branch.protect()
231 out.done()
232
233 return
234
235 elif options.rename:
236
237 if len(args) != 2:
238 parser.error('incorrect number of arguments')
239
240 if __is_current_branch(args[0]):
241 raise CmdException, 'Renaming the current branch is not supported'
242
243 stack.Series(args[0]).rename(args[1])
244
245 out.info('Renamed branch "%s" to "%s"' % (args[0], args[1]))
246
247 return
248
249 elif options.unprotect:
250
251 if len(args) == 0:
252 branch_name = crt_series.get_name()
253 elif len(args) == 1:
254 branch_name = args[0]
255 else:
256 parser.error('incorrect number of arguments')
257 branch = stack.Series(branch_name)
258
259 if not branch.is_initialised():
260 raise CmdException, 'Branch "%s" is not controlled by StGIT' \
261 % branch_name
262
263 out.info('Unprotecting branch "%s"' % branch_name)
264 branch.unprotect()
265 out.done()
266
267 return
268
269 elif options.description is not None:
270
271 if len(args) == 0:
272 branch_name = crt_series.get_name()
273 elif len(args) == 1:
274 branch_name = args[0]
275 else:
276 parser.error('incorrect number of arguments')
277 branch = stack.Series(branch_name)
278
279 if not branch.is_initialised():
280 raise CmdException, 'Branch "%s" is not controlled by StGIT' \
281 % branch_name
282
283 branch.set_description(options.description)
284
285 return
286
287 elif len(args) == 1:
288
289 if __is_current_branch(args[0]):
290 raise CmdException, 'Branch "%s" is already the current branch' \
291 % args[0]
292
293 check_local_changes()
294 check_conflicts()
295 check_head_top_equal()
296
297 out.start('Switching to branch "%s"' % args[0])
298 git.switch_branch(args[0])
299 out.done()
300 return
301
302 # default action: print the current branch
303 if len(args) != 0:
304 parser.error('incorrect number of arguments')
305
306 print crt_series.get_name()