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 argparse, 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."""
48 args = [argparse.all_branches]
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 linkstg: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 linkstg:init[] manually. If you give a committish argument,
62 the new branch is based there; otherwise, it is based at the
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 linkstg: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
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
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
98 A protected branch cannot be deleted; it must be unprotected
99 first (see '--unprotect' above).
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')]
107 directory = DirectoryGotoToplevel(log = False)
109 def __is_current_branch(branch_name):
110 return crt_series.get_name() == branch_name
112 def __print_branch(branch_name, length):
117 branch = stack.Series(branch_name)
119 if branch.is_initialised():
121 if __is_current_branch(branch_name):
123 if branch.get_protected():
125 out.stdout(current + ' ' + initialized + protected + '\t'
126 + branch_name.ljust(length) + ' | ' + branch.get_description())
128 def __delete_branch(doomed_name, force = False):
129 doomed = stack.Series(doomed_name)
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'
136 out.start('Deleting branch "%s"' % doomed_name)
140 def func(parser, options, args):
144 if len(args) == 0 or len(args) > 2:
145 parser.error('incorrect number of arguments')
147 check_local_changes()
149 check_head_top_equal(crt_series)
155 branchpoint = git.rev_parse(args[1])
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
164 parentbranch = args[1]
166 except git.GitException:
167 # should use a more specific exception to catch only
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
174 tree_id = git_id(crt_series, branchpoint or args[1])
177 out.info('Recording "%s" as parent branch' % parentbranch)
179 out.info('Don\'t know how to determine parent branch'
180 ' from "%s"' % args[1])
182 # branch stack off current branch
183 parentbranch = git.get_head_file()
186 parentremote = git.identify_remote(parentbranch)
188 out.info('Using remote "%s" to pull parent from'
191 out.info('Recording as a local branch')
193 # no known parent branch, can't guess the remote
196 stack.Series(args[0]).init(create_at = tree_id,
197 parent_remote = parentremote,
198 parent_branch = parentbranch)
200 out.info('Branch "%s" created' % args[0])
201 log.compat_log_entry('branch --create')
207 clone = crt_series.get_name() + \
208 time.strftime('-%C%y%m%d-%H%M%S')
212 parser.error('incorrect number of arguments')
214 check_local_changes()
216 check_head_top_equal(crt_series)
218 out.start('Cloning current branch to "%s"' % clone)
219 crt_series.clone(clone)
222 log.copy_log(log.default_repo(), crt_series.get_name(), clone,
229 parser.error('incorrect number of arguments')
230 __delete_branch(args[0], options.force)
231 log.delete_log(log.default_repo(), args[0])
237 parser.error('incorrect number of arguments')
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:
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)
251 out.info('No branches')
254 elif options.protect:
257 branch_name = crt_series.get_name()
259 branch_name = args[0]
261 parser.error('incorrect number of arguments')
262 branch = stack.Series(branch_name)
264 if not branch.is_initialised():
265 raise CmdException, 'Branch "%s" is not controlled by StGIT' \
268 out.start('Protecting branch "%s"' % branch_name)
277 parser.error('incorrect number of arguments')
279 if __is_current_branch(args[0]):
280 raise CmdException, 'Renaming the current branch is not supported'
282 stack.Series(args[0]).rename(args[1])
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')
288 elif options.unprotect:
291 branch_name = crt_series.get_name()
293 branch_name = args[0]
295 parser.error('incorrect number of arguments')
296 branch = stack.Series(branch_name)
298 if not branch.is_initialised():
299 raise CmdException, 'Branch "%s" is not controlled by StGIT' \
302 out.info('Unprotecting branch "%s"' % branch_name)
308 elif options.description is not None:
311 branch_name = crt_series.get_name()
313 branch_name = args[0]
315 parser.error('incorrect number of arguments')
316 branch = stack.Series(branch_name)
318 if not branch.is_initialised():
319 raise CmdException, 'Branch "%s" is not controlled by StGIT' \
322 branch.set_description(options.description)
328 if __is_current_branch(args[0]):
329 raise CmdException, 'Branch "%s" is already the current branch' \
332 check_local_changes()
334 check_head_top_equal(crt_series)
336 out.start('Switching to branch "%s"' % args[0])
337 git.switch_branch(args[0])
341 # default action: print the current branch
343 parser.error('incorrect number of arguments')
345 print crt_series.get_name()