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