2 Copyright (C) 2005, Chuck Lever <cel@netapp.com>
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.
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.
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
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 stack, git, basedir
24 from stgit.lib import log
26 help = 'Branch operations: switch, list, create, rename, delete, ...'
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>]']
39 Create, clone, switch between, rename, or delete development branches
40 within a git repository.
43 Display the name of the current branch.
45 'stg branch' <branch>::
46 Switch to the given branch."""
49 opt('-l', '--list', action = 'store_true',
50 short = 'List the branches contained in this repository', long = """
51 List each branch in the current repository, followed by its
52 branch description (if any). The current branch is prefixed
53 with '>'. Branches that have been initialized for StGit (with
54 stglink:init[]) are prefixed with 's'. Protected branches are
55 prefixed with 'p'."""),
56 opt('-c', '--create', action = 'store_true',
57 short = 'Create (and switch to) a new branch', long = """
58 Create (and switch to) a new branch. The new branch is already
59 initialized as an StGit patch stack, so you do not have to run
60 stglink:init[] manually. If you give a committish argument,
61 the new branch is based there; otherwise, it is based at the
64 StGit will try to detect the branch off of which the new
65 branch is forked, as well as the remote repository from which
66 that parent branch is taken (if any), so that running
67 stglink:pull[] will automatically pull new commits from the
68 correct branch. It will warn if it cannot guess the parent
69 branch (e.g. if you do not specify a branch name as
71 opt('--clone', action = 'store_true',
72 short = 'Clone the contents of the current branch', long = """
73 Clone the current branch, under the name <new-branch> if
74 specified, or using the current branch's name plus a
77 The description of the new branch is set to tell it is a clone
78 of the current branch. The parent information of the new
79 branch is copied from the current branch."""),
80 opt('-r', '--rename', action = 'store_true',
81 short = 'Rename an existing branch'),
82 opt('-p', '--protect', action = 'store_true',
83 short = 'Prevent StGit from modifying a branch', long = """
84 Prevent StGit from modifying a branch -- either the current
85 one, or one named on the command line."""),
86 opt('-u', '--unprotect', action = 'store_true',
87 short = 'Allow StGit to modify a branch', long = """
88 Allow StGit to modify a branch -- either the current one, or
89 one named on the command line. This undoes the effect of an
90 earlier 'stg branch --protect' command."""),
91 opt('--delete', action = 'store_true',
92 short = 'Delete a branch', long = """
93 Delete the named branch. If there are any patches left in the
94 branch, StGit will refuse to delete it unless you give the
97 A protected branch cannot be deleted; it must be unprotected
98 first (see '--unprotect' above).
100 If you delete the current branch, you are switched to the
101 "master" branch, if it exists."""),
102 opt('-d', '--description', short = 'Set the branch description'),
103 opt('--force', action = 'store_true',
104 short = 'Force a delete when the series is not empty')]
106 directory = DirectoryGotoToplevel(log = False)
108 def __is_current_branch(branch_name):
109 return crt_series.get_name() == branch_name
111 def __print_branch(branch_name, length):
116 branch = stack.Series(branch_name)
118 if branch.is_initialised():
120 if __is_current_branch(branch_name):
122 if branch.get_protected():
124 out.stdout(current + ' ' + initialized + protected + '\t'
125 + branch_name.ljust(length) + ' | ' + branch.get_description())
127 def __delete_branch(doomed_name, force = False):
128 doomed = stack.Series(doomed_name)
130 if __is_current_branch(doomed_name):
131 raise CmdException('Cannot delete the current branch')
132 if doomed.get_protected():
133 raise CmdException, 'This branch is protected. Delete is not permitted'
135 out.start('Deleting branch "%s"' % doomed_name)
139 def func(parser, options, args):
143 if len(args) == 0 or len(args) > 2:
144 parser.error('incorrect number of arguments')
146 check_local_changes()
148 check_head_top_equal(crt_series)
154 branchpoint = git.rev_parse(args[1])
157 head_re = re.compile('refs/(heads|remotes)/')
158 ref_re = re.compile(args[1] + '$')
159 for ref in git.all_refs():
160 if head_re.match(ref) and ref_re.search(ref):
161 # args[1] is a valid ref from the branchpoint
163 parentbranch = args[1]
165 except git.GitException:
166 # should use a more specific exception to catch only
168 out.info('Don\'t know how to determine parent branch'
169 ' from "%s"' % args[1])
170 # exception in branch = rev_parse() leaves branchpoint unbound
173 tree_id = git_id(crt_series, branchpoint or args[1])
176 out.info('Recording "%s" as parent branch' % parentbranch)
178 out.info('Don\'t know how to determine parent branch'
179 ' from "%s"' % args[1])
181 # branch stack off current branch
182 parentbranch = git.get_head_file()
185 parentremote = git.identify_remote(parentbranch)
187 out.info('Using remote "%s" to pull parent from'
190 out.info('Recording as a local branch')
192 # no known parent branch, can't guess the remote
195 stack.Series(args[0]).init(create_at = tree_id,
196 parent_remote = parentremote,
197 parent_branch = parentbranch)
199 out.info('Branch "%s" created' % args[0])
200 log.compat_log_entry('branch --create')
206 clone = crt_series.get_name() + \
207 time.strftime('-%C%y%m%d-%H%M%S')
211 parser.error('incorrect number of arguments')
213 check_local_changes()
215 check_head_top_equal(crt_series)
217 out.start('Cloning current branch to "%s"' % clone)
218 crt_series.clone(clone)
221 log.copy_log(log.default_repo(), crt_series.get_name(), clone,
228 parser.error('incorrect number of arguments')
229 __delete_branch(args[0], options.force)
230 log.delete_log(log.default_repo(), args[0])
236 parser.error('incorrect number of arguments')
238 branches = set(git.get_heads())
239 for br in set(branches):
240 m = re.match(r'^(.*)\.stgit$', br)
241 if m and m.group(1) in branches:
245 out.info('Available branches:')
246 max_len = max([len(i) for i in branches])
247 for i in sorted(branches):
248 __print_branch(i, max_len)
250 out.info('No branches')
253 elif options.protect:
256 branch_name = crt_series.get_name()
258 branch_name = args[0]
260 parser.error('incorrect number of arguments')
261 branch = stack.Series(branch_name)
263 if not branch.is_initialised():
264 raise CmdException, 'Branch "%s" is not controlled by StGIT' \
267 out.start('Protecting branch "%s"' % branch_name)
276 parser.error('incorrect number of arguments')
278 if __is_current_branch(args[0]):
279 raise CmdException, 'Renaming the current branch is not supported'
281 stack.Series(args[0]).rename(args[1])
283 out.info('Renamed branch "%s" to "%s"' % (args[0], args[1]))
284 log.rename_log(log.default_repo(), args[0], args[1], 'branch --rename')
287 elif options.unprotect:
290 branch_name = crt_series.get_name()
292 branch_name = args[0]
294 parser.error('incorrect number of arguments')
295 branch = stack.Series(branch_name)
297 if not branch.is_initialised():
298 raise CmdException, 'Branch "%s" is not controlled by StGIT' \
301 out.info('Unprotecting branch "%s"' % branch_name)
307 elif options.description is not None:
310 branch_name = crt_series.get_name()
312 branch_name = args[0]
314 parser.error('incorrect number of arguments')
315 branch = stack.Series(branch_name)
317 if not branch.is_initialised():
318 raise CmdException, 'Branch "%s" is not controlled by StGIT' \
321 branch.set_description(options.description)
327 if __is_current_branch(args[0]):
328 raise CmdException, 'Branch "%s" is already the current branch' \
331 check_local_changes()
333 check_head_top_equal(crt_series)
335 out.start('Switching to branch "%s"' % args[0])
336 git.switch_branch(args[0])
340 # default action: print the current branch
342 parser.error('incorrect number of arguments')
344 print crt_series.get_name()