Rename "stg coalesce" to "stg squash"
[stgit] / stgit / commands / squash.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
575bbdae 20from stgit.argparse import opt
48b209cd 21from stgit.out import *
20a52e06 22from stgit import argparse, utils
48b209cd
KH
23from stgit.commands import common
24from stgit.lib import git, transaction
25
594aa463 26help = 'Squash two or more patches into one'
33ff9cdd 27kind = 'stack'
575bbdae
KH
28usage = ['[options] <patches>']
29description = """
594aa463 30Squash two or more patches, creating one big patch that contains all
dcd32afa
KH
31their changes.
32
33If there are conflicts when reordering the patches to match the order
34you specify, you will have to resolve them manually just as if you had
35done a sequence of pushes and pops yourself."""
48b209cd 36
6c8a90e1
KH
37args = [argparse.patch_range(argparse.applied_patches,
38 argparse.unapplied_patches)]
594aa463 39options = [opt('-n', '--name', short = 'Name of squashed patch')
f9d69fc4 40 ] + argparse.message_options(save_template = True)
48b209cd 41
575bbdae
KH
42directory = common.DirectoryHasRepositoryLib()
43
d568af72
KH
44class SaveTemplateDone(Exception):
45 pass
46
594aa463 47def _squash_patches(trans, patches, msg, save_template):
dcd32afa 48 cd = trans.patches[patches[0]].data
f5f22afe 49 cd = git.CommitData(tree = cd.tree, parents = cd.parents)
dcd32afa
KH
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)
d568af72
KH
62 if save_template:
63 save_template(msg)
64 raise SaveTemplateDone()
65 else:
594aa463 66 msg = utils.edit_string(msg, '.stgit-squash.txt').strip()
dcd32afa
KH
67 cd = cd.set_message(msg)
68
69 return cd
48b209cd 70
594aa463 71def _squash(stack, iw, name, msg, save_template, patches):
48b209cd 72
dcd32afa 73 # If a name was supplied on the command line, make sure it's OK.
48b209cd
KH
74 def bad_name(pn):
75 return pn not in patches and stack.patches.exists(pn)
dcd32afa
KH
76 def get_name(cd):
77 return name or utils.make_patch_name(cd.message, bad_name)
48b209cd
KH
78 if name and bad_name(name):
79 raise common.CmdException('Patch name "%s" already taken')
48b209cd 80
594aa463 81 def make_squashed_patch(trans, new_commit_data):
dcd32afa
KH
82 name = get_name(new_commit_data)
83 trans.patches[name] = stack.repository.commit(new_commit_data)
84 trans.unapplied.insert(0, name)
85
594aa463 86 trans = transaction.StackTransaction(stack, 'squash',
781e549a 87 allow_conflicts = True)
dcd32afa 88 push_new_patch = bool(set(patches) & set(trans.applied))
dcd32afa 89 try:
594aa463 90 new_commit_data = _squash_patches(trans, patches, msg, save_template)
dcd32afa 91 if new_commit_data:
594aa463 92 # We were able to construct the squashed commit
dcd32afa
KH
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)
594aa463 102 new_commit_data = _squash_patches(trans, patches, msg,
d568af72 103 save_template)
dcd32afa 104 assert not trans.delete_patches(lambda pn: pn in patches)
594aa463 105 make_squashed_patch(trans, new_commit_data)
dcd32afa
KH
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)
d568af72
KH
113 except SaveTemplateDone:
114 trans.abort(iw)
115 return
dcd32afa
KH
116 except transaction.TransactionHalted:
117 pass
f9cc5e69 118 return trans.run(iw)
48b209cd
KH
119
120def func(parser, options, args):
121 stack = directory.repository.current_stack
a5920051 122 patches = common.parse_patches(args, list(stack.patchorder.all))
48b209cd
KH
123 if len(patches) < 2:
124 raise common.CmdException('Need at least two patches')
594aa463
KH
125 return _squash(stack, stack.repository.default_iw, options.name,
126 options.message, options.save_template, patches)