Auto-generate man pages for all StGit commands
[stgit] / stgit / commands / branch.py
1 __copyright__ = """
2 Copyright (C) 2005, Chuck Lever <cel@netapp.com>
3
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.
7
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.
12
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
16 """
17
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
24
25 help = 'Branch operations: switch, list, create, rename, delete, ...'
26 usage = ['',
27 '<branch>',
28 '--list',
29 '--create <new-branch> [<committish>]',
30 '--clone [<new-branch>]',
31 '--rename <old-name> <new-name>',
32 '--protect [<branch>]',
33 '--unprotect [<branch>]',
34 '--delete [--force] <branch>',
35 '--description=<description> [<branch>]']
36 description = """
37 Create, clone, switch between, rename, or delete development branches
38 within a git repository.
39
40 'stg branch'::
41 Display the name of the current branch.
42
43 'stg branch' <branch>::
44 Switch to the given branch."""
45
46 options = [
47 opt('-l', '--list', action = 'store_true',
48 short = 'List the branches contained in this repository', long = """
49 List each branch in the current repository, followed by its
50 branch description (if any). The current branch is prefixed
51 with '>'. Branches that have been initialized for StGit (with
52 stglink:init[]) are prefixed with 's'. Protected branches are
53 prefixed with 'p'."""),
54 opt('-c', '--create', action = 'store_true',
55 short = 'Create (and switch to) a new branch', long = """
56 Create (and switch to) a new branch. The new branch is already
57 initialized as an StGit patch stack, so you do not have to run
58 stglink:init[] manually. If you give a committish argument,
59 the new branch is based there; otherwise, it is based at the
60 current HEAD.
61
62 StGit will try to detect the branch off of which the new
63 branch is forked, as well as the remote repository from which
64 that parent branch is taken (if any), so that running
65 stglink:pull[] will automatically pull new commits from the
66 correct branch. It will warn if it cannot guess the parent
67 branch (e.g. if you do not specify a branch name as
68 committish)."""),
69 opt('--clone', action = 'store_true',
70 short = 'Clone the contents of the current branch', long = """
71 Clone the current branch, under the name <new-branch> if
72 specified, or using the current branch's name plus a
73 timestamp.
74
75 The description of the new branch is set to tell it is a clone
76 of the current branch. The parent information of the new
77 branch is copied from the current branch."""),
78 opt('-r', '--rename', action = 'store_true',
79 short = 'Rename an existing branch'),
80 opt('-p', '--protect', action = 'store_true',
81 short = 'Prevent StGit from modifying a branch', long = """
82 Prevent StGit from modifying a branch -- either the current
83 one, or one named on the command line."""),
84 opt('-u', '--unprotect', action = 'store_true',
85 short = 'Allow StGit to modify a branch', long = """
86 Allow StGit to modify a branch -- either the current one, or
87 one named on the command line. This undoes the effect of an
88 earlier 'stg branch --protect' command."""),
89 opt('--delete', action = 'store_true',
90 short = 'Delete a branch', long = """
91 Delete the named branch. If there are any patches left in the
92 branch, StGit will refuse to delete it unless you give the
93 '--force' flag.
94
95 A protected branch cannot be deleted; it must be unprotected
96 first (see '--unprotect' above).
97
98 If you delete the current branch, you are switched to the
99 "master" branch, if it exists."""),
100 opt('-d', '--description', short = 'Set the branch description'),
101 opt('--force', action = 'store_true',
102 short = 'Force a delete when the series is not empty')]
103
104 directory = DirectoryGotoToplevel()
105
106 def __is_current_branch(branch_name):
107 return crt_series.get_name() == branch_name
108
109 def __print_branch(branch_name, length):
110 initialized = ' '
111 current = ' '
112 protected = ' '
113
114 branch = stack.Series(branch_name)
115
116 if branch.is_initialised():
117 initialized = 's'
118 if __is_current_branch(branch_name):
119 current = '>'
120 if branch.get_protected():
121 protected = 'p'
122 out.stdout(current + ' ' + initialized + protected + '\t'
123 + branch_name.ljust(length) + ' | ' + branch.get_description())
124
125 def __delete_branch(doomed_name, force = False):
126 doomed = stack.Series(doomed_name)
127
128 if __is_current_branch(doomed_name):
129 raise CmdException('Cannot delete the current branch')
130 if doomed.get_protected():
131 raise CmdException, 'This branch is protected. Delete is not permitted'
132
133 out.start('Deleting branch "%s"' % doomed_name)
134 doomed.delete(force)
135 out.done()
136
137 def func(parser, options, args):
138
139 if options.create:
140
141 if len(args) == 0 or len(args) > 2:
142 parser.error('incorrect number of arguments')
143
144 check_local_changes()
145 check_conflicts()
146 check_head_top_equal(crt_series)
147
148 tree_id = None
149 if len(args) >= 2:
150 parentbranch = None
151 try:
152 branchpoint = git.rev_parse(args[1])
153
154 # parent branch?
155 head_re = re.compile('refs/(heads|remotes)/')
156 ref_re = re.compile(args[1] + '$')
157 for ref in git.all_refs():
158 if head_re.match(ref) and ref_re.search(ref):
159 # args[1] is a valid ref from the branchpoint
160 # setting above
161 parentbranch = args[1]
162 break;
163 except git.GitException:
164 # should use a more specific exception to catch only
165 # non-git refs ?
166 out.info('Don\'t know how to determine parent branch'
167 ' from "%s"' % args[1])
168 # exception in branch = rev_parse() leaves branchpoint unbound
169 branchpoint = None
170
171 tree_id = git_id(crt_series, branchpoint or args[1])
172
173 if parentbranch:
174 out.info('Recording "%s" as parent branch' % parentbranch)
175 else:
176 out.info('Don\'t know how to determine parent branch'
177 ' from "%s"' % args[1])
178 else:
179 # branch stack off current branch
180 parentbranch = git.get_head_file()
181
182 if parentbranch:
183 parentremote = git.identify_remote(parentbranch)
184 if parentremote:
185 out.info('Using remote "%s" to pull parent from'
186 % parentremote)
187 else:
188 out.info('Recording as a local branch')
189 else:
190 # no known parent branch, can't guess the remote
191 parentremote = None
192
193 stack.Series(args[0]).init(create_at = tree_id,
194 parent_remote = parentremote,
195 parent_branch = parentbranch)
196
197 out.info('Branch "%s" created' % args[0])
198 return
199
200 elif options.clone:
201
202 if len(args) == 0:
203 clone = crt_series.get_name() + \
204 time.strftime('-%C%y%m%d-%H%M%S')
205 elif len(args) == 1:
206 clone = args[0]
207 else:
208 parser.error('incorrect number of arguments')
209
210 check_local_changes()
211 check_conflicts()
212 check_head_top_equal(crt_series)
213
214 out.start('Cloning current branch to "%s"' % clone)
215 crt_series.clone(clone)
216 out.done()
217
218 return
219
220 elif options.delete:
221
222 if len(args) != 1:
223 parser.error('incorrect number of arguments')
224 __delete_branch(args[0], options.force)
225 return
226
227 elif options.list:
228
229 if len(args) != 0:
230 parser.error('incorrect number of arguments')
231
232 branches = git.get_heads()
233 branches.sort()
234
235 if branches:
236 out.info('Available branches:')
237 max_len = max([len(i) for i in branches])
238 for i in branches:
239 __print_branch(i, max_len)
240 else:
241 out.info('No branches')
242 return
243
244 elif options.protect:
245
246 if len(args) == 0:
247 branch_name = crt_series.get_name()
248 elif len(args) == 1:
249 branch_name = args[0]
250 else:
251 parser.error('incorrect number of arguments')
252 branch = stack.Series(branch_name)
253
254 if not branch.is_initialised():
255 raise CmdException, 'Branch "%s" is not controlled by StGIT' \
256 % branch_name
257
258 out.start('Protecting branch "%s"' % branch_name)
259 branch.protect()
260 out.done()
261
262 return
263
264 elif options.rename:
265
266 if len(args) != 2:
267 parser.error('incorrect number of arguments')
268
269 if __is_current_branch(args[0]):
270 raise CmdException, 'Renaming the current branch is not supported'
271
272 stack.Series(args[0]).rename(args[1])
273
274 out.info('Renamed branch "%s" to "%s"' % (args[0], args[1]))
275
276 return
277
278 elif options.unprotect:
279
280 if len(args) == 0:
281 branch_name = crt_series.get_name()
282 elif len(args) == 1:
283 branch_name = args[0]
284 else:
285 parser.error('incorrect number of arguments')
286 branch = stack.Series(branch_name)
287
288 if not branch.is_initialised():
289 raise CmdException, 'Branch "%s" is not controlled by StGIT' \
290 % branch_name
291
292 out.info('Unprotecting branch "%s"' % branch_name)
293 branch.unprotect()
294 out.done()
295
296 return
297
298 elif options.description is not None:
299
300 if len(args) == 0:
301 branch_name = crt_series.get_name()
302 elif len(args) == 1:
303 branch_name = args[0]
304 else:
305 parser.error('incorrect number of arguments')
306 branch = stack.Series(branch_name)
307
308 if not branch.is_initialised():
309 raise CmdException, 'Branch "%s" is not controlled by StGIT' \
310 % branch_name
311
312 branch.set_description(options.description)
313
314 return
315
316 elif len(args) == 1:
317
318 if __is_current_branch(args[0]):
319 raise CmdException, 'Branch "%s" is already the current branch' \
320 % args[0]
321
322 check_local_changes()
323 check_conflicts()
324 check_head_top_equal(crt_series)
325
326 out.start('Switching to branch "%s"' % args[0])
327 git.switch_branch(args[0])
328 out.done()
329 return
330
331 # default action: print the current branch
332 if len(args) != 0:
333 parser.error('incorrect number of arguments')
334
335 print crt_series.get_name()