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