Have 'stg branch --create' record parent information.
[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 development branches'
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('--convert',
49 help = 'switch between old and new format branches',
50 action = 'store_true'),
51 make_option('--delete',
52 help = 'delete an existing development branch',
53 action = 'store_true'),
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 "stg pull" 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 "stg pull" to modify this branch',
68 action = 'store_true')]
69
70
71 def __is_current_branch(branch_name):
72 return crt_series.get_branch() == 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 print 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 print 'Deleting branch "%s"...' % doomed_name,
97 sys.stdout.flush()
98
99 if __is_current_branch(doomed_name):
100 check_local_changes()
101 check_conflicts()
102 check_head_top_equal()
103
104 if doomed_name != 'master':
105 git.switch_branch('master')
106
107 doomed.delete(force)
108
109 if doomed_name != 'master':
110 git.delete_branch(doomed_name)
111
112 print 'done'
113
114 def func(parser, options, args):
115
116 if options.create:
117
118 if len(args) == 0 or len(args) > 2:
119 parser.error('incorrect number of arguments')
120
121 check_local_changes()
122 check_conflicts()
123 check_head_top_equal()
124
125 tree_id = None
126 if len(args) >= 2:
127 try:
128 if git.rev_parse(args[1]) == git.rev_parse('refs/heads/' + args[1]):
129 # we are for sure refering to a branch
130 parentbranch = 'refs/heads/' + args[1]
131 print 'Recording "%s" as parent branch.' % parentbranch
132 elif git.rev_parse(args[1]) and re.search('/', args[1]):
133 # FIXME: should the test be more strict ?
134 parentbranch = args[1]
135 else:
136 # Note: this includes refs to StGIT patches
137 print 'Don\'t know how to determine parent branch from "%s".' % args[1]
138 parentbranch = None
139 except git.GitException:
140 # should use a more specific exception to catch only non-git refs ?
141 print 'Don\'t know how to determine parent branch from "%s".' % args[1]
142 parentbranch = None
143
144 tree_id = git_id(args[1])
145 else:
146 # branch stack off current branch
147 parentbranch = git.get_head_file()
148
149 if parentbranch:
150 parentremote = git.identify_remote(parentbranch)
151 if parentremote:
152 print 'Using "%s" remote to pull parent from.' % parentremote
153 else:
154 print 'Not identified a remote to pull parent from.'
155 else:
156 parentremote = None
157
158 stack.Series(args[0]).init(create_at = tree_id,
159 parent_remote = parentremote,
160 parent_branch = parentbranch)
161
162 print 'Branch "%s" created.' % args[0]
163 return
164
165 elif options.clone:
166
167 if len(args) == 0:
168 clone = crt_series.get_branch() + \
169 time.strftime('-%C%y%m%d-%H%M%S')
170 elif len(args) == 1:
171 clone = args[0]
172 else:
173 parser.error('incorrect number of arguments')
174
175 check_local_changes()
176 check_conflicts()
177 check_head_top_equal()
178
179 print 'Cloning current branch to "%s"...' % clone,
180 sys.stdout.flush()
181 crt_series.clone(clone)
182 print 'done'
183
184 return
185
186 elif options.convert:
187
188 if len(args) != 0:
189 parser.error('incorrect number of arguments')
190
191 crt_series.convert()
192 return
193
194 elif options.delete:
195
196 if len(args) != 1:
197 parser.error('incorrect number of arguments')
198 __delete_branch(args[0], options.force)
199 return
200
201 elif options.list:
202
203 if len(args) != 0:
204 parser.error('incorrect number of arguments')
205
206 branches = []
207 basepath = os.path.join(basedir.get(), 'refs', 'heads')
208 for path, files, dirs in walk_tree(basepath):
209 branches += [os.path.join(path, f) for f in files]
210 branches.sort()
211
212 if branches:
213 print 'Available branches:'
214 max_len = max([len(i) for i in branches])
215 for i in branches:
216 __print_branch(i, max_len)
217 else:
218 print 'No branches'
219 return
220
221 elif options.protect:
222
223 if len(args) == 0:
224 branch_name = crt_series.get_branch()
225 elif len(args) == 1:
226 branch_name = args[0]
227 else:
228 parser.error('incorrect number of arguments')
229 branch = stack.Series(branch_name)
230
231 if not branch.is_initialised():
232 raise CmdException, 'Branch "%s" is not controlled by StGIT' \
233 % branch_name
234
235 print 'Protecting branch "%s"...' % branch_name,
236 sys.stdout.flush()
237 branch.protect()
238 print 'done'
239
240 return
241
242 elif options.rename:
243
244 if len(args) != 2:
245 parser.error('incorrect number of arguments')
246
247 if __is_current_branch(args[0]):
248 raise CmdException, 'Renaming the current branch is not supported'
249
250 stack.Series(args[0]).rename(args[1])
251
252 print 'Renamed branch "%s" as "%s".' % (args[0], args[1])
253
254 return
255
256 elif options.unprotect:
257
258 if len(args) == 0:
259 branch_name = crt_series.get_branch()
260 elif len(args) == 1:
261 branch_name = args[0]
262 else:
263 parser.error('incorrect number of arguments')
264 branch = stack.Series(branch_name)
265
266 if not branch.is_initialised():
267 raise CmdException, 'Branch "%s" is not controlled by StGIT' \
268 % branch_name
269
270 print 'Unprotecting branch "%s"...' % branch_name,
271 sys.stdout.flush()
272 branch.unprotect()
273 print 'done'
274
275 return
276
277 elif len(args) == 1:
278
279 if __is_current_branch(args[0]):
280 raise CmdException, 'Branch "%s" is already the current branch' \
281 % args[0]
282
283 check_local_changes()
284 check_conflicts()
285 check_head_top_equal()
286
287 print 'Switching to branch "%s"...' % args[0],
288 sys.stdout.flush()
289
290 git.switch_branch(args[0])
291
292 print 'done'
293 return
294
295 # default action: print the current branch
296 if len(args) != 0:
297 parser.error('incorrect number of arguments')
298
299 print crt_series.get_branch()