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 | ||
575bbdae | 20 | from stgit.argparse import opt |
48b209cd | 21 | from stgit.out import * |
20a52e06 | 22 | from stgit import argparse, utils |
48b209cd KH |
23 | from stgit.commands import common |
24 | from stgit.lib import git, transaction | |
25 | ||
575bbdae KH |
26 | help = 'Coalesce two or more patches into one' |
27 | usage = ['[options] <patches>'] | |
28 | description = """ | |
48b209cd | 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 | 35 | |
575bbdae | 36 | options = [opt('-n', '--name', short = 'Name of coalesced patch') |
20a52e06 | 37 | ] + argparse.message_options() |
48b209cd | 38 | |
575bbdae KH |
39 | directory = common.DirectoryHasRepositoryLib() |
40 | ||
d568af72 KH |
41 | class SaveTemplateDone(Exception): |
42 | pass | |
43 | ||
44 | def _coalesce_patches(trans, patches, msg, save_template): | |
dcd32afa | 45 | cd = trans.patches[patches[0]].data |
f5f22afe | 46 | cd = git.CommitData(tree = cd.tree, parents = cd.parents) |
dcd32afa KH |
47 | for pn in patches[1:]: |
48 | c = trans.patches[pn] | |
49 | tree = trans.stack.repository.simple_merge( | |
50 | base = c.data.parent.data.tree, | |
51 | ours = cd.tree, theirs = c.data.tree) | |
52 | if not tree: | |
53 | return None | |
54 | cd = cd.set_tree(tree) | |
55 | if msg == None: | |
56 | msg = '\n\n'.join('%s\n\n%s' % (pn.ljust(70, '-'), | |
57 | trans.patches[pn].data.message) | |
58 | for pn in patches) | |
d568af72 KH |
59 | if save_template: |
60 | save_template(msg) | |
61 | raise SaveTemplateDone() | |
62 | else: | |
63 | msg = utils.edit_string(msg, '.stgit-coalesce.txt').strip() | |
dcd32afa KH |
64 | cd = cd.set_message(msg) |
65 | ||
66 | return cd | |
48b209cd | 67 | |
d568af72 | 68 | def _coalesce(stack, iw, name, msg, save_template, patches): |
48b209cd | 69 | |
dcd32afa | 70 | # If a name was supplied on the command line, make sure it's OK. |
48b209cd KH |
71 | def bad_name(pn): |
72 | return pn not in patches and stack.patches.exists(pn) | |
dcd32afa KH |
73 | def get_name(cd): |
74 | return name or utils.make_patch_name(cd.message, bad_name) | |
48b209cd KH |
75 | if name and bad_name(name): |
76 | raise common.CmdException('Patch name "%s" already taken') | |
48b209cd | 77 | |
dcd32afa KH |
78 | def make_coalesced_patch(trans, new_commit_data): |
79 | name = get_name(new_commit_data) | |
80 | trans.patches[name] = stack.repository.commit(new_commit_data) | |
81 | trans.unapplied.insert(0, name) | |
82 | ||
781e549a KH |
83 | trans = transaction.StackTransaction(stack, 'coalesce', |
84 | allow_conflicts = True) | |
dcd32afa | 85 | push_new_patch = bool(set(patches) & set(trans.applied)) |
dcd32afa | 86 | try: |
d568af72 | 87 | new_commit_data = _coalesce_patches(trans, patches, msg, save_template) |
dcd32afa KH |
88 | if new_commit_data: |
89 | # We were able to construct the coalesced commit | |
90 | # automatically. So just delete its constituent patches. | |
91 | to_push = trans.delete_patches(lambda pn: pn in patches) | |
92 | else: | |
93 | # Automatic construction failed. So push the patches | |
94 | # consecutively, so that a second construction attempt is | |
95 | # guaranteed to work. | |
96 | to_push = trans.pop_patches(lambda pn: pn in patches) | |
97 | for pn in patches: | |
98 | trans.push_patch(pn, iw) | |
d568af72 KH |
99 | new_commit_data = _coalesce_patches(trans, patches, msg, |
100 | save_template) | |
dcd32afa KH |
101 | assert not trans.delete_patches(lambda pn: pn in patches) |
102 | make_coalesced_patch(trans, new_commit_data) | |
103 | ||
104 | # Push the new patch if necessary, and any unrelated patches we've | |
105 | # had to pop out of the way. | |
106 | if push_new_patch: | |
107 | trans.push_patch(get_name(new_commit_data), iw) | |
108 | for pn in to_push: | |
109 | trans.push_patch(pn, iw) | |
d568af72 KH |
110 | except SaveTemplateDone: |
111 | trans.abort(iw) | |
112 | return | |
dcd32afa KH |
113 | except transaction.TransactionHalted: |
114 | pass | |
f9cc5e69 | 115 | return trans.run(iw) |
48b209cd KH |
116 | |
117 | def func(parser, options, args): | |
118 | stack = directory.repository.current_stack | |
a5920051 | 119 | patches = common.parse_patches(args, list(stack.patchorder.all)) |
48b209cd KH |
120 | if len(patches) < 2: |
121 | raise common.CmdException('Need at least two patches') | |
a0848ecf | 122 | return _coalesce(stack, stack.repository.default_iw, options.name, |
f9cc5e69 | 123 | options.message, options.save_template, patches) |