chiark / gitweb /
33a8afe2aa5ea6edf0b40911b8ff5509d97a3dcb
[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()