chiark / gitweb /
75a9046ad95f41baad5863d8337dd0a736627314
[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 = git.get_heads()
212         branches.sort()
213
214         if branches:
215             out.info('Available branches:')
216             max_len = max([len(i) for i in branches])
217             for i in branches:
218                 __print_branch(i, max_len)
219         else:
220             out.info('No branches')
221         return
222
223     elif options.protect:
224
225         if len(args) == 0:
226             branch_name = crt_series.get_name()
227         elif len(args) == 1:
228             branch_name = args[0]
229         else:
230             parser.error('incorrect number of arguments')
231         branch = stack.Series(branch_name)
232
233         if not branch.is_initialised():
234             raise CmdException, 'Branch "%s" is not controlled by StGIT' \
235                   % branch_name
236
237         out.start('Protecting branch "%s"' % branch_name)
238         branch.protect()
239         out.done()
240
241         return
242
243     elif options.rename:
244
245         if len(args) != 2:
246             parser.error('incorrect number of arguments')
247
248         if __is_current_branch(args[0]):
249             raise CmdException, 'Renaming the current branch is not supported'
250
251         stack.Series(args[0]).rename(args[1])
252
253         out.info('Renamed branch "%s" to "%s"' % (args[0], args[1]))
254
255         return
256
257     elif options.unprotect:
258
259         if len(args) == 0:
260             branch_name = crt_series.get_name()
261         elif len(args) == 1:
262             branch_name = args[0]
263         else:
264             parser.error('incorrect number of arguments')
265         branch = stack.Series(branch_name)
266
267         if not branch.is_initialised():
268             raise CmdException, 'Branch "%s" is not controlled by StGIT' \
269                   % branch_name
270
271         out.info('Unprotecting branch "%s"' % branch_name)
272         branch.unprotect()
273         out.done()
274
275         return
276
277     elif options.description is not None:
278
279         if len(args) == 0:
280             branch_name = crt_series.get_name()
281         elif len(args) == 1:
282             branch_name = args[0]
283         else:
284             parser.error('incorrect number of arguments')
285         branch = stack.Series(branch_name)
286
287         if not branch.is_initialised():
288             raise CmdException, 'Branch "%s" is not controlled by StGIT' \
289                   % branch_name
290
291         branch.set_description(options.description)
292
293         return
294
295     elif len(args) == 1:
296
297         if __is_current_branch(args[0]):
298             raise CmdException, 'Branch "%s" is already the current branch' \
299                   % args[0]
300
301         check_local_changes()
302         check_conflicts()
303         check_head_top_equal()
304
305         out.start('Switching to branch "%s"' % args[0])
306         git.switch_branch(args[0])
307         out.done()
308         return
309
310     # default action: print the current branch
311     if len(args) != 0:
312         parser.error('incorrect number of arguments')
313
314     print crt_series.get_name()