Add --binary flag to commands that generate diffs
[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('--delete',
49 help = 'delete an existing development branch',
50 action = 'store_true'),
51 make_option('-d', '--description',
52 help = 'set the branch description'),
53 make_option('--force',
54 help = 'force a delete when the series is not empty',
55 action = 'store_true'),
56 make_option('-l', '--list',
57 help = 'list branches contained in this repository',
58 action = 'store_true'),
59 make_option('-p', '--protect',
60 help = 'prevent StGIT from modifying this branch',
61 action = 'store_true'),
62 make_option('-r', '--rename',
63 help = 'rename an existing development branch',
64 action = 'store_true'),
65 make_option('-u', '--unprotect',
66 help = 'allow StGIT to modify this branch',
67 action = 'store_true')]
68
69
70 def __is_current_branch(branch_name):
71 return crt_series.get_branch() == branch_name
72
73 def __print_branch(branch_name, length):
74 initialized = ' '
75 current = ' '
76 protected = ' '
77
78 branch = stack.Series(branch_name)
79
80 if branch.is_initialised():
81 initialized = 's'
82 if __is_current_branch(branch_name):
83 current = '>'
84 if branch.get_protected():
85 protected = 'p'
86 print current + ' ' + initialized + protected + '\t' + \
87 branch_name.ljust(length) + ' | ' + branch.get_description()
88
89 def __delete_branch(doomed_name, force = False):
90 doomed = stack.Series(doomed_name)
91
92 if doomed.get_protected():
93 raise CmdException, 'This branch is protected. Delete is not permitted'
94
95 print 'Deleting branch "%s"...' % doomed_name,
96 sys.stdout.flush()
97
98 if __is_current_branch(doomed_name):
99 check_local_changes()
100 check_conflicts()
101 check_head_top_equal()
102
103 if doomed_name != 'master':
104 git.switch_branch('master')
105
106 doomed.delete(force)
107
108 if doomed_name != 'master':
109 git.delete_branch(doomed_name)
110
111 print 'done'
112
113 def func(parser, options, args):
114
115 if options.create:
116
117 if len(args) == 0 or len(args) > 2:
118 parser.error('incorrect number of arguments')
119
120 check_local_changes()
121 check_conflicts()
122 check_head_top_equal()
123
124 tree_id = None
125 if len(args) >= 2:
126 try:
127 if git.rev_parse(args[1]) == git.rev_parse('refs/heads/' + args[1]):
128 # we are for sure referring to a branch
129 parentbranch = 'refs/heads/' + args[1]
130 print 'Recording "%s" as parent branch.' % parentbranch
131 elif git.rev_parse(args[1]) and re.search('/', args[1]):
132 # FIXME: should the test be more strict ?
133 parentbranch = args[1]
134 else:
135 # Note: this includes refs to StGIT patches
136 print 'Don\'t know how to determine parent branch from "%s".' % args[1]
137 parentbranch = None
138 except git.GitException:
139 # should use a more specific exception to catch only non-git refs ?
140 print 'Don\'t know how to determine parent branch from "%s".' % args[1]
141 parentbranch = None
142
143 tree_id = git_id(args[1])
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:
151 print 'Using "%s" remote to pull parent from.' % parentremote
152 else:
153 print 'Recording as a local branch.'
154 else:
155 # no known parent branch, can't guess the remote
156 parentremote = None
157
158 stack.Series(args[0]).init(create_at = tree_id,
159 parent_remote = parentremote,
160 parent_branch = parentbranch)
161
162 print 'Branch "%s" created.' % args[0]
163 return
164
165 elif options.clone:
166
167 if len(args) == 0:
168 clone = crt_series.get_branch() + \
169 time.strftime('-%C%y%m%d-%H%M%S')
170 elif len(args) == 1:
171 clone = args[0]
172 else:
173 parser.error('incorrect number of arguments')
174
175 check_local_changes()
176 check_conflicts()
177 check_head_top_equal()
178
179 print 'Cloning current branch to "%s"...' % clone,
180 sys.stdout.flush()
181 crt_series.clone(clone)
182 print 'done'
183
184 return
185
186 elif options.delete:
187
188 if len(args) != 1:
189 parser.error('incorrect number of arguments')
190 __delete_branch(args[0], options.force)
191 return
192
193 elif options.list:
194
195 if len(args) != 0:
196 parser.error('incorrect number of arguments')
197
198 branches = []
199 basepath = os.path.join(basedir.get(), 'refs', 'heads')
200 for path, files, dirs in walk_tree(basepath):
201 branches += [os.path.join(path, f) for f in files]
202 branches.sort()
203
204 if branches:
205 print 'Available branches:'
206 max_len = max([len(i) for i in branches])
207 for i in branches:
208 __print_branch(i, max_len)
209 else:
210 print 'No branches'
211 return
212
213 elif options.protect:
214
215 if len(args) == 0:
216 branch_name = crt_series.get_branch()
217 elif len(args) == 1:
218 branch_name = args[0]
219 else:
220 parser.error('incorrect number of arguments')
221 branch = stack.Series(branch_name)
222
223 if not branch.is_initialised():
224 raise CmdException, 'Branch "%s" is not controlled by StGIT' \
225 % branch_name
226
227 print 'Protecting branch "%s"...' % branch_name,
228 sys.stdout.flush()
229 branch.protect()
230 print 'done'
231
232 return
233
234 elif options.rename:
235
236 if len(args) != 2:
237 parser.error('incorrect number of arguments')
238
239 if __is_current_branch(args[0]):
240 raise CmdException, 'Renaming the current branch is not supported'
241
242 stack.Series(args[0]).rename(args[1])
243
244 print 'Renamed branch "%s" as "%s".' % (args[0], args[1])
245
246 return
247
248 elif options.unprotect:
249
250 if len(args) == 0:
251 branch_name = crt_series.get_branch()
252 elif len(args) == 1:
253 branch_name = args[0]
254 else:
255 parser.error('incorrect number of arguments')
256 branch = stack.Series(branch_name)
257
258 if not branch.is_initialised():
259 raise CmdException, 'Branch "%s" is not controlled by StGIT' \
260 % branch_name
261
262 print 'Unprotecting branch "%s"...' % branch_name,
263 sys.stdout.flush()
264 branch.unprotect()
265 print 'done'
266
267 return
268
269 elif options.description is not None:
270
271 if len(args) == 0:
272 branch_name = crt_series.get_branch()
273 elif len(args) == 1:
274 branch_name = args[0]
275 else:
276 parser.error('incorrect number of arguments')
277 branch = stack.Series(branch_name)
278
279 if not branch.is_initialised():
280 raise CmdException, 'Branch "%s" is not controlled by StGIT' \
281 % branch_name
282
283 branch.set_description(options.description)
284
285 return
286
287 elif len(args) == 1:
288
289 if __is_current_branch(args[0]):
290 raise CmdException, 'Branch "%s" is already the current branch' \
291 % args[0]
292
293 check_local_changes()
294 check_conflicts()
295 check_head_top_equal()
296
297 print 'Switching to branch "%s"...' % args[0],
298 sys.stdout.flush()
299
300 git.switch_branch(args[0])
301
302 print 'done'
303 return
304
305 # default action: print the current branch
306 if len(args) != 0:
307 parser.error('incorrect number of arguments')
308
309 print crt_series.get_branch()