chiark / gitweb /
Some clean-up of the branch manipulation commands
[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, re
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 directory = DirectoryGotoToplevel()
44 options = [make_option('-c', '--create',
45                        help = 'create a new development branch',
46                        action = 'store_true'),
47            make_option('--clone',
48                        help = 'clone the contents of the current branch',
49                        action = 'store_true'),
50            make_option('--delete',
51                        help = 'delete an existing development branch',
52                        action = 'store_true'),
53            make_option('-d', '--description',
54                        help = 'set the branch description'),
55            make_option('--force',
56                        help = 'force a delete when the series is not empty',
57                        action = 'store_true'),
58            make_option('-l', '--list',
59                        help = 'list branches contained in this repository',
60                        action = 'store_true'),
61            make_option('-p', '--protect',
62                        help = 'prevent StGIT from modifying this branch',
63                        action = 'store_true'),
64            make_option('-r', '--rename',
65                        help = 'rename an existing development branch',
66                        action = 'store_true'),
67            make_option('-u', '--unprotect',
68                        help = 'allow StGIT to modify this branch',
69                        action = 'store_true')]
70
71
72 def __is_current_branch(branch_name):
73     return crt_series.get_name() == branch_name
74
75 def __print_branch(branch_name, length):
76     initialized = ' '
77     current = ' '
78     protected = ' '
79
80     branch = stack.Series(branch_name)
81
82     if branch.is_initialised():
83         initialized = 's'
84     if __is_current_branch(branch_name):
85         current = '>'
86     if branch.get_protected():
87         protected = 'p'
88     out.stdout(current + ' ' + initialized + protected + '\t'
89                + branch_name.ljust(length) + '  | ' + branch.get_description())
90
91 def __delete_branch(doomed_name, force = False):
92     doomed = stack.Series(doomed_name)
93
94     if __is_current_branch(doomed_name):
95         raise CmdException('Cannot delete the current branch')
96     if doomed.get_protected():
97         raise CmdException, 'This branch is protected. Delete is not permitted'
98
99     out.start('Deleting branch "%s"' % doomed_name)
100     doomed.delete(force)
101     out.done()
102
103 def func(parser, options, args):
104
105     if options.create:
106
107         if len(args) == 0 or len(args) > 2:
108             parser.error('incorrect number of arguments')
109
110         check_local_changes()
111         check_conflicts()
112         check_head_top_equal(crt_series)
113
114         tree_id = None
115         if len(args) >= 2:
116             parentbranch = None
117             try:
118                 branchpoint = git.rev_parse(args[1])
119
120                 # parent branch?
121                 head_re = re.compile('refs/(heads|remotes)/')
122                 ref_re = re.compile(args[1] + '$')
123                 for ref in git.all_refs():
124                     if head_re.match(ref) and ref_re.search(ref):
125                         # args[1] is a valid ref from the branchpoint
126                         # setting above
127                         parentbranch = args[1]
128                         break;
129             except git.GitException:
130                 # should use a more specific exception to catch only
131                 # non-git refs ?
132                 out.info('Don\'t know how to determine parent branch'
133                          ' from "%s"' % args[1])
134                 # exception in branch = rev_parse() leaves branchpoint unbound
135                 branchpoint = None
136
137             tree_id = git_id(crt_series, branchpoint or args[1])
138
139             if parentbranch:
140                 out.info('Recording "%s" as parent branch' % parentbranch)
141             else:
142                 out.info('Don\'t know how to determine parent branch'
143                          ' from "%s"' % 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                 out.info('Using remote "%s" to pull parent from'
152                          % parentremote)
153             else:
154                 out.info('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         out.info('Branch "%s" created' % args[0])
164         return
165
166     elif options.clone:
167
168         if len(args) == 0:
169             clone = crt_series.get_name() + \
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(crt_series)
179
180         out.start('Cloning current branch to "%s"' % clone)
181         crt_series.clone(clone)
182         out.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 = git.get_heads()
199         branches.sort()
200
201         if branches:
202             out.info('Available branches:')
203             max_len = max([len(i) for i in branches])
204             for i in branches:
205                 __print_branch(i, max_len)
206         else:
207             out.info('No branches')
208         return
209
210     elif options.protect:
211
212         if len(args) == 0:
213             branch_name = crt_series.get_name()
214         elif len(args) == 1:
215             branch_name = args[0]
216         else:
217             parser.error('incorrect number of arguments')
218         branch = stack.Series(branch_name)
219
220         if not branch.is_initialised():
221             raise CmdException, 'Branch "%s" is not controlled by StGIT' \
222                   % branch_name
223
224         out.start('Protecting branch "%s"' % branch_name)
225         branch.protect()
226         out.done()
227
228         return
229
230     elif options.rename:
231
232         if len(args) != 2:
233             parser.error('incorrect number of arguments')
234
235         if __is_current_branch(args[0]):
236             raise CmdException, 'Renaming the current branch is not supported'
237
238         stack.Series(args[0]).rename(args[1])
239
240         out.info('Renamed branch "%s" to "%s"' % (args[0], args[1]))
241
242         return
243
244     elif options.unprotect:
245
246         if len(args) == 0:
247             branch_name = crt_series.get_name()
248         elif len(args) == 1:
249             branch_name = args[0]
250         else:
251             parser.error('incorrect number of arguments')
252         branch = stack.Series(branch_name)
253
254         if not branch.is_initialised():
255             raise CmdException, 'Branch "%s" is not controlled by StGIT' \
256                   % branch_name
257
258         out.info('Unprotecting branch "%s"' % branch_name)
259         branch.unprotect()
260         out.done()
261
262         return
263
264     elif options.description is not None:
265
266         if len(args) == 0:
267             branch_name = crt_series.get_name()
268         elif len(args) == 1:
269             branch_name = args[0]
270         else:
271             parser.error('incorrect number of arguments')
272         branch = stack.Series(branch_name)
273
274         if not branch.is_initialised():
275             raise CmdException, 'Branch "%s" is not controlled by StGIT' \
276                   % branch_name
277
278         branch.set_description(options.description)
279
280         return
281
282     elif len(args) == 1:
283
284         if __is_current_branch(args[0]):
285             raise CmdException, 'Branch "%s" is already the current branch' \
286                   % args[0]
287
288         check_local_changes()
289         check_conflicts()
290         check_head_top_equal(crt_series)
291
292         out.start('Switching to branch "%s"' % args[0])
293         git.switch_branch(args[0])
294         out.done()
295         return
296
297     # default action: print the current branch
298     if len(args) != 0:
299         parser.error('incorrect number of arguments')
300
301     print crt_series.get_name()