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