stg coalesce: Support --file and --save-template
[stgit] / stgit / commands / coalesce.py
CommitLineData
48b209cd
KH
1# -*- coding: utf-8 -*-
2
3__copyright__ = """
4Copyright (C) 2007, 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
20from optparse import make_option
21from stgit.out import *
22from stgit import utils
23from stgit.commands import common
24from stgit.lib import git, transaction
25
26help = 'coalesce two or more patches into one'
27usage = """%prog [options] <patches>
28
29Coalesce two or more patches, creating one big patch that contains all
dcd32afa
KH
30their changes.
31
32If there are conflicts when reordering the patches to match the order
33you specify, you will have to resolve them manually just as if you had
34done a sequence of pushes and pops yourself."""
48b209cd
KH
35
36directory = common.DirectoryHasRepositoryLib()
d568af72
KH
37options = [make_option('-n', '--name', help = 'name of coalesced patch')
38 ] + utils.make_message_options()
48b209cd 39
d568af72
KH
40class SaveTemplateDone(Exception):
41 pass
42
43def _coalesce_patches(trans, patches, msg, save_template):
dcd32afa
KH
44 cd = trans.patches[patches[0]].data
45 cd = git.Commitdata(tree = cd.tree, parents = cd.parents)
46 for pn in patches[1:]:
47 c = trans.patches[pn]
48 tree = trans.stack.repository.simple_merge(
49 base = c.data.parent.data.tree,
50 ours = cd.tree, theirs = c.data.tree)
51 if not tree:
52 return None
53 cd = cd.set_tree(tree)
54 if msg == None:
55 msg = '\n\n'.join('%s\n\n%s' % (pn.ljust(70, '-'),
56 trans.patches[pn].data.message)
57 for pn in patches)
d568af72
KH
58 if save_template:
59 save_template(msg)
60 raise SaveTemplateDone()
61 else:
62 msg = utils.edit_string(msg, '.stgit-coalesce.txt').strip()
dcd32afa
KH
63 cd = cd.set_message(msg)
64
65 return cd
48b209cd 66
d568af72 67def _coalesce(stack, iw, name, msg, save_template, patches):
48b209cd 68
dcd32afa 69 # If a name was supplied on the command line, make sure it's OK.
48b209cd
KH
70 def bad_name(pn):
71 return pn not in patches and stack.patches.exists(pn)
dcd32afa
KH
72 def get_name(cd):
73 return name or utils.make_patch_name(cd.message, bad_name)
48b209cd
KH
74 if name and bad_name(name):
75 raise common.CmdException('Patch name "%s" already taken')
48b209cd 76
dcd32afa
KH
77 def make_coalesced_patch(trans, new_commit_data):
78 name = get_name(new_commit_data)
79 trans.patches[name] = stack.repository.commit(new_commit_data)
80 trans.unapplied.insert(0, name)
81
48b209cd 82 trans = transaction.StackTransaction(stack, 'stg coalesce')
dcd32afa 83 push_new_patch = bool(set(patches) & set(trans.applied))
dcd32afa 84 try:
d568af72 85 new_commit_data = _coalesce_patches(trans, patches, msg, save_template)
dcd32afa
KH
86 if new_commit_data:
87 # We were able to construct the coalesced commit
88 # automatically. So just delete its constituent patches.
89 to_push = trans.delete_patches(lambda pn: pn in patches)
90 else:
91 # Automatic construction failed. So push the patches
92 # consecutively, so that a second construction attempt is
93 # guaranteed to work.
94 to_push = trans.pop_patches(lambda pn: pn in patches)
95 for pn in patches:
96 trans.push_patch(pn, iw)
d568af72
KH
97 new_commit_data = _coalesce_patches(trans, patches, msg,
98 save_template)
dcd32afa
KH
99 assert not trans.delete_patches(lambda pn: pn in patches)
100 make_coalesced_patch(trans, new_commit_data)
101
102 # Push the new patch if necessary, and any unrelated patches we've
103 # had to pop out of the way.
104 if push_new_patch:
105 trans.push_patch(get_name(new_commit_data), iw)
106 for pn in to_push:
107 trans.push_patch(pn, iw)
d568af72
KH
108 except SaveTemplateDone:
109 trans.abort(iw)
110 return
dcd32afa
KH
111 except transaction.TransactionHalted:
112 pass
113 trans.run(iw)
48b209cd
KH
114
115def func(parser, options, args):
116 stack = directory.repository.current_stack
dcd32afa
KH
117 patches = common.parse_patches(args, (list(stack.patchorder.applied)
118 + list(stack.patchorder.unapplied)))
48b209cd
KH
119 if len(patches) < 2:
120 raise common.CmdException('Need at least two patches')
dcd32afa 121 _coalesce(stack, stack.repository.default_iw(),
d568af72 122 options.name, options.message, options.save_template, patches)