Don't touch ref files manually
[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 parentbranch = None
126 try:
127 branchpoint = git.rev_parse(args[1])
128
129 # first, look for branchpoint in well-known branch namespaces
130 for namespace in ('refs/heads/', 'remotes/'):
131 # check if branchpoint exists in namespace
132 try:
133 maybehead = git.rev_parse(namespace + args[1])
134 except git.GitException:
135 maybehead = None
136
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]
141
142 except git.GitException:
143 # should use a more specific exception to catch only
144 # non-git refs ?
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
148 branchpoint = None
149
150 tree_id = branchpoint or git_id(args[1])
151
152 if parentbranch:
153 out.info('Recording "%s" as parent branch' % parentbranch)
154 else:
155 out.info('Don\'t know how to determine parent branch'
156 ' from "%s"' % args[1])
157 else:
158 # branch stack off current branch
159 parentbranch = git.get_head_file()
160
161 if parentbranch:
162 parentremote = git.identify_remote(parentbranch)
163 if parentremote:
164 out.info('Using remote "%s" to pull parent from'
165 % parentremote)
166 else:
167 out.info('Recording as a local branch')
168 else:
169 # no known parent branch, can't guess the remote
170 parentremote = None
171
172 stack.Series(args[0]).init(create_at = tree_id,
173 parent_remote = parentremote,
174 parent_branch = parentbranch)
175
176 out.info('Branch "%s" created' % args[0])
177 return
178
179 elif options.clone:
180
181 if len(args) == 0:
182 clone = crt_series.get_name() + \
183 time.strftime('-%C%y%m%d-%H%M%S')
184 elif len(args) == 1:
185 clone = args[0]
186 else:
187 parser.error('incorrect number of arguments')
188
189 check_local_changes()
190 check_conflicts()
191 check_head_top_equal()
192
193 out.start('Cloning current branch to "%s"' % clone)
194 crt_series.clone(clone)
195 out.done()
196
197 return
198
199 elif options.delete:
200
201 if len(args) != 1:
202 parser.error('incorrect number of arguments')
203 __delete_branch(args[0], options.force)
204 return
205
206 elif options.list:
207
208 if len(args) != 0:
209 parser.error('incorrect number of arguments')
210
211 branches = git.get_heads()
212 branches.sort()
213
214 if branches:
215 out.info('Available branches:')
216 max_len = max([len(i) for i in branches])
217 for i in branches:
218 __print_branch(i, max_len)
219 else:
220 out.info('No branches')
221 return
222
223 elif options.protect:
224
225 if len(args) == 0:
226 branch_name = crt_series.get_name()
227 elif len(args) == 1:
228 branch_name = args[0]
229 else:
230 parser.error('incorrect number of arguments')
231 branch = stack.Series(branch_name)
232
233 if not branch.is_initialised():
234 raise CmdException, 'Branch "%s" is not controlled by StGIT' \
235 % branch_name
236
237 out.start('Protecting branch "%s"' % branch_name)
238 branch.protect()
239 out.done()
240
241 return
242
243 elif options.rename:
244
245 if len(args) != 2:
246 parser.error('incorrect number of arguments')
247
248 if __is_current_branch(args[0]):
249 raise CmdException, 'Renaming the current branch is not supported'
250
251 stack.Series(args[0]).rename(args[1])
252
253 out.info('Renamed branch "%s" to "%s"' % (args[0], args[1]))
254
255 return
256
257 elif options.unprotect:
258
259 if len(args) == 0:
260 branch_name = crt_series.get_name()
261 elif len(args) == 1:
262 branch_name = args[0]
263 else:
264 parser.error('incorrect number of arguments')
265 branch = stack.Series(branch_name)
266
267 if not branch.is_initialised():
268 raise CmdException, 'Branch "%s" is not controlled by StGIT' \
269 % branch_name
270
271 out.info('Unprotecting branch "%s"' % branch_name)
272 branch.unprotect()
273 out.done()
274
275 return
276
277 elif options.description is not None:
278
279 if len(args) == 0:
280 branch_name = crt_series.get_name()
281 elif len(args) == 1:
282 branch_name = args[0]
283 else:
284 parser.error('incorrect number of arguments')
285 branch = stack.Series(branch_name)
286
287 if not branch.is_initialised():
288 raise CmdException, 'Branch "%s" is not controlled by StGIT' \
289 % branch_name
290
291 branch.set_description(options.description)
292
293 return
294
295 elif len(args) == 1:
296
297 if __is_current_branch(args[0]):
298 raise CmdException, 'Branch "%s" is already the current branch' \
299 % args[0]
300
301 check_local_changes()
302 check_conflicts()
303 check_head_top_equal()
304
305 out.start('Switching to branch "%s"' % args[0])
306 git.switch_branch(args[0])
307 out.done()
308 return
309
310 # default action: print the current branch
311 if len(args) != 0:
312 parser.error('incorrect number of arguments')
313
314 print crt_series.get_name()