chiark / gitweb /
Allow "branch --create" to be given a tag id
[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 = DirectoryHasRepository()
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()