Commit | Line | Data |
---|---|---|
48b209cd KH |
1 | # -*- coding: utf-8 -*- |
2 | ||
3 | __copyright__ = """ | |
4 | Copyright (C) 2007, 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 | from optparse import make_option | |
21 | from stgit.out import * | |
22 | from stgit import utils | |
23 | from stgit.commands import common | |
24 | from stgit.lib import git, transaction | |
25 | ||
26 | help = 'coalesce two or more patches into one' | |
27 | usage = """%prog [options] <patches> | |
28 | ||
29 | Coalesce two or more patches, creating one big patch that contains all | |
dcd32afa KH |
30 | their changes. |
31 | ||
32 | If there are conflicts when reordering the patches to match the order | |
33 | you specify, you will have to resolve them manually just as if you had | |
34 | done a sequence of pushes and pops yourself.""" | |
48b209cd KH |
35 | |
36 | directory = common.DirectoryHasRepositoryLib() | |
d568af72 KH |
37 | options = [make_option('-n', '--name', help = 'name of coalesced patch') |
38 | ] + utils.make_message_options() | |
48b209cd | 39 | |
d568af72 KH |
40 | class SaveTemplateDone(Exception): |
41 | pass | |
42 | ||
43 | def _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 | 67 | def _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 | |
115 | def 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) |