3d912fc3f8ce4938900200ba6e1fd5b205c9c64f
[stgit] / stgit / commands / branch.py
1 __copyright__ = """
2 Copyright (C) 2005, Chuck Lever <cel@netapp.com>
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License version 2 as
6 published by the Free Software Foundation.
7
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License
14 along with this program; if not, write to the Free Software
15 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16 """
17
18 import sys, os, time, re
19 from stgit.argparse import opt
20 from stgit.commands.common import *
21 from stgit.utils import *
22 from stgit.out import *
23 from stgit import argparse, stack, git, basedir
24 from stgit.lib import log
25
26 help = 'Branch operations: switch, list, create, rename, delete, ...'
27 kind = 'stack'
28 usage = ['',
29 '<branch>',
30 '--list',
31 '--create <new-branch> [<committish>]',
32 '--clone [<new-branch>]',
33 '--rename <old-name> <new-name>',
34 '--protect [<branch>]',
35 '--unprotect [<branch>]',
36 '--delete [--force] <branch>',
37 '--description=<description> [<branch>]']
38 description = """
39 Create, clone, switch between, rename, or delete development branches
40 within a git repository.
41
42 'stg branch'::
43 Display the name of the current branch.
44
45 'stg branch' <branch>::
46 Switch to the given branch."""
47
48 args = [argparse.all_branches]
49 options = [
50 opt('-l', '--list', action = 'store_true',
51 short = 'List the branches contained in this repository', long = """
52 List each branch in the current repository, followed by its
53 branch description (if any). The current branch is prefixed
54 with '>'. Branches that have been initialized for StGit (with
55 stglink:init[]) are prefixed with 's'. Protected branches are
56 prefixed with 'p'."""),
57 opt('-c', '--create', action = 'store_true',
58 short = 'Create (and switch to) a new branch', long = """
59 Create (and switch to) a new branch. The new branch is already
60 initialized as an StGit patch stack, so you do not have to run
61 stglink:init[] manually. If you give a committish argument,
62 the new branch is based there; otherwise, it is based at the
63 current HEAD.
64
65 StGit will try to detect the branch off of which the new
66 branch is forked, as well as the remote repository from which
67 that parent branch is taken (if any), so that running
68 stglink:pull[] will automatically pull new commits from the
69 correct branch. It will warn if it cannot guess the parent
70 branch (e.g. if you do not specify a branch name as
71 committish)."""),
72 opt('--clone', action = 'store_true',
73 short = 'Clone the contents of the current branch', long = """
74 Clone the current branch, under the name <new-branch> if
75 specified, or using the current branch's name plus a
76 timestamp.
77
78 The description of the new branch is set to tell it is a clone
79 of the current branch. The parent information of the new
80 branch is copied from the current branch."""),
81 opt('-r', '--rename', action = 'store_true',
82 short = 'Rename an existing branch'),
83 opt('-p', '--protect', action = 'store_true',
84 short = 'Prevent StGit from modifying a branch', long = """
85 Prevent StGit from modifying a branch -- either the current
86 one, or one named on the command line."""),
87 opt('-u', '--unprotect', action = 'store_true',
88 short = 'Allow StGit to modify a branch', long = """
89 Allow StGit to modify a branch -- either the current one, or
90 one named on the command line. This undoes the effect of an
91 earlier 'stg branch --protect' command."""),
92 opt('--delete', action = 'store_true',
93 short = 'Delete a branch', long = """
94 Delete the named branch. If there are any patches left in the
95 branch, StGit will refuse to delete it unless you give the
96 '--force' flag.
97
98 A protected branch cannot be deleted; it must be unprotected
99 first (see '--unprotect' above).
100
101 If you delete the current branch, you are switched to the
102 "master" branch, if it exists."""),
103 opt('-d', '--description', short = 'Set the branch description'),
104 opt('--force', action = 'store_true',
105 short = 'Force a delete when the series is not empty')]
106
107 directory = DirectoryGotoToplevel(log = False)
108
109 def __is_current_branch(branch_name):
110 return crt_series.get_name() == branch_name
111
112 def __print_branch(branch_name, length):
113 initialized = ' '
114 current = ' '
115 protected = ' '
116
117 branch = stack.Series(branch_name)
118
119 if branch.is_initialised():
120 initialized = 's'
121 if __is_current_branch(branch_name):
122 current = '>'
123 if branch.get_protected():
124 protected = 'p'
125 out.stdout(current + ' ' + initialized + protected + '\t'
126 + branch_name.ljust(length) + ' | ' + branch.get_description())
127
128 def __delete_branch(doomed_name, force = False):
129 doomed = stack.Series(doomed_name)
130
131 if __is_current_branch(doomed_name):
132 raise CmdException('Cannot delete the current branch')
133 if doomed.get_protected():
134 raise CmdException, 'This branch is protected. Delete is not permitted'
135
136 out.start('Deleting branch "%s"' % doomed_name)
137 doomed.delete(force)
138 out.done()
139
140 def func(parser, options, args):
141
142 if options.create:
143
144 if len(args) == 0 or len(args) > 2:
145 parser.error('incorrect number of arguments')
146
147 check_local_changes()
148 check_conflicts()
149 check_head_top_equal(crt_series)
150
151 tree_id = None
152 if len(args) >= 2:
153 parentbranch = None
154 try:
155 branchpoint = git.rev_parse(args[1])
156
157 # parent branch?
158 head_re = re.compile('refs/(heads|remotes)/')
159 ref_re = re.compile(args[1] + '$')
160 for ref in git.all_refs():
161 if head_re.match(ref) and ref_re.search(ref):
162 # args[1] is a valid ref from the branchpoint
163 # setting above
164 parentbranch = args[1]
165 break;
166 except git.GitException:
167 # should use a more specific exception to catch only
168 # non-git refs ?
169 out.info('Don\'t know how to determine parent branch'
170 ' from "%s"' % args[1])
171 # exception in branch = rev_parse() leaves branchpoint unbound
172 branchpoint = None
173
174 tree_id = git_id(crt_series, branchpoint or args[1])
175
176 if parentbranch:
177 out.info('Recording "%s" as parent branch' % parentbranch)
178 else:
179 out.info('Don\'t know how to determine parent branch'
180 ' from "%s"' % args[1])
181 else:
182 # branch stack off current branch
183 parentbranch = git.get_head_file()
184
185 if parentbranch:
186 parentremote = git.identify_remote(parentbranch)
187 if parentremote:
188 out.info('Using remote "%s" to pull parent from'
189 % parentremote)
190 else:
191 out.info('Recording as a local branch')
192 else:
193 # no known parent branch, can't guess the remote
194 parentremote = None
195
196 stack.Series(args[0]).init(create_at = tree_id,
197 parent_remote = parentremote,
198 parent_branch = parentbranch)
199
200 out.info('Branch "%s" created' % args[0])
201 log.compat_log_entry('branch --create')
202 return
203
204 elif options.clone:
205
206 if len(args) == 0:
207 clone = crt_series.get_name() + \
208 time.strftime('-%C%y%m%d-%H%M%S')
209 elif len(args) == 1:
210 clone = args[0]
211 else:
212 parser.error('incorrect number of arguments')
213
214 check_local_changes()
215 check_conflicts()
216 check_head_top_equal(crt_series)
217
218 out.start('Cloning current branch to "%s"' % clone)
219 crt_series.clone(clone)
220 out.done()
221
222 log.copy_log(log.default_repo(), crt_series.get_name(), clone,
223 'branch --clone')
224 return
225
226 elif options.delete:
227
228 if len(args) != 1:
229 parser.error('incorrect number of arguments')
230 __delete_branch(args[0], options.force)
231 log.delete_log(log.default_repo(), args[0])
232 return
233
234 elif options.list:
235
236 if len(args) != 0:
237 parser.error('incorrect number of arguments')
238
239 branches = set(git.get_heads())
240 for br in set(branches):
241 m = re.match(r'^(.*)\.stgit$', br)
242 if m and m.group(1) in branches:
243 branches.remove(br)
244
245 if branches:
246 out.info('Available branches:')
247 max_len = max([len(i) for i in branches])
248 for i in sorted(branches):
249 __print_branch(i, max_len)
250 else:
251 out.info('No branches')
252 return
253
254 elif options.protect:
255
256 if len(args) == 0:
257 branch_name = crt_series.get_name()
258 elif len(args) == 1:
259 branch_name = args[0]
260 else:
261 parser.error('incorrect number of arguments')
262 branch = stack.Series(branch_name)
263
264 if not branch.is_initialised():
265 raise CmdException, 'Branch "%s" is not controlled by StGIT' \
266 % branch_name
267
268 out.start('Protecting branch "%s"' % branch_name)
269 branch.protect()
270 out.done()
271
272 return
273
274 elif options.rename:
275
276 if len(args) != 2:
277 parser.error('incorrect number of arguments')
278
279 if __is_current_branch(args[0]):
280 raise CmdException, 'Renaming the current branch is not supported'
281
282 stack.Series(args[0]).rename(args[1])
283
284 out.info('Renamed branch "%s" to "%s"' % (args[0], args[1]))
285 log.rename_log(log.default_repo(), args[0], args[1], 'branch --rename')
286 return
287
288 elif options.unprotect:
289
290 if len(args) == 0:
291 branch_name = crt_series.get_name()
292 elif len(args) == 1:
293 branch_name = args[0]
294 else:
295 parser.error('incorrect number of arguments')
296 branch = stack.Series(branch_name)
297
298 if not branch.is_initialised():
299 raise CmdException, 'Branch "%s" is not controlled by StGIT' \
300 % branch_name
301
302 out.info('Unprotecting branch "%s"' % branch_name)
303 branch.unprotect()
304 out.done()
305
306 return
307
308 elif options.description is not None:
309
310 if len(args) == 0:
311 branch_name = crt_series.get_name()
312 elif len(args) == 1:
313 branch_name = args[0]
314 else:
315 parser.error('incorrect number of arguments')
316 branch = stack.Series(branch_name)
317
318 if not branch.is_initialised():
319 raise CmdException, 'Branch "%s" is not controlled by StGIT' \
320 % branch_name
321
322 branch.set_description(options.description)
323
324 return
325
326 elif len(args) == 1:
327
328 if __is_current_branch(args[0]):
329 raise CmdException, 'Branch "%s" is already the current branch' \
330 % args[0]
331
332 check_local_changes()
333 check_conflicts()
334 check_head_top_equal(crt_series)
335
336 out.start('Switching to branch "%s"' % args[0])
337 git.switch_branch(args[0])
338 out.done()
339 return
340
341 # default action: print the current branch
342 if len(args) != 0:
343 parser.error('incorrect number of arguments')
344
345 print crt_series.get_name()