chiark / gitweb /
Merge branch 'stable'
[stgit] / stgit / commands / coalesce.py
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
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."""
35
36 directory = common.DirectoryHasRepositoryLib()
37 options = [make_option('-n', '--name', help = 'name of coalesced patch')
38            ] + utils.make_message_options()
39
40 class SaveTemplateDone(Exception):
41     pass
42
43 def _coalesce_patches(trans, patches, msg, save_template):
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)
58         if save_template:
59             save_template(msg)
60             raise SaveTemplateDone()
61         else:
62             msg = utils.edit_string(msg, '.stgit-coalesce.txt').strip()
63     cd = cd.set_message(msg)
64
65     return cd
66
67 def _coalesce(stack, iw, name, msg, save_template, patches):
68
69     # If a name was supplied on the command line, make sure it's OK.
70     def bad_name(pn):
71         return pn not in patches and stack.patches.exists(pn)
72     def get_name(cd):
73         return name or utils.make_patch_name(cd.message, bad_name)
74     if name and bad_name(name):
75         raise common.CmdException('Patch name "%s" already taken')
76
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
82     trans = transaction.StackTransaction(stack, 'coalesce',
83                                          allow_conflicts = True)
84     push_new_patch = bool(set(patches) & set(trans.applied))
85     try:
86         new_commit_data = _coalesce_patches(trans, patches, msg, save_template)
87         if new_commit_data:
88             # We were able to construct the coalesced commit
89             # automatically. So just delete its constituent patches.
90             to_push = trans.delete_patches(lambda pn: pn in patches)
91         else:
92             # Automatic construction failed. So push the patches
93             # consecutively, so that a second construction attempt is
94             # guaranteed to work.
95             to_push = trans.pop_patches(lambda pn: pn in patches)
96             for pn in patches:
97                 trans.push_patch(pn, iw)
98             new_commit_data = _coalesce_patches(trans, patches, msg,
99                                                 save_template)
100             assert not trans.delete_patches(lambda pn: pn in patches)
101         make_coalesced_patch(trans, new_commit_data)
102
103         # Push the new patch if necessary, and any unrelated patches we've
104         # had to pop out of the way.
105         if push_new_patch:
106             trans.push_patch(get_name(new_commit_data), iw)
107         for pn in to_push:
108             trans.push_patch(pn, iw)
109     except SaveTemplateDone:
110         trans.abort(iw)
111         return
112     except transaction.TransactionHalted:
113         pass
114     return trans.run(iw)
115
116 def func(parser, options, args):
117     stack = directory.repository.current_stack
118     patches = common.parse_patches(args, list(stack.patchorder.all))
119     if len(patches) < 2:
120         raise common.CmdException('Need at least two patches')
121     return _coalesce(stack, stack.repository.default_iw, options.name,
122                      options.message, options.save_template, patches)