chiark / gitweb /
d7ddedba73f097a53dcb0126753290845b8a848b
[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 development branches'
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 "stg pull" 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 "stg pull" 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 'Not identified a remote to pull parent from.'
155         else:
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.convert:
187
188         if len(args) != 0:
189             parser.error('incorrect number of arguments')
190
191         crt_series.convert()
192         return
193
194     elif options.delete:
195
196         if len(args) != 1:
197             parser.error('incorrect number of arguments')
198         __delete_branch(args[0], options.force)
199         return
200
201     elif options.list:
202
203         if len(args) != 0:
204             parser.error('incorrect number of arguments')
205
206         branches = []
207         basepath = os.path.join(basedir.get(), 'refs', 'heads')
208         for path, files, dirs in walk_tree(basepath):
209             branches += [os.path.join(path, f) for f in files]
210         branches.sort()
211
212         if branches:
213             print 'Available branches:'
214             max_len = max([len(i) for i in branches])
215             for i in branches:
216                 __print_branch(i, max_len)
217         else:
218             print 'No branches'
219         return
220
221     elif options.protect:
222
223         if len(args) == 0:
224             branch_name = crt_series.get_branch()
225         elif len(args) == 1:
226             branch_name = args[0]
227         else:
228             parser.error('incorrect number of arguments')
229         branch = stack.Series(branch_name)
230
231         if not branch.is_initialised():
232             raise CmdException, 'Branch "%s" is not controlled by StGIT' \
233                   % branch_name
234
235         print 'Protecting branch "%s"...' % branch_name,
236         sys.stdout.flush()
237         branch.protect()
238         print 'done'
239
240         return
241
242     elif options.rename:
243
244         if len(args) != 2:
245             parser.error('incorrect number of arguments')
246
247         if __is_current_branch(args[0]):
248             raise CmdException, 'Renaming the current branch is not supported'
249
250         stack.Series(args[0]).rename(args[1])
251
252         print 'Renamed branch "%s" as "%s".' % (args[0], args[1])
253
254         return
255
256     elif options.unprotect:
257
258         if len(args) == 0:
259             branch_name = crt_series.get_branch()
260         elif len(args) == 1:
261             branch_name = args[0]
262         else:
263             parser.error('incorrect number of arguments')
264         branch = stack.Series(branch_name)
265
266         if not branch.is_initialised():
267             raise CmdException, 'Branch "%s" is not controlled by StGIT' \
268                   % branch_name
269
270         print 'Unprotecting branch "%s"...' % branch_name,
271         sys.stdout.flush()
272         branch.unprotect()
273         print 'done'
274
275         return
276
277     elif len(args) == 1:
278
279         if __is_current_branch(args[0]):
280             raise CmdException, 'Branch "%s" is already the current branch' \
281                   % args[0]
282
283         check_local_changes()
284         check_conflicts()
285         check_head_top_equal()
286
287         print 'Switching to branch "%s"...' % args[0],
288         sys.stdout.flush()
289
290         git.switch_branch(args[0])
291
292         print 'done'
293         return
294
295     # default action: print the current branch
296     if len(args) != 0:
297         parser.error('incorrect number of arguments')
298
299     print crt_series.get_branch()