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