chiark / gitweb /
b043c6928747eafeda355ab1610305975870aa5b
[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('--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('-d', '--description',
55                        help = 'set the branch description'),
56            make_option('--force',
57                        help = 'force a delete when the series is not empty',
58                        action = 'store_true'),
59            make_option('-l', '--list',
60                        help = 'list branches contained in this repository',
61                        action = 'store_true'),
62            make_option('-p', '--protect',
63                        help = 'prevent StGIT from modifying this branch',
64                        action = 'store_true'),
65            make_option('-r', '--rename',
66                        help = 'rename an existing development branch',
67                        action = 'store_true'),
68            make_option('-u', '--unprotect',
69                        help = 'allow StGIT to modify this branch',
70                        action = 'store_true')]
71
72
73 def __is_current_branch(branch_name):
74     return crt_series.get_branch() == branch_name
75
76 def __print_branch(branch_name, length):
77     initialized = ' '
78     current = ' '
79     protected = ' '
80
81     branch = stack.Series(branch_name)
82
83     if branch.is_initialised():
84         initialized = 's'
85     if __is_current_branch(branch_name):
86         current = '>'
87     if branch.get_protected():
88         protected = 'p'
89     print current + ' ' + initialized + protected + '\t' + \
90           branch_name.ljust(length) + '  | ' + branch.get_description()
91
92 def __delete_branch(doomed_name, force = False):
93     doomed = stack.Series(doomed_name)
94
95     if doomed.get_protected():
96         raise CmdException, 'This branch is protected. Delete is not permitted'
97
98     print 'Deleting branch "%s"...' % doomed_name,
99     sys.stdout.flush()
100
101     if __is_current_branch(doomed_name):
102         check_local_changes()
103         check_conflicts()
104         check_head_top_equal()
105
106         if doomed_name != 'master':
107             git.switch_branch('master')
108
109     doomed.delete(force)
110
111     if doomed_name != 'master':
112         git.delete_branch(doomed_name)
113
114     print 'done'
115
116 def func(parser, options, args):
117
118     if options.create:
119
120         if len(args) == 0 or len(args) > 2:
121             parser.error('incorrect number of arguments')
122
123         check_local_changes()
124         check_conflicts()
125         check_head_top_equal()
126
127         tree_id = None
128         if len(args) >= 2:
129             try:
130                 if git.rev_parse(args[1]) == git.rev_parse('refs/heads/' + args[1]):
131                     # we are for sure referring to a branch
132                     parentbranch = 'refs/heads/' + args[1]
133                     print 'Recording "%s" as parent branch.' % parentbranch
134                 elif git.rev_parse(args[1]) and re.search('/', args[1]):
135                     # FIXME: should the test be more strict ?
136                     parentbranch = args[1]
137                 else:
138                     # Note: this includes refs to StGIT patches
139                     print 'Don\'t know how to determine parent branch from "%s".' % args[1]
140                     parentbranch = None
141             except git.GitException:
142                 # should use a more specific exception to catch only non-git refs ?
143                 print 'Don\'t know how to determine parent branch from "%s".' % args[1]
144                 parentbranch = None
145
146             tree_id = git_id(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                 print 'Using "%s" remote to pull parent from.' % parentremote
155             else:
156                 print 'Recording as a local branch.'
157         else:
158             # no known parent branch, can't guess the remote
159             parentremote = None
160
161         stack.Series(args[0]).init(create_at = tree_id,
162                                    parent_remote = parentremote,
163                                    parent_branch = parentbranch)
164
165         print 'Branch "%s" created.' % args[0]
166         return
167
168     elif options.clone:
169
170         if len(args) == 0:
171             clone = crt_series.get_branch() + \
172                     time.strftime('-%C%y%m%d-%H%M%S')
173         elif len(args) == 1:
174             clone = args[0]
175         else:
176             parser.error('incorrect number of arguments')
177
178         check_local_changes()
179         check_conflicts()
180         check_head_top_equal()
181
182         print 'Cloning current branch to "%s"...' % clone,
183         sys.stdout.flush()
184         crt_series.clone(clone)
185         print 'done'
186
187         return
188
189     elif options.convert:
190
191         if len(args) != 0:
192             parser.error('incorrect number of arguments')
193
194         crt_series.convert()
195         return
196
197     elif options.delete:
198
199         if len(args) != 1:
200             parser.error('incorrect number of arguments')
201         __delete_branch(args[0], options.force)
202         return
203
204     elif options.list:
205
206         if len(args) != 0:
207             parser.error('incorrect number of arguments')
208
209         branches = []
210         basepath = os.path.join(basedir.get(), 'refs', 'heads')
211         for path, files, dirs in walk_tree(basepath):
212             branches += [os.path.join(path, f) for f in files]
213         branches.sort()
214
215         if branches:
216             print 'Available branches:'
217             max_len = max([len(i) for i in branches])
218             for i in branches:
219                 __print_branch(i, max_len)
220         else:
221             print 'No branches'
222         return
223
224     elif options.protect:
225
226         if len(args) == 0:
227             branch_name = crt_series.get_branch()
228         elif len(args) == 1:
229             branch_name = args[0]
230         else:
231             parser.error('incorrect number of arguments')
232         branch = stack.Series(branch_name)
233
234         if not branch.is_initialised():
235             raise CmdException, 'Branch "%s" is not controlled by StGIT' \
236                   % branch_name
237
238         print 'Protecting branch "%s"...' % branch_name,
239         sys.stdout.flush()
240         branch.protect()
241         print 'done'
242
243         return
244
245     elif options.rename:
246
247         if len(args) != 2:
248             parser.error('incorrect number of arguments')
249
250         if __is_current_branch(args[0]):
251             raise CmdException, 'Renaming the current branch is not supported'
252
253         stack.Series(args[0]).rename(args[1])
254
255         print 'Renamed branch "%s" as "%s".' % (args[0], args[1])
256
257         return
258
259     elif options.unprotect:
260
261         if len(args) == 0:
262             branch_name = crt_series.get_branch()
263         elif len(args) == 1:
264             branch_name = args[0]
265         else:
266             parser.error('incorrect number of arguments')
267         branch = stack.Series(branch_name)
268
269         if not branch.is_initialised():
270             raise CmdException, 'Branch "%s" is not controlled by StGIT' \
271                   % branch_name
272
273         print 'Unprotecting branch "%s"...' % branch_name,
274         sys.stdout.flush()
275         branch.unprotect()
276         print 'done'
277
278         return
279
280     elif options.description is not None:
281
282         if len(args) == 0:
283             branch_name = crt_series.get_branch()
284         elif len(args) == 1:
285             branch_name = args[0]
286         else:
287             parser.error('incorrect number of arguments')
288         branch = stack.Series(branch_name)
289
290         if not branch.is_initialised():
291             raise CmdException, 'Branch "%s" is not controlled by StGIT' \
292                   % branch_name
293
294         branch.set_description(options.description)
295
296         return
297
298     elif len(args) == 1:
299
300         if __is_current_branch(args[0]):
301             raise CmdException, 'Branch "%s" is already the current branch' \
302                   % args[0]
303
304         check_local_changes()
305         check_conflicts()
306         check_head_top_equal()
307
308         print 'Switching to branch "%s"...' % args[0],
309         sys.stdout.flush()
310
311         git.switch_branch(args[0])
312
313         print 'done'
314         return
315
316     # default action: print the current branch
317     if len(args) != 0:
318         parser.error('incorrect number of arguments')
319
320     print crt_series.get_branch()