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