1 # -*- coding: utf-8 -*-
4 Copyright (C) 2007, Karl Hasselström <kha@treskal.com>
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.
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.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, see http://www.gnu.org/licenses/.
19 from stgit
.argparse
import opt
20 from stgit
.out
import *
21 from stgit
import argparse
, utils
22 from stgit
.commands
import common
23 from stgit
.lib
import git
, transaction
25 help = 'Squash two or more patches into one'
27 usage
= ['[options] [--] <patches>']
29 Squash two or more patches, creating one big patch that contains all
30 their changes. In more detail:
32 1. Pop all the given patches, plus any other patches on top of them.
34 2. Push the given patches in the order they were given on the
37 3. Squash the given patches into one big patch.
39 4. Allow the user to edit the commit message of the new patch
42 5. Push the other patches that were popped in step (1).
44 Conflicts can occur whenever we push a patch; that is, in step (2) and
45 (5). If there are conflicts, the command will stop so that you can
48 args
= [argparse
.patch_range(argparse
.applied_patches
,
49 argparse
.unapplied_patches
)]
50 options
= ([opt('-n', '--name', short
= 'Name of squashed patch')] +
51 argparse
.message_options(save_template
= True) +
52 argparse
.hook_options())
54 directory
= common
.DirectoryHasRepositoryLib()
56 class SaveTemplateDone(Exception):
59 def _squash_patches(trans
, patches
, msg
, save_template
, no_verify
=False):
60 cd
= trans
.patches
[patches
[0]].data
61 cd
= git
.CommitData(tree
= cd
.tree
, parents
= cd
.parents
)
62 for pn
in patches
[1:]:
64 tree
= trans
.stack
.repository
.simple_merge(
65 base
= c
.data
.parent
.data
.tree
,
66 ours
= cd
.tree
, theirs
= c
.data
.tree
)
69 cd
= cd
.set_tree(tree
)
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:]))
78 raise SaveTemplateDone()
80 msg
= utils
.edit_string(msg
, '.stgit-squash.txt')
81 msg
= utils
.strip_comment(msg
).strip()
82 cd
= cd
.set_message(msg
)
85 cd
= common
.run_commit_msg_hook(trans
.stack
.repository
, cd
)
89 def _squash(stack
, iw
, name
, msg
, save_template
, patches
, no_verify
=False):
91 # If a name was supplied on the command line, make sure it's OK.
93 return pn
not in patches
and stack
.patches
.exists(pn
)
95 return name
or utils
.make_patch_name(cd
.message
, bad_name
)
96 if name
and bad_name(name
):
97 raise common
.CmdException('Patch name "%s" already taken')
99 def make_squashed_patch(trans
, new_commit_data
):
100 name
= get_name(new_commit_data
)
101 trans
.patches
[name
] = stack
.repository
.commit(new_commit_data
)
102 trans
.unapplied
.insert(0, name
)
104 trans
= transaction
.StackTransaction(stack
, 'squash',
105 allow_conflicts
= True)
106 push_new_patch
= bool(set(patches
) & set(trans
.applied
))
108 new_commit_data
= _squash_patches(trans
, patches
, msg
, save_template
,
111 # We were able to construct the squashed commit
112 # automatically. So just delete its constituent patches.
113 to_push
= trans
.delete_patches(lambda pn
: pn
in patches
)
115 # Automatic construction failed. So push the patches
116 # consecutively, so that a second construction attempt is
117 # guaranteed to work.
118 to_push
= trans
.pop_patches(lambda pn
: pn
in patches
)
120 trans
.push_patch(pn
, iw
)
121 new_commit_data
= _squash_patches(trans
, patches
, msg
,
122 save_template
, no_verify
)
123 assert not trans
.delete_patches(lambda pn
: pn
in patches
)
124 make_squashed_patch(trans
, new_commit_data
)
126 # Push the new patch if necessary, and any unrelated patches we've
127 # had to pop out of the way.
129 trans
.push_patch(get_name(new_commit_data
), iw
)
131 trans
.push_patch(pn
, iw
)
132 except SaveTemplateDone
:
135 except transaction
.TransactionHalted
:
139 def func(parser
, options
, args
):
140 stack
= directory
.repository
.current_stack
141 patches
= common
.parse_patches(args
, list(stack
.patchorder
.all
))
143 raise common
.CmdException('Need at least two patches')
144 return _squash(stack
, stack
.repository
.default_iw
, options
.name
,
145 options
.message
, options
.save_template
, patches
,