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
25 help = 'Branch operations: switch, list, create, rename, delete, ...'
29 '--create <new-branch> [<committish>]',
30 '--clone [<new-branch>]',
31 '--rename <old-name> <new-name>',
32 '--protect [<branch>]',
33 '--unprotect [<branch>]',
34 '--delete [--force] <branch>',
35 '--description=<description> [<branch>]']
37 Create, clone, switch between, rename, or delete development branches
38 within a git repository.
41 Display the name of the current branch.
43 'stg branch' <branch>::
44 Switch to the given branch."""
47 opt('-l', '--list', action = 'store_true',
48 short = 'List the branches contained in this repository', long = """
49 List each branch in the current repository, followed by its
50 branch description (if any). The current branch is prefixed
51 with '>'. Branches that have been initialized for StGit (with
52 stglink:init[]) are prefixed with 's'. Protected branches are
53 prefixed with 'p'."""),
54 opt('-c', '--create', action = 'store_true',
55 short = 'Create (and switch to) a new branch', long = """
56 Create (and switch to) a new branch. The new branch is already
57 initialized as an StGit patch stack, so you do not have to run
58 stglink:init[] manually. If you give a committish argument,
59 the new branch is based there; otherwise, it is based at the
62 StGit will try to detect the branch off of which the new
63 branch is forked, as well as the remote repository from which
64 that parent branch is taken (if any), so that running
65 stglink:pull[] will automatically pull new commits from the
66 correct branch. It will warn if it cannot guess the parent
67 branch (e.g. if you do not specify a branch name as
69 opt('--clone', action = 'store_true',
70 short = 'Clone the contents of the current branch', long = """
71 Clone the current branch, under the name <new-branch> if
72 specified, or using the current branch's name plus a
75 The description of the new branch is set to tell it is a clone
76 of the current branch. The parent information of the new
77 branch is copied from the current branch."""),
78 opt('-r', '--rename', action = 'store_true',
79 short = 'Rename an existing branch'),
80 opt('-p', '--protect', action = 'store_true',
81 short = 'Prevent StGit from modifying a branch', long = """
82 Prevent StGit from modifying a branch -- either the current
83 one, or one named on the command line."""),
84 opt('-u', '--unprotect', action = 'store_true',
85 short = 'Allow StGit to modify a branch', long = """
86 Allow StGit to modify a branch -- either the current one, or
87 one named on the command line. This undoes the effect of an
88 earlier 'stg branch --protect' command."""),
89 opt('--delete', action = 'store_true',
90 short = 'Delete a branch', long = """
91 Delete the named branch. If there are any patches left in the
92 branch, StGit will refuse to delete it unless you give the
95 A protected branch cannot be deleted; it must be unprotected
96 first (see '--unprotect' above).
98 If you delete the current branch, you are switched to the
99 "master" branch, if it exists."""),
100 opt('-d', '--description', short = 'Set the branch description'),
101 opt('--force', action = 'store_true',
102 short = 'Force a delete when the series is not empty')]
104 directory = DirectoryGotoToplevel()
106 def __is_current_branch(branch_name):
107 return crt_series.get_name() == branch_name
109 def __print_branch(branch_name, length):
114 branch = stack.Series(branch_name)
116 if branch.is_initialised():
118 if __is_current_branch(branch_name):
120 if branch.get_protected():
122 out.stdout(current + ' ' + initialized + protected + '\t'
123 + branch_name.ljust(length) + ' | ' + branch.get_description())
125 def __delete_branch(doomed_name, force = False):
126 doomed = stack.Series(doomed_name)
128 if __is_current_branch(doomed_name):
129 raise CmdException('Cannot delete the current branch')
130 if doomed.get_protected():
131 raise CmdException, 'This branch is protected. Delete is not permitted'
133 out.start('Deleting branch "%s"' % doomed_name)
137 def func(parser, options, args):
141 if len(args) == 0 or len(args) > 2:
142 parser.error('incorrect number of arguments')
144 check_local_changes()
146 check_head_top_equal(crt_series)
152 branchpoint = git.rev_parse(args[1])
155 head_re = re.compile('refs/(heads|remotes)/')
156 ref_re = re.compile(args[1] + '$')
157 for ref in git.all_refs():
158 if head_re.match(ref) and ref_re.search(ref):
159 # args[1] is a valid ref from the branchpoint
161 parentbranch = args[1]
163 except git.GitException:
164 # should use a more specific exception to catch only
166 out.info('Don\'t know how to determine parent branch'
167 ' from "%s"' % args[1])
168 # exception in branch = rev_parse() leaves branchpoint unbound
171 tree_id = git_id(crt_series, branchpoint or args[1])
174 out.info('Recording "%s" as parent branch' % parentbranch)
176 out.info('Don\'t know how to determine parent branch'
177 ' from "%s"' % args[1])
179 # branch stack off current branch
180 parentbranch = git.get_head_file()
183 parentremote = git.identify_remote(parentbranch)
185 out.info('Using remote "%s" to pull parent from'
188 out.info('Recording as a local branch')
190 # no known parent branch, can't guess the remote
193 stack.Series(args[0]).init(create_at = tree_id,
194 parent_remote = parentremote,
195 parent_branch = parentbranch)
197 out.info('Branch "%s" created' % args[0])
203 clone = crt_series.get_name() + \
204 time.strftime('-%C%y%m%d-%H%M%S')
208 parser.error('incorrect number of arguments')
210 check_local_changes()
212 check_head_top_equal(crt_series)
214 out.start('Cloning current branch to "%s"' % clone)
215 crt_series.clone(clone)
223 parser.error('incorrect number of arguments')
224 __delete_branch(args[0], options.force)
230 parser.error('incorrect number of arguments')
232 branches = git.get_heads()
236 out.info('Available branches:')
237 max_len = max([len(i) for i in branches])
239 __print_branch(i, max_len)
241 out.info('No branches')
244 elif options.protect:
247 branch_name = crt_series.get_name()
249 branch_name = args[0]
251 parser.error('incorrect number of arguments')
252 branch = stack.Series(branch_name)
254 if not branch.is_initialised():
255 raise CmdException, 'Branch "%s" is not controlled by StGIT' \
258 out.start('Protecting branch "%s"' % branch_name)
267 parser.error('incorrect number of arguments')
269 if __is_current_branch(args[0]):
270 raise CmdException, 'Renaming the current branch is not supported'
272 stack.Series(args[0]).rename(args[1])
274 out.info('Renamed branch "%s" to "%s"' % (args[0], args[1]))
278 elif options.unprotect:
281 branch_name = crt_series.get_name()
283 branch_name = args[0]
285 parser.error('incorrect number of arguments')
286 branch = stack.Series(branch_name)
288 if not branch.is_initialised():
289 raise CmdException, 'Branch "%s" is not controlled by StGIT' \
292 out.info('Unprotecting branch "%s"' % branch_name)
298 elif options.description is not None:
301 branch_name = crt_series.get_name()
303 branch_name = args[0]
305 parser.error('incorrect number of arguments')
306 branch = stack.Series(branch_name)
308 if not branch.is_initialised():
309 raise CmdException, 'Branch "%s" is not controlled by StGIT' \
312 branch.set_description(options.description)
318 if __is_current_branch(args[0]):
319 raise CmdException, 'Branch "%s" is already the current branch' \
322 check_local_changes()
324 check_head_top_equal(crt_series)
326 out.start('Switching to branch "%s"' % args[0])
327 git.switch_branch(args[0])
331 # default action: print the current branch
333 parser.error('incorrect number of arguments')
335 print crt_series.get_name()