Add a Documentation directory inspired by the git one.
[stgit] / stgit / commands / branch.py
1 """Branch command
2 """
3
4 __copyright__ = """
5 Copyright (C) 2005, Chuck Lever <cel@netapp.com>
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License version 2 as
9 published by the Free Software Foundation.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 """
20
21 import sys, os, time
22 from optparse import OptionParser, make_option
23
24 from stgit.commands.common import *
25 from stgit.utils import *
26 from stgit import stack, git, basedir
27
28
29 help = 'manage patch stacks'
30 usage = """%prog [options] branch-name [commit-id]
31
32 Create, clone, switch between, rename, or delete development branches
33 within a git repository. By default, a single branch called 'master'
34 is always created in a new repository. This subcommand allows you to
35 manage several patch series in the same repository via GIT branches.
36
37 When displaying the branches, the names can be prefixed with
38 's' (StGIT managed) or 'p' (protected).
39
40 If not given any options, switch to the named branch."""
41
42 options = [make_option('-c', '--create',
43 help = 'create a new development branch',
44 action = 'store_true'),
45 make_option('--clone',
46 help = 'clone the contents of the current branch',
47 action = 'store_true'),
48 make_option('--convert',
49 help = 'switch between old and new format branches',
50 action = 'store_true'),
51 make_option('--delete',
52 help = 'delete an existing development branch',
53 action = 'store_true'),
54 make_option('--force',
55 help = 'force a delete when the series is not empty',
56 action = 'store_true'),
57 make_option('-l', '--list',
58 help = 'list branches contained in this repository',
59 action = 'store_true'),
60 make_option('-p', '--protect',
61 help = 'prevent "stg pull" from modifying this branch',
62 action = 'store_true'),
63 make_option('-r', '--rename',
64 help = 'rename an existing development branch',
65 action = 'store_true'),
66 make_option('-u', '--unprotect',
67 help = 'allow "stg pull" to modify this branch',
68 action = 'store_true')]
69
70
71 def __is_current_branch(branch_name):
72 return crt_series.get_branch() == branch_name
73
74 def __print_branch(branch_name, length):
75 initialized = ' '
76 current = ' '
77 protected = ' '
78
79 branch = stack.Series(branch_name)
80
81 if branch.is_initialised():
82 initialized = 's'
83 if __is_current_branch(branch_name):
84 current = '>'
85 if branch.get_protected():
86 protected = 'p'
87 print current + ' ' + initialized + protected + '\t' + \
88 branch_name.ljust(length) + ' | ' + branch.get_description()
89
90 def __delete_branch(doomed_name, force = False):
91 doomed = stack.Series(doomed_name)
92
93 if doomed.get_protected():
94 raise CmdException, 'This branch is protected. Delete is not permitted'
95
96 print 'Deleting branch "%s"...' % doomed_name,
97 sys.stdout.flush()
98
99 if __is_current_branch(doomed_name):
100 check_local_changes()
101 check_conflicts()
102 check_head_top_equal()
103
104 if doomed_name != 'master':
105 git.switch_branch('master')
106
107 doomed.delete(force)
108
109 if doomed_name != 'master':
110 git.delete_branch(doomed_name)
111
112 print 'done'
113
114 def func(parser, options, args):
115
116 if options.create:
117
118 if len(args) == 0 or len(args) > 2:
119 parser.error('incorrect number of arguments')
120
121 check_local_changes()
122 check_conflicts()
123 check_head_top_equal()
124
125 tree_id = None
126 if len(args) >= 2:
127 try:
128 if git.rev_parse(args[1]) == git.rev_parse('refs/heads/' + args[1]):
129 # we are for sure referring to a branch
130 parentbranch = 'refs/heads/' + args[1]
131 print 'Recording "%s" as parent branch.' % parentbranch
132 elif git.rev_parse(args[1]) and re.search('/', args[1]):
133 # FIXME: should the test be more strict ?
134 parentbranch = args[1]
135 else:
136 # Note: this includes refs to StGIT patches
137 print 'Don\'t know how to determine parent branch from "%s".' % args[1]
138 parentbranch = None
139 except git.GitException:
140 # should use a more specific exception to catch only non-git refs ?
141 print 'Don\'t know how to determine parent branch from "%s".' % args[1]
142 parentbranch = None
143
144 tree_id = git_id(args[1])
145 else:
146 # branch stack off current branch
147 parentbranch = git.get_head_file()
148
149 if parentbranch:
150 parentremote = git.identify_remote(parentbranch)
151 if parentremote:
152 print 'Using "%s" remote to pull parent from.' % parentremote
153 else:
154 print 'Recording as a local branch.'
155 else:
156 # no known parent branch, can't guess the remote
157 parentremote = None
158
159 stack.Series(args[0]).init(create_at = tree_id,
160 parent_remote = parentremote,
161 parent_branch = parentbranch)
162
163 print 'Branch "%s" created.' % args[0]
164 return
165
166 elif options.clone:
167
168 if len(args) == 0:
169 clone = crt_series.get_branch() + \
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()
178 check_head_top_equal()
179
180 print 'Cloning current branch to "%s"...' % clone,
181 sys.stdout.flush()
182 crt_series.clone(clone)
183 print 'done'
184
185 return
186
187 elif options.convert:
188
189 if len(args) != 0:
190 parser.error('incorrect number of arguments')
191
192 crt_series.convert()
193 return
194
195 elif options.delete:
196
197 if len(args) != 1:
198 parser.error('incorrect number of arguments')
199 __delete_branch(args[0], options.force)
200 return
201
202 elif options.list:
203
204 if len(args) != 0:
205 parser.error('incorrect number of arguments')
206
207 branches = []
208 basepath = os.path.join(basedir.get(), 'refs', 'heads')
209 for path, files, dirs in walk_tree(basepath):
210 branches += [os.path.join(path, f) for f in files]
211 branches.sort()
212
213 if branches:
214 print 'Available branches:'
215 max_len = max([len(i) for i in branches])
216 for i in branches:
217 __print_branch(i, max_len)
218 else:
219 print 'No branches'
220 return
221
222 elif options.protect:
223
224 if len(args) == 0:
225 branch_name = crt_series.get_branch()
226 elif len(args) == 1:
227 branch_name = args[0]
228 else:
229 parser.error('incorrect number of arguments')
230 branch = stack.Series(branch_name)
231
232 if not branch.is_initialised():
233 raise CmdException, 'Branch "%s" is not controlled by StGIT' \
234 % branch_name
235
236 print 'Protecting branch "%s"...' % branch_name,
237 sys.stdout.flush()
238 branch.protect()
239 print 'done'
240
241 return
242
243 elif options.rename:
244
245 if len(args) != 2:
246 parser.error('incorrect number of arguments')
247
248 if __is_current_branch(args[0]):
249 raise CmdException, 'Renaming the current branch is not supported'
250
251 stack.Series(args[0]).rename(args[1])
252
253 print 'Renamed branch "%s" as "%s".' % (args[0], args[1])
254
255 return
256
257 elif options.unprotect:
258
259 if len(args) == 0:
260 branch_name = crt_series.get_branch()
261 elif len(args) == 1:
262 branch_name = args[0]
263 else:
264 parser.error('incorrect number of arguments')
265 branch = stack.Series(branch_name)
266
267 if not branch.is_initialised():
268 raise CmdException, 'Branch "%s" is not controlled by StGIT' \
269 % branch_name
270
271 print 'Unprotecting branch "%s"...' % branch_name,
272 sys.stdout.flush()
273 branch.unprotect()
274 print 'done'
275
276 return
277
278 elif len(args) == 1:
279
280 if __is_current_branch(args[0]):
281 raise CmdException, 'Branch "%s" is already the current branch' \
282 % args[0]
283
284 check_local_changes()
285 check_conflicts()
286 check_head_top_equal()
287
288 print 'Switching to branch "%s"...' % args[0],
289 sys.stdout.flush()
290
291 git.switch_branch(args[0])
292
293 print 'done'
294 return
295
296 # default action: print the current branch
297 if len(args) != 0:
298 parser.error('incorrect number of arguments')
299
300 print crt_series.get_branch()