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