chiark / gitweb /
0cd0fb09949293b3b82c04af84b957205cca7f27
[stgit] / stgit / commands / uncommit.py
1 # -*- coding: utf-8 -*-
2
3 __copyright__ = """
4 Copyright (C) 2006, Karl Hasselström <kha@treskal.com>
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License version 2 as
8 published by the Free Software Foundation.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 """
19
20 import sys, os
21 from optparse import OptionParser, make_option
22
23 from stgit.commands.common import *
24 from stgit.utils import *
25 from stgit.out import *
26 from stgit import stack, git
27
28 help = 'turn regular GIT commits into StGIT patches'
29 usage = """%prog [<patchnames>] | -n NUM [<prefix>]] | -t <committish>
30
31 Take one or more git commits at the base of the current stack and turn
32 them into StGIT patches. The new patches are created as applied patches
33 at the bottom of the stack. This is the exact opposite of 'stg commit'.
34
35 By default, the number of patches to uncommit is determined by the
36 number of patch names provided on the command line. First name is used
37 for the first patch to uncommit, i.e. for the newest patch.
38
39 The -n/--number option specifies the number of patches to uncommit. In
40 this case, at most one patch name may be specified. It is used as
41 prefix to which the patch number is appended. If no patch names are
42 provided on the command line, StGIT automatically generates them based
43 on the first line of the patch description.
44
45 The -t/--to option specifies that all commits up to and including the
46 given commit should be uncommitted.
47
48 Only commits with exactly one parent can be uncommitted; in other
49 words, you can't uncommit a merge."""
50
51 options = [make_option('-n', '--number', type = 'int',
52                        help = 'uncommit the specified number of commits'),
53            make_option('-t', '--to',
54                        help = 'uncommit to the specified commit'),
55            make_option('-x', '--exclusive',
56                        help = 'exclude the commit specified by the --to option',
57                        action = 'store_true')]
58
59 def func(parser, options, args):
60     """Uncommit a number of patches.
61     """
62     if options.to:
63         if options.number:
64             parser.error('cannot give both --to and --number')
65         if len(args) != 0:
66             parser.error('cannot specify patch name with --to')
67         patch_nr = patchnames = None
68         to_commit = git.rev_parse(options.to)
69     elif options.number:
70         if options.number <= 0:
71             parser.error('invalid value passed to --number')
72
73         patch_nr = options.number
74
75         if len(args) == 0:
76             patchnames = None
77         elif len(args) == 1:
78             # prefix specified
79             patchnames = ['%s%d' % (args[0], i)
80                           for i in xrange(patch_nr, 0, -1)]
81         else:
82             parser.error('when using --number, specify at most one patch name')
83     elif len(args) == 0:
84         patchnames = None
85         patch_nr = 1
86     else:
87         patchnames = args
88         patch_nr = len(patchnames)
89
90     if crt_series.get_protected():
91         raise CmdException, \
92               'This branch is protected. Uncommit is not permitted'
93
94     def get_commit(commit_id):
95         commit = git.Commit(commit_id)
96         try:
97             parent, = commit.get_parents()
98         except ValueError:
99             raise CmdException('Commit %s does not have exactly one parent'
100                                % commit_id)
101         return (commit, commit_id, parent)
102
103     commits = []
104     next_commit = crt_series.get_base()
105     if patch_nr:
106         out.start('Uncommitting %d patches' % patch_nr)
107         for i in xrange(patch_nr):
108             commit, commit_id, parent = get_commit(next_commit)
109             commits.append((commit, commit_id, parent))
110             next_commit = parent
111     else:
112         if options.exclusive:
113             out.start('Uncommitting to %s (exclusive)' % to_commit)
114         else:
115             out.start('Uncommitting to %s' % to_commit)
116         while True:
117             commit, commit_id, parent = get_commit(next_commit)
118             if commit_id == to_commit:
119                 if not options.exclusive:
120                     commits.append((commit, commit_id, parent))
121                 break
122             commits.append((commit, commit_id, parent))
123             next_commit = parent
124         patch_nr = len(commits)
125
126     for (commit, commit_id, parent), patchname in \
127         zip(commits, patchnames or [None for i in xrange(len(commits))]):
128         author_name, author_email, author_date = \
129                      name_email_date(commit.get_author())
130         crt_series.new_patch(patchname,
131                              can_edit = False, before_existing = True,
132                              commit = False,
133                              top = commit_id, bottom = parent,
134                              message = commit.get_log(),
135                              author_name = author_name,
136                              author_email = author_email,
137                              author_date = author_date)
138
139     out.done()