1 from stgit
import argparse
, utils
2 from stgit
.argparse
import opt
, patch_range
3 from stgit
.commands
.common
import (
5 DirectoryHasRepository
,
9 from stgit
.lib
.transaction
import StackTransaction
, TransactionHalted
12 Copyright (C) 2007, Karl Hasselström <kha@treskal.com>
14 This program is free software; you can redistribute it and/or modify
15 it under the terms of the GNU General Public License version 2 as
16 published by the Free Software Foundation.
18 This program is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU General Public License for more details.
23 You should have received a copy of the GNU General Public License
24 along with this program; if not, see http://www.gnu.org/licenses/.
27 help = 'Squash two or more patches into one'
29 usage
= ['[options] [--] <patches>']
31 Squash two or more patches, creating one big patch that contains all
32 their changes. In more detail:
34 1. Pop all the given patches, plus any other patches on top of them.
36 2. Push the given patches in the order they were given on the
39 3. Squash the given patches into one big patch.
41 4. Allow the user to edit the commit message of the new patch
44 5. Push the other patches that were popped in step (1).
46 Conflicts can occur whenever we push a patch; that is, in step (2) and
47 (5). If there are conflicts, the command will stop so that you can
50 args
= [patch_range('applied_patches', 'unapplied_patches')]
52 [opt('-n', '--name', short
='Name of squashed patch')]
53 + argparse
.message_options(save_template
=True)
54 + argparse
.hook_options()
57 directory
= DirectoryHasRepository()
60 class SaveTemplateDone(Exception):
64 def _append_comment(message
, comment
):
70 'Everything following the line with "---" will be ignored',
77 def _strip_comment(message
):
79 return message
[: message
.index('\n---\n')]
84 def _squash_patches(trans
, patches
, msg
, save_template
, no_verify
=False):
85 cd
= trans
.patches
[patches
[0]].data
86 for pn
in patches
[1:]:
88 tree
= trans
.stack
.repository
.simple_merge(
89 base
=c
.data
.parent
.data
.tree
,
95 cd
= cd
.set_tree(tree
)
97 msg
= _append_comment(
100 '%s\n\n%s' % (pn
.ljust(70, '-'), trans
.patches
[pn
].data
.message_str
)
101 for pn
in patches
[1:]
105 save_template(msg
.encode(cd
.encoding
))
106 raise SaveTemplateDone()
108 msg
= utils
.edit_string(msg
, '.stgit-squash.txt')
109 msg
= _strip_comment(msg
).strip()
110 cd
= cd
.set_message(msg
)
113 cd
= run_commit_msg_hook(trans
.stack
.repository
, cd
)
118 def _squash(stack
, iw
, name
, msg
, save_template
, patches
, no_verify
=False):
119 # If a name was supplied on the command line, make sure it's OK.
120 if name
and name
not in patches
and stack
.patches
.exists(name
):
121 raise CmdException('Patch name "%s" already taken' % name
)
124 return name
or stack
.patches
.make_name(cd
.message_str
, allow
=patches
)
126 def make_squashed_patch(trans
, new_commit_data
):
127 name
= get_name(new_commit_data
)
128 trans
.patches
[name
] = stack
.repository
.commit(new_commit_data
)
129 trans
.unapplied
.insert(0, name
)
131 trans
= StackTransaction(stack
, 'squash', allow_conflicts
=True)
132 push_new_patch
= bool(set(patches
) & set(trans
.applied
))
134 new_commit_data
= _squash_patches(trans
, patches
, msg
, save_template
, no_verify
)
136 # We were able to construct the squashed commit
137 # automatically. So just delete its constituent patches.
138 to_push
= trans
.delete_patches(lambda pn
: pn
in patches
)
140 # Automatic construction failed. So push the patches
141 # consecutively, so that a second construction attempt is
142 # guaranteed to work.
143 to_push
= trans
.pop_patches(lambda pn
: pn
in patches
)
145 trans
.push_patch(pn
, iw
)
146 new_commit_data
= _squash_patches(
147 trans
, patches
, msg
, save_template
, no_verify
149 popped_extra
= trans
.delete_patches(lambda pn
: pn
in patches
)
150 assert not popped_extra
151 make_squashed_patch(trans
, new_commit_data
)
153 # Push the new patch if necessary, and any unrelated patches we've
154 # had to pop out of the way.
156 trans
.push_patch(get_name(new_commit_data
), iw
)
158 trans
.push_patch(pn
, iw
)
159 except SaveTemplateDone
:
162 except TransactionHalted
:
167 def func(parser
, options
, args
):
168 stack
= directory
.repository
.current_stack
169 patches
= parse_patches(args
, list(stack
.patchorder
.all
))
171 raise CmdException('Need at least two patches')
172 if options
.name
and not stack
.patches
.is_name_valid(options
.name
):
173 raise CmdException('Patch name "%s" is invalid' % options
.name
)
176 stack
.repository
.default_iw
,
179 options
.save_template
,