chiark / gitweb /
5f79f7ed87dee54ceccb163f2ceb180b85deea5e
[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('--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_branch() == 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     print 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     print 'Deleting branch "%s"...' % doomed_name,
97     sys.stdout.flush()
98
99     if __is_current_branch(doomed_name):
100         check_local_changes()
101         check_conflicts()
102         check_head_top_equal()
103
104         if doomed_name != 'master':
105             git.switch_branch('master')
106
107     doomed.delete(force)
108
109     if doomed_name != 'master':
110         git.delete_branch(doomed_name)
111
112     print 'done'
113
114 def func(parser, options, args):
115
116     if options.create:
117
118         if len(args) == 0 or len(args) > 2:
119             parser.error('incorrect number of arguments')
120
121         check_local_changes()
122         check_conflicts()
123         check_head_top_equal()
124
125         tree_id = None
126         if len(args) >= 2:
127             try:
128                 if git.rev_parse(args[1]) == git.rev_parse('refs/heads/' + args[1]):
129                     # we are for sure referring to a branch
130                     parentbranch = 'refs/heads/' + args[1]
131                     print 'Recording "%s" as parent branch.' % parentbranch
132                 elif git.rev_parse(args[1]) and re.search('/', args[1]):
133                     # FIXME: should the test be more strict ?
134                     parentbranch = args[1]
135                 else:
136                     # Note: this includes refs to StGIT patches
137                     print 'Don\'t know how to determine parent branch from "%s".' % args[1]
138                     parentbranch = None
139             except git.GitException:
140                 # should use a more specific exception to catch only non-git refs ?
141                 print 'Don\'t know how to determine parent branch from "%s".' % args[1]
142                 parentbranch = None
143
144             tree_id = git_id(args[1])
145         else:
146             # branch stack off current branch
147             parentbranch = git.get_head_file()
148
149         if parentbranch:
150             parentremote = git.identify_remote(parentbranch)
151             if parentremote:
152                 print 'Using "%s" remote to pull parent from.' % parentremote
153             else:
154                 print 'Recording as a local branch.'
155         else:
156             # no known parent branch, can't guess the remote
157             parentremote = None
158
159         stack.Series(args[0]).init(create_at = tree_id,
160                                    parent_remote = parentremote,
161                                    parent_branch = parentbranch)
162
163         print 'Branch "%s" created.' % args[0]
164         return
165
166     elif options.clone:
167
168         if len(args) == 0:
169             clone = crt_series.get_branch() + \
170                     time.strftime('-%C%y%m%d-%H%M%S')
171         elif len(args) == 1:
172             clone = args[0]
173         else:
174             parser.error('incorrect number of arguments')
175
176         check_local_changes()
177         check_conflicts()
178         check_head_top_equal()
179
180         print 'Cloning current branch to "%s"...' % clone,
181         sys.stdout.flush()
182         crt_series.clone(clone)
183         print 'done'
184
185         return
186
187     elif options.convert:
188
189         if len(args) != 0:
190             parser.error('incorrect number of arguments')
191
192         crt_series.convert()
193         return
194
195     elif options.delete:
196
197         if len(args) != 1:
198             parser.error('incorrect number of arguments')
199         __delete_branch(args[0], options.force)
200         return
201
202     elif options.list:
203
204         if len(args) != 0:
205             parser.error('incorrect number of arguments')
206
207         branches = []
208         basepath = os.path.join(basedir.get(), 'refs', 'heads')
209         for path, files, dirs in walk_tree(basepath):
210             branches += [os.path.join(path, f) for f in files]
211         branches.sort()
212
213         if branches:
214             print 'Available branches:'
215             max_len = max([len(i) for i in branches])
216             for i in branches:
217                 __print_branch(i, max_len)
218         else:
219             print 'No branches'
220         return
221
222     elif options.protect:
223
224         if len(args) == 0:
225             branch_name = crt_series.get_branch()
226         elif len(args) == 1:
227             branch_name = args[0]
228         else:
229             parser.error('incorrect number of arguments')
230         branch = stack.Series(branch_name)
231
232         if not branch.is_initialised():
233             raise CmdException, 'Branch "%s" is not controlled by StGIT' \
234                   % branch_name
235
236         print 'Protecting branch "%s"...' % branch_name,
237         sys.stdout.flush()
238         branch.protect()
239         print '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         print 'Renamed branch "%s" as "%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_branch()
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         print 'Unprotecting branch "%s"...' % branch_name,
272         sys.stdout.flush()
273         branch.unprotect()
274         print 'done'
275
276         return
277
278     elif len(args) == 1:
279
280         if __is_current_branch(args[0]):
281             raise CmdException, 'Branch "%s" is already the current branch' \
282                   % args[0]
283
284         check_local_changes()
285         check_conflicts()
286         check_head_top_equal()
287
288         print 'Switching to branch "%s"...' % args[0],
289         sys.stdout.flush()
290
291         git.switch_branch(args[0])
292
293         print 'done'
294         return
295
296     # default action: print the current branch
297     if len(args) != 0:
298         parser.error('incorrect number of arguments')
299
300     print crt_series.get_branch()