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