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, ...'
30 '--create <new-branch> [<committish>]',
31 '--clone [<new-branch>]',
32 '--rename <old-name> <new-name>',
33 '--protect [<branch>]',
34 '--unprotect [<branch>]',
35 '--delete [--force] <branch>',
36 '--description=<description> [<branch>]']
38 Create, clone, switch between, rename, or delete development branches
39 within a git repository.
42 Display the name of the current branch.
44 'stg branch' <branch>::
45 Switch to the given branch."""
48 opt('-l', '--list', action = 'store_true',
49 short = 'List the branches contained in this repository', long = """
50 List each branch in the current repository, followed by its
51 branch description (if any). The current branch is prefixed
52 with '>'. Branches that have been initialized for StGit (with
53 stglink:init[]) are prefixed with 's'. Protected branches are
54 prefixed with 'p'."""),
55 opt('-c', '--create', action = 'store_true',
56 short = 'Create (and switch to) a new branch', long = """
57 Create (and switch to) a new branch. The new branch is already
58 initialized as an StGit patch stack, so you do not have to run
59 stglink:init[] manually. If you give a committish argument,
60 the new branch is based there; otherwise, it is based at the
63 StGit will try to detect the branch off of which the new
64 branch is forked, as well as the remote repository from which
65 that parent branch is taken (if any), so that running
66 stglink:pull[] will automatically pull new commits from the
67 correct branch. It will warn if it cannot guess the parent
68 branch (e.g. if you do not specify a branch name as
70 opt('--clone', action = 'store_true',
71 short = 'Clone the contents of the current branch', long = """
72 Clone the current branch, under the name <new-branch> if
73 specified, or using the current branch's name plus a
76 The description of the new branch is set to tell it is a clone
77 of the current branch. The parent information of the new
78 branch is copied from the current branch."""),
79 opt('-r', '--rename', action = 'store_true',
80 short = 'Rename an existing branch'),
81 opt('-p', '--protect', action = 'store_true',
82 short = 'Prevent StGit from modifying a branch', long = """
83 Prevent StGit from modifying a branch -- either the current
84 one, or one named on the command line."""),
85 opt('-u', '--unprotect', action = 'store_true',
86 short = 'Allow StGit to modify a branch', long = """
87 Allow StGit to modify a branch -- either the current one, or
88 one named on the command line. This undoes the effect of an
89 earlier 'stg branch --protect' command."""),
90 opt('--delete', action = 'store_true',
91 short = 'Delete a branch', long = """
92 Delete the named branch. If there are any patches left in the
93 branch, StGit will refuse to delete it unless you give the
96 A protected branch cannot be deleted; it must be unprotected
97 first (see '--unprotect' above).
99 If you delete the current branch, you are switched to the
100 "master" branch, if it exists."""),
101 opt('-d', '--description', short = 'Set the branch description'),
102 opt('--force', action = 'store_true',
103 short = 'Force a delete when the series is not empty')]
105 directory = DirectoryGotoToplevel()
107 def __is_current_branch(branch_name):
108 return crt_series.get_name() == branch_name
110 def __print_branch(branch_name, length):
115 branch = stack.Series(branch_name)
117 if branch.is_initialised():
119 if __is_current_branch(branch_name):
121 if branch.get_protected():
123 out.stdout(current + ' ' + initialized + protected + '\t'
124 + branch_name.ljust(length) + ' | ' + branch.get_description())
126 def __delete_branch(doomed_name, force = False):
127 doomed = stack.Series(doomed_name)
129 if __is_current_branch(doomed_name):
130 raise CmdException('Cannot delete the current branch')
131 if doomed.get_protected():
132 raise CmdException, 'This branch is protected. Delete is not permitted'
134 out.start('Deleting branch "%s"' % doomed_name)
138 def func(parser, options, args):
142 if len(args) == 0 or len(args) > 2:
143 parser.error('incorrect number of arguments')
145 check_local_changes()
147 check_head_top_equal(crt_series)
153 branchpoint = git.rev_parse(args[1])
156 head_re = re.compile('refs/(heads|remotes)/')
157 ref_re = re.compile(args[1] + '$')
158 for ref in git.all_refs():
159 if head_re.match(ref) and ref_re.search(ref):
160 # args[1] is a valid ref from the branchpoint
162 parentbranch = args[1]
164 except git.GitException:
165 # should use a more specific exception to catch only
167 out.info('Don\'t know how to determine parent branch'
168 ' from "%s"' % args[1])
169 # exception in branch = rev_parse() leaves branchpoint unbound
172 tree_id = git_id(crt_series, branchpoint or args[1])
175 out.info('Recording "%s" as parent branch' % parentbranch)
177 out.info('Don\'t know how to determine parent branch'
178 ' from "%s"' % args[1])
180 # branch stack off current branch
181 parentbranch = git.get_head_file()
184 parentremote = git.identify_remote(parentbranch)
186 out.info('Using remote "%s" to pull parent from'
189 out.info('Recording as a local branch')
191 # no known parent branch, can't guess the remote
194 stack.Series(args[0]).init(create_at = tree_id,
195 parent_remote = parentremote,
196 parent_branch = parentbranch)
198 out.info('Branch "%s" created' % args[0])
204 clone = crt_series.get_name() + \
205 time.strftime('-%C%y%m%d-%H%M%S')
209 parser.error('incorrect number of arguments')
211 check_local_changes()
213 check_head_top_equal(crt_series)
215 out.start('Cloning current branch to "%s"' % clone)
216 crt_series.clone(clone)
224 parser.error('incorrect number of arguments')
225 __delete_branch(args[0], options.force)
231 parser.error('incorrect number of arguments')
233 branches = git.get_heads()
237 out.info('Available branches:')
238 max_len = max([len(i) for i in branches])
240 __print_branch(i, max_len)
242 out.info('No branches')
245 elif options.protect:
248 branch_name = crt_series.get_name()
250 branch_name = args[0]
252 parser.error('incorrect number of arguments')
253 branch = stack.Series(branch_name)
255 if not branch.is_initialised():
256 raise CmdException, 'Branch "%s" is not controlled by StGIT' \
259 out.start('Protecting branch "%s"' % branch_name)
268 parser.error('incorrect number of arguments')
270 if __is_current_branch(args[0]):
271 raise CmdException, 'Renaming the current branch is not supported'
273 stack.Series(args[0]).rename(args[1])
275 out.info('Renamed branch "%s" to "%s"' % (args[0], args[1]))
279 elif options.unprotect:
282 branch_name = crt_series.get_name()
284 branch_name = args[0]
286 parser.error('incorrect number of arguments')
287 branch = stack.Series(branch_name)
289 if not branch.is_initialised():
290 raise CmdException, 'Branch "%s" is not controlled by StGIT' \
293 out.info('Unprotecting branch "%s"' % branch_name)
299 elif options.description is not None:
302 branch_name = crt_series.get_name()
304 branch_name = args[0]
306 parser.error('incorrect number of arguments')
307 branch = stack.Series(branch_name)
309 if not branch.is_initialised():
310 raise CmdException, 'Branch "%s" is not controlled by StGIT' \
313 branch.set_description(options.description)
319 if __is_current_branch(args[0]):
320 raise CmdException, 'Branch "%s" is already the current branch' \
323 check_local_changes()
325 check_head_top_equal(crt_series)
327 out.start('Switching to branch "%s"' % args[0])
328 git.switch_branch(args[0])
332 # default action: print the current branch
334 parser.error('incorrect number of arguments')
336 print crt_series.get_name()