chiark / gitweb /
Some clean-up of the branch manipulation commands
[stgit] / stgit / commands / branch.py
CommitLineData
7b1ba1a6
CL
1"""Branch command
2"""
3
4__copyright__ = """
5Copyright (C) 2005, Chuck Lever <cel@netapp.com>
6
7This program is free software; you can redistribute it and/or modify
8it under the terms of the GNU General Public License version 2 as
9published by the Free Software Foundation.
10
11This program is distributed in the hope that it will be useful,
12but WITHOUT ANY WARRANTY; without even the implied warranty of
13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14GNU General Public License for more details.
15
16You should have received a copy of the GNU General Public License
17along with this program; if not, write to the Free Software
18Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19"""
20
8319e9ba 21import sys, os, time, re
7b1ba1a6
CL
22from optparse import OptionParser, make_option
23
24from stgit.commands.common import *
25from stgit.utils import *
5e888f30 26from stgit.out import *
170f576b 27from stgit import stack, git, basedir
7b1ba1a6
CL
28
29
4ec67741 30help = 'manage patch stacks'
7b1ba1a6
CL
31usage = """%prog [options] branch-name [commit-id]
32
cc3db2b1 33Create, clone, switch between, rename, or delete development branches
7b1ba1a6
CL
34within a git repository. By default, a single branch called 'master'
35is always created in a new repository. This subcommand allows you to
cc3db2b1 36manage several patch series in the same repository via GIT branches.
7b1ba1a6
CL
37
38When displaying the branches, the names can be prefixed with
a8032a85
CL
39's' (StGIT managed) or 'p' (protected).
40
41If not given any options, switch to the named branch."""
7b1ba1a6 42
7b601c9e 43directory = DirectoryGotoToplevel()
7b1ba1a6
CL
44options = [make_option('-c', '--create',
45 help = 'create a new development branch',
46 action = 'store_true'),
cc3db2b1
CL
47 make_option('--clone',
48 help = 'clone the contents of the current branch',
49 action = 'store_true'),
7b1ba1a6
CL
50 make_option('--delete',
51 help = 'delete an existing development branch',
52 action = 'store_true'),
302600dc
CM
53 make_option('-d', '--description',
54 help = 'set the branch description'),
7b1ba1a6
CL
55 make_option('--force',
56 help = 'force a delete when the series is not empty',
57 action = 'store_true'),
58 make_option('-l', '--list',
59 help = 'list branches contained in this repository',
60 action = 'store_true'),
0b4b9499 61 make_option('-p', '--protect',
f644f510 62 help = 'prevent StGIT from modifying this branch',
0b4b9499 63 action = 'store_true'),
7b1ba1a6
CL
64 make_option('-r', '--rename',
65 help = 'rename an existing development branch',
0b4b9499
CL
66 action = 'store_true'),
67 make_option('-u', '--unprotect',
f644f510 68 help = 'allow StGIT to modify this branch',
7b1ba1a6
CL
69 action = 'store_true')]
70
71
fe4f6d58 72def __is_current_branch(branch_name):
d37ff079 73 return crt_series.get_name() == branch_name
7b1ba1a6 74
862ba51a 75def __print_branch(branch_name, length):
7b1ba1a6
CL
76 initialized = ' '
77 current = ' '
0b4b9499 78 protected = ' '
ebfd0156
CL
79
80 branch = stack.Series(branch_name)
81
2d00440c 82 if branch.is_initialised():
7b1ba1a6 83 initialized = 's'
fe4f6d58 84 if __is_current_branch(branch_name):
7b1ba1a6 85 current = '>'
ebfd0156 86 if branch.get_protected():
0b4b9499 87 protected = 'p'
27ac2b7e
KH
88 out.stdout(current + ' ' + initialized + protected + '\t'
89 + branch_name.ljust(length) + ' | ' + branch.get_description())
7b1ba1a6 90
fe4f6d58 91def __delete_branch(doomed_name, force = False):
ebfd0156
CL
92 doomed = stack.Series(doomed_name)
93
ff432158
CM
94 if __is_current_branch(doomed_name):
95 raise CmdException('Cannot delete the current branch')
ebfd0156 96 if doomed.get_protected():
0b4b9499
CL
97 raise CmdException, 'This branch is protected. Delete is not permitted'
98
27ac2b7e 99 out.start('Deleting branch "%s"' % doomed_name)
ebfd0156 100 doomed.delete(force)
27ac2b7e 101 out.done()
7b1ba1a6 102
7b1ba1a6
CL
103def func(parser, options, args):
104
105 if options.create:
106
107 if len(args) == 0 or len(args) > 2:
108 parser.error('incorrect number of arguments')
fe4f6d58
CM
109
110 check_local_changes()
111 check_conflicts()
6972fd6b 112 check_head_top_equal(crt_series)
fe4f6d58 113
7b1ba1a6 114 tree_id = None
4f5a8c72 115 if len(args) >= 2:
0d90f0ac 116 parentbranch = None
4f5a8c72 117 try:
0d90f0ac
YD
118 branchpoint = git.rev_parse(args[1])
119
8319e9ba
CM
120 # parent branch?
121 head_re = re.compile('refs/(heads|remotes)/')
122 ref_re = re.compile(args[1] + '$')
123 for ref in git.all_refs():
124 if head_re.match(ref) and ref_re.search(ref):
125 # args[1] is a valid ref from the branchpoint
126 # setting above
127 parentbranch = args[1]
128 break;
4f5a8c72 129 except git.GitException:
27ac2b7e
KH
130 # should use a more specific exception to catch only
131 # non-git refs ?
132 out.info('Don\'t know how to determine parent branch'
133 ' from "%s"' % args[1])
0d90f0ac
YD
134 # exception in branch = rev_parse() leaves branchpoint unbound
135 branchpoint = None
4f5a8c72 136
42cd003e 137 tree_id = git_id(crt_series, branchpoint or args[1])
0d90f0ac
YD
138
139 if parentbranch:
140 out.info('Recording "%s" as parent branch' % parentbranch)
141 else:
142 out.info('Don\'t know how to determine parent branch'
143 ' from "%s"' % args[1])
4f5a8c72
YD
144 else:
145 # branch stack off current branch
146 parentbranch = git.get_head_file()
147
148 if parentbranch:
149 parentremote = git.identify_remote(parentbranch)
150 if parentremote:
27ac2b7e
KH
151 out.info('Using remote "%s" to pull parent from'
152 % parentremote)
4f5a8c72 153 else:
27ac2b7e 154 out.info('Recording as a local branch')
4f5a8c72 155 else:
4646e7a3 156 # no known parent branch, can't guess the remote
4f5a8c72
YD
157 parentremote = None
158
159 stack.Series(args[0]).init(create_at = tree_id,
160 parent_remote = parentremote,
161 parent_branch = parentbranch)
7b1ba1a6 162
27ac2b7e 163 out.info('Branch "%s" created' % args[0])
7b1ba1a6
CL
164 return
165
cc3db2b1
CL
166 elif options.clone:
167
168 if len(args) == 0:
d37ff079 169 clone = crt_series.get_name() + \
cc3db2b1
CL
170 time.strftime('-%C%y%m%d-%H%M%S')
171 elif len(args) == 1:
172 clone = args[0]
173 else:
174 parser.error('incorrect number of arguments')
175
176 check_local_changes()
177 check_conflicts()
6972fd6b 178 check_head_top_equal(crt_series)
cc3db2b1 179
27ac2b7e 180 out.start('Cloning current branch to "%s"' % clone)
cc3db2b1 181 crt_series.clone(clone)
27ac2b7e 182 out.done()
cc3db2b1
CL
183
184 return
185
7b1ba1a6
CL
186 elif options.delete:
187
188 if len(args) != 1:
189 parser.error('incorrect number of arguments')
fe4f6d58 190 __delete_branch(args[0], options.force)
7b1ba1a6
CL
191 return
192
193 elif options.list:
194
195 if len(args) != 0:
196 parser.error('incorrect number of arguments')
197
262d31dc 198 branches = git.get_heads()
7b1ba1a6
CL
199 branches.sort()
200