chiark / gitweb /
2fb5f5993a6302a9e7a9f2eb9bcf78abaabb94d1
[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_name() == 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             parentbranch = None
126             try:
127                 branchpoint = git.rev_parse(args[1])
128
129                 # first, look for branchpoint in well-known branch namespaces
130                 for namespace in ('refs/heads/', 'remotes/'):
131                     # check if branchpoint exists in namespace
132                     try:
133                         maybehead = git.rev_parse(namespace + args[1])
134                     except git.GitException:
135                         maybehead = None
136
137                     # check if git resolved branchpoint to this namespace
138                     if maybehead and branchpoint == maybehead:
139                         # we are for sure referring to a branch
140                         parentbranch = namespace + args[1]
141
142             except git.GitException:
143                 # should use a more specific exception to catch only
144                 # non-git refs ?
145                 out.info('Don\'t know how to determine parent branch'
146                          ' from "%s"' % args[1])
147                 # exception in branch = rev_parse() leaves branchpoint unbound
148                 branchpoint = None
149
150             tree_id = branchpoint or git_id(args[1])
151
152             if parentbranch:
153                 out.info('Recording "%s" as parent branch' % parentbranch)
154             else:
155                 out.info('Don\'t know how to determine parent branch'
156                          ' from "%s"' % args[1])                
157         else:
158             # branch stack off current branch
159             parentbranch = git.get_head_file()
160
161         if parentbranch:
162             parentremote = git.identify_remote(parentbranch)
163             if parentremote:
164                 out.info('Using remote "%s" to pull parent from'
165                          % parentremote)
166             else:
167                 out.info('Recording as a local branch')
168         else:
169             # no known parent branch, can't guess the remote
170             parentremote = None
171
172         stack.Series(args[0]).init(create_at = tree_id,
173                                    parent_remote = parentremote,
174                                    parent_branch = parentbranch)
175
176         out.info('Branch "%s" created' % args[0])
177         return
178
179     elif options.clone:
180
181         if len(args) == 0:
182             clone = crt_series.get_name() + \
183                     time.strftime('-%C%y%m%d-%H%M%S')
184         elif len(args) == 1:
185             clone = args[0]
186         else:
187             parser.error('incorrect number of arguments')
188
189         check_local_changes()
190         check_conflicts()
191         check_head_top_equal()
192
193         out.start('Cloning current branch to "%s"' % clone)
194         crt_series.clone(clone)
195         out.done()
196
197         return
198
199     elif options.delete:
200
201         if len(args) != 1:
202             parser.error('incorrect number of arguments')
203         __delete_branch(args[0], options.force)
204         return
205
206     elif options.list:
207
208         if len(args) != 0:
209             parser.error('incorrect number of arguments')
210
211         branches = []
212         basepath = os.path.join(basedir.get(), 'refs', 'heads')
213         for path, files, dirs in walk_tree(basepath):
214             branches += [os.path.join(path, f) for f in files]
215         branches.sort()
216
217         if branches:
218             out.info('Available branches:')
219             max_len = max([len(i) for i in branches])
220             for i in branches:
221                 __print_branch(i, max_len)
222         else:
223             out.info('No branches')
224         return
225
226     elif options.protect:
227
228         if len(args) == 0:
229             branch_name = crt_series.get_name()
230         elif len(args) == 1:
231             branch_name = args[0]
232         else:
233             parser.error('incorrect number of arguments')
234         branch = stack.Series(branch_name)
235
236         if not branch.is_initialised():
237             raise CmdException, 'Branch "%s" is not controlled by StGIT' \
238                   % branch_name
239
240         out.start('Protecting branch "%s"' % branch_name)
241         branch.protect()
242         out.done()
243
244         return
245
246     elif options.rename:
247
248         if len(args) != 2:
249             parser.error('incorrect number of arguments')
250
251         if __is_current_branch(args[0]):
252             raise CmdException, 'Renaming the current branch is not supported'
253
254         stack.Series(args[0]).rename(args[1])
255
256         out.info('Renamed branch "%s" to "%s"' % (args[0], args[1]))
257
258         return
259
260     elif options.unprotect:
261
262         if len(args) == 0:
263             branch_name = crt_series.get_name()
264         elif len(args) == 1:
265             branch_name = args[0]
266         else:
267             parser.error('incorrect number of arguments')
268         branch = stack.Series(branch_name)
269
270         if not branch.is_initialised():
271             raise CmdException, 'Branch "%s" is not controlled by StGIT' \
272                   % branch_name
273
274         out.info('Unprotecting branch "%s"' % branch_name)
275         branch.unprotect()
276         out.done()
277
278         return
279
280     elif options.description is not None:
281
282         if len(args) == 0:
283             branch_name = crt_series.get_name()
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         out.start('Switching to branch "%s"' % args[0])
309         git.switch_branch(args[0])
310         out.done()
311         return
312
313     # default action: print the current branch
314     if len(args) != 0:
315         parser.error('incorrect number of arguments')
316
317     print crt_series.get_name()