Use Patches.make_name() in various commands
[stgit.git] / stgit / commands / pick.py
blob853f1e65158a38913d579ee944bb465e8986d245
1 from stgit.argparse import opt, patch_range
2 from stgit.commands.common import (
3 CmdException,
4 DirectoryGotoTopLevel,
5 check_conflicts,
6 check_head_top_equal,
7 check_local_changes,
8 git_commit,
9 parse_patches,
10 parse_rev,
11 print_current_patch,
13 from stgit.config import config
14 from stgit.lib.git import CommitData, MergeConflictException, MergeException, Person
15 from stgit.lib.transaction import StackTransaction, TransactionHalted
16 from stgit.out import out
17 from stgit.run import Run
18 from stgit.utils import STGIT_CONFLICT, STGIT_SUCCESS
20 __copyright__ = """
21 Copyright (C) 2005, Catalin Marinas <catalin.marinas@gmail.com>
23 This program is free software; you can redistribute it and/or modify
24 it under the terms of the GNU General Public License version 2 as
25 published by the Free Software Foundation.
27 This program is distributed in the hope that it will be useful,
28 but WITHOUT ANY WARRANTY; without even the implied warranty of
29 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30 GNU General Public License for more details.
32 You should have received a copy of the GNU General Public License
33 along with this program; if not, see http://www.gnu.org/licenses/.
34 """
36 help = 'Import a patch from a different branch or a commit object'
37 kind = 'patch'
38 usage = ['[options] [--] ([<patch1>] [<patch2>] [<patch3>..<patch4>])|<commit>']
39 description = """
40 Import one or more patches from a different branch or a commit object
41 into the current series. By default, the name of the imported patch is
42 used as the name of the current patch. It can be overridden with the
43 '--name' option. A commit object can be reverted with the '--revert'
44 option. The log and author information are those of the commit
45 object.
47 When using the '--expose' option, the format of the commit message is
48 determined by the 'stgit.pick.expose-format' configuration option. This option
49 is a format string as may supplied as the '--pretty' option to
50 linkgit:git-show[1]. The default is "format:%B%n(imported from commit %H)",
51 which appends the commit hash of the picked commit to the patch's commit
52 message.
53 """
55 args = [patch_range('applied_patches', 'unapplied_patches', 'hidden_patches')]
56 options = [
57 opt(
58 '-n',
59 '--name',
60 short='Use NAME as the patch name',
62 opt(
63 '-B',
64 '--ref-branch',
65 args=['stg_branches'],
66 short='Pick patches from BRANCH',
68 opt(
69 '-r',
70 '--revert',
71 action='store_true',
72 short='Revert the given commit object',
74 opt(
75 '-p',
76 '--parent',
77 metavar='COMMITID',
78 args=['commit'],
79 short='Use COMMITID as parent',
81 opt(
82 '-x',
83 '--expose',
84 action='store_true',
85 short='Append the imported commit id to the patch log',
87 opt(
88 '--fold',
89 action='store_true',
90 short='Fold the commit object into the current patch',
92 opt(
93 '--update',
94 action='store_true',
95 short='Like fold but only update the current patch files',
97 opt(
98 '-f',
99 '--file',
100 action='append',
101 short='Only fold the given file (can be used multiple times)',
103 opt(
104 '--unapplied',
105 action='store_true',
106 short='Keep the patch unapplied',
110 directory = DirectoryGotoTopLevel()
113 def __pick_commit(stack, ref_stack, iw, commit, patchname, options):
114 """Pick a commit."""
115 repository = stack.repository
117 if options.name:
118 patchname = options.name
119 elif patchname and options.revert:
120 patchname = 'revert-' + patchname
122 if patchname:
123 patchname = stack.patches.make_name(patchname, lower=False)
124 else:
125 patchname = stack.patches.make_name(commit.data.message_str)
127 if options.parent:
128 parent = git_commit(options.parent, repository, ref_stack.name)
129 else:
130 parent = commit.data.parent
132 if not options.revert:
133 bottom = parent
134 top = commit
135 else:
136 bottom = commit
137 top = parent
139 if options.fold:
140 out.start('Folding commit %s' % commit.sha1)
142 diff = repository.diff_tree(
143 bottom.data.tree, top.data.tree, pathlimits=options.file
146 if diff:
147 try:
148 # try a direct git apply first
149 iw.apply(diff, quiet=True)
150 except MergeException:
151 if options.file:
152 out.done('conflict(s)')
153 out.error('%s does not apply cleanly' % patchname)
154 return STGIT_CONFLICT
155 else:
156 try:
157 iw.merge(
158 bottom.data.tree,
159 stack.head.data.tree,
160 top.data.tree,
162 except MergeConflictException as e:
163 out.done('%s conflicts' % len(e.conflicts))
164 out.error('%s does not apply cleanly' % patchname, *e.conflicts)
165 return STGIT_CONFLICT
166 out.done()
167 else:
168 out.done('no changes')
169 return STGIT_SUCCESS
170 elif options.update:
171 files = [
173 for _, _, _, _, _, fn1, fn2 in repository.diff_tree_files(
174 stack.top.data.parent.data.tree, stack.top.data.tree
178 diff = repository.diff_tree(bottom.data.tree, top.data.tree, pathlimits=files)
180 out.start('Updating with commit %s' % commit.sha1)
182 try:
183 iw.apply(diff, quiet=True)
184 except MergeException:
185 out.done('conflict(s)')
186 out.error('%s does not apply cleanly' % patchname)
187 return STGIT_CONFLICT
188 else:
189 out.done()
190 return STGIT_SUCCESS
191 else:
192 author = commit.data.author
193 message = commit.data.message_str
195 if options.revert:
196 author = Person.author()
197 if message:
198 lines = message.splitlines()
199 subject = lines[0]
200 body = '\n'.join(lines[2:])
201 else:
202 subject = commit.sha1
203 body = ''
204 message = 'Revert "%s"\n\nThis reverts commit %s.\n\n%s\n' % (
205 subject,
206 commit.sha1,
207 body,
209 elif options.expose:
210 fmt = config.get('stgit.pick.expose-format')
211 message = Run(
212 'git', 'show', '--no-patch', '--pretty=' + fmt, commit.sha1
213 ).raw_output()
214 message = message.rstrip() + '\n'
216 out.start('Importing commit %s' % commit.sha1)
218 new_commit = repository.commit(
219 CommitData(
220 tree=top.data.tree,
221 parents=[bottom],
222 message=message,
223 author=author,
227 trans = StackTransaction(stack, 'pick %s from %s' % (patchname, ref_stack.name))
228 trans.patches[patchname] = new_commit
230 trans.unapplied.append(patchname)
231 if not options.unapplied:
232 try:
233 trans.push_patch(patchname, iw, allow_interactive=True)
234 except TransactionHalted:
235 pass
237 retval = trans.run(iw, print_current_patch=False)
239 if retval == STGIT_CONFLICT:
240 out.done('conflict(s)')
241 elif stack.patches.get(patchname).is_empty():
242 out.done('empty patch')
243 else:
244 out.done()
246 return retval
249 def func(parser, options, args):
250 """Import a commit object as a new patch"""
251 if not args:
252 parser.error('incorrect number of arguments')
254 if options.file and not options.fold:
255 parser.error('--file can only be specified with --fold')
257 repository = directory.repository
258 stack = repository.get_stack()
259 iw = repository.default_iw
261 if not options.unapplied:
262 check_local_changes(repository)
263 check_conflicts(iw)
264 check_head_top_equal(stack)
266 if options.ref_branch:
267 ref_stack = repository.get_stack(options.ref_branch)
268 else:
269 ref_stack = stack
271 try:
272 patches = parse_patches(
273 args,
274 ref_stack.patchorder.all_visible,
275 len(ref_stack.patchorder.applied),
277 commit = None
278 except CmdException:
279 if len(args) > 1:
280 raise
282 branch, patch = parse_rev(args[0])
284 if not branch:
285 commit = git_commit(patch, repository, options.ref_branch)
286 patches = []
287 else:
288 ref_stack = repository.get_stack(branch)
289 patches = parse_patches(
290 [patch],
291 ref_stack.patchorder.all_visible,
292 len(ref_stack.patchorder.applied),
294 commit = None
296 if not commit and len(patches) > 1:
297 if options.name:
298 raise CmdException('--name can only be specified with one patch')
299 if options.parent:
300 raise CmdException('--parent can only be specified with one patch')
302 if options.update and not stack.patchorder.applied:
303 raise CmdException('No patches applied')
305 if commit:
306 patchname = None
307 retval = __pick_commit(stack, ref_stack, iw, commit, patchname, options)
308 else:
309 if options.unapplied:
310 patches.reverse()
311 for patchname in patches:
312 commit = git_commit(patchname, repository, ref_stack.name)
313 retval = __pick_commit(stack, ref_stack, iw, commit, patchname, options)
314 if retval != STGIT_SUCCESS:
315 break
317 if retval == STGIT_SUCCESS:
318 print_current_patch(stack)
320 return retval