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 | ||
594aa463 | 26 | help = 'Squash two or more patches into one' |
33ff9cdd | 27 | kind = 'stack' |
575bbdae KH |
28 | usage = ['[options] <patches>'] |
29 | description = """ | |
594aa463 | 30 | Squash two or more patches, creating one big patch that contains all |
0d1d5574 | 31 | their changes. In more detail: |
dcd32afa | 32 | |
0d1d5574 KH |
33 | 1. Pop all the given patches, plus any other patches on top of them. |
34 | ||
35 | 2. Push the given patches in the order they were given on the | |
36 | command line. | |
37 | ||
38 | 3. Squash the given patches into one big patch. | |
39 | ||
40 | 4. Allow the user to edit the commit message of the new patch | |
41 | interactively. | |
42 | ||
43 | 5. Push the other patches that were popped in step (1). | |
44 | ||
45 | Conflicts can occur whenever we push a patch; that is, in step (2) and | |
46 | (5). If there are conflicts, the command will stop so that you can | |
47 | resolve them.""" | |
48b209cd | 48 | |
6c8a90e1 KH |
49 | args = [argparse.patch_range(argparse.applied_patches, |
50 | argparse.unapplied_patches)] | |
594aa463 | 51 | options = [opt('-n', '--name', short = 'Name of squashed patch') |
f9d69fc4 | 52 | ] + argparse.message_options(save_template = True) |
48b209cd | 53 | |
575bbdae KH |
54 | directory = common.DirectoryHasRepositoryLib() |
55 | ||
d568af72 KH |
56 | class SaveTemplateDone(Exception): |
57 | pass | |
58 | ||
594aa463 | 59 | def _squash_patches(trans, patches, msg, save_template): |
dcd32afa | 60 | cd = trans.patches[patches[0]].data |
f5f22afe | 61 | cd = git.CommitData(tree = cd.tree, parents = cd.parents) |
dcd32afa KH |
62 | for pn in patches[1:]: |
63 | c = trans.patches[pn] | |
64 | tree = trans.stack.repository.simple_merge( | |
65 | base = c.data.parent.data.tree, | |
66 | ours = cd.tree, theirs = c.data.tree) | |
67 | if not tree: | |
68 | return None | |
69 | cd = cd.set_tree(tree) | |
70 | if msg == None: | |
4c47af78 KW |
71 | msg = utils.append_comment( |
72 | trans.patches[patches[0]].data.message, | |
73 | '\n\n'.join('%s\n\n%s' % (pn.ljust(70, '-'), | |
74 | trans.patches[pn].data.message) | |
75 | for pn in patches[1:])) | |
d568af72 KH |
76 | if save_template: |
77 | save_template(msg) | |
78 | raise SaveTemplateDone() | |
79 | else: | |
4c47af78 KW |
80 | msg = utils.edit_string(msg, '.stgit-squash.txt') |
81 | msg = utils.strip_comment(msg).strip() | |
dcd32afa KH |
82 | cd = cd.set_message(msg) |
83 | ||
84 | return cd | |
48b209cd | 85 | |
594aa463 | 86 | def _squash(stack, iw, name, msg, save_template, patches): |
48b209cd | 87 | |
dcd32afa | 88 | # If a name was supplied on the command line, make sure it's OK. |
48b209cd KH |
89 | def bad_name(pn): |
90 | return pn not in patches and stack.patches.exists(pn) | |
dcd32afa KH |
91 | def get_name(cd): |
92 | return name or utils.make_patch_name(cd.message, bad_name) | |
48b209cd KH |
93 | if name and bad_name(name): |
94 | raise common.CmdException('Patch name "%s" already taken') | |
48b209cd | 95 | |
594aa463 | 96 | def make_squashed_patch(trans, new_commit_data): |
dcd32afa KH |
97 | name = get_name(new_commit_data) |
98 | trans.patches[name] = stack.repository.commit(new_commit_data) | |
99 | trans.unapplied.insert(0, name) | |
100 | ||
594aa463 | 101 | trans = transaction.StackTransaction(stack, 'squash', |
781e549a | 102 | allow_conflicts = True) |
dcd32afa | 103 | push_new_patch = bool(set(patches) & set(trans.applied)) |
dcd32afa | 104 | try: |
594aa463 | 105 | new_commit_data = _squash_patches(trans, patches, msg, save_template) |
dcd32afa | 106 | if new_commit_data: |
594aa463 | 107 | # We were able to construct the squashed commit |
dcd32afa KH |
108 | # automatically. So just delete its constituent patches. |
109 | to_push = trans.delete_patches(lambda pn: pn in patches) | |
110 | else: | |
111 | # Automatic construction failed. So push the patches | |
112 | # consecutively, so that a second construction attempt is | |
113 | # guaranteed to work. | |
114 | to_push = trans.pop_patches(lambda pn: pn in patches) | |
115 | for pn in patches: | |
116 | trans.push_patch(pn, iw) | |
594aa463 | 117 | new_commit_data = _squash_patches(trans, patches, msg, |
d568af72 | 118 | save_template) |
dcd32afa | 119 | assert not trans.delete_patches(lambda pn: pn in patches) |
594aa463 | 120 | make_squashed_patch(trans, new_commit_data) |
dcd32afa KH |
121 | |
122 | # Push the new patch if necessary, and any unrelated patches we've | |
123 | # had to pop out of the way. | |
124 | if push_new_patch: | |
125 | trans.push_patch(get_name(new_commit_data), iw) | |
126 | for pn in to_push: | |
127 | trans.push_patch(pn, iw) | |
d568af72 KH |
128 | except SaveTemplateDone: |
129 | trans.abort(iw) | |
130 | return | |
dcd32afa KH |
131 | except transaction.TransactionHalted: |
132 | pass | |
f9cc5e69 | 133 | return trans.run(iw) |
48b209cd KH |
134 | |
135 | def func(parser, options, args): | |
136 | stack = directory.repository.current_stack | |
a5920051 | 137 | patches = common.parse_patches(args, list(stack.patchorder.all)) |
48b209cd KH |
138 | if len(patches) < 2: |
139 | raise common.CmdException('Need at least two patches') | |
594aa463 KH |
140 | return _squash(stack, stack.repository.default_iw, options.name, |
141 | options.message, options.save_template, patches) |