chiark / gitweb /
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()