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