It coalesces two or more consecutive applied patches, with no need to touch index/worktree, and no possibiliy of conflicts. Future improvements could relax the "consecutive" and "applied" restrictions, by building a new chain of commits just like "stg push" will do once it's been converted to the new infrastructure. Signed-off-by: Karl Hasselström <kha@xxxxxxxxxxx> --- contrib/stgit-completion.bash | 2 + stgit/commands/coalesce.py | 84 +++++++++++++++++++++++++++++++++++++++++ stgit/main.py | 2 + stgit/utils.py | 11 +++++ t/t2600-coalesce.sh | 31 +++++++++++++++ 5 files changed, 130 insertions(+), 0 deletions(-) create mode 100644 stgit/commands/coalesce.py create mode 100755 t/t2600-coalesce.sh diff --git a/contrib/stgit-completion.bash b/contrib/stgit-completion.bash index b3b23d4..b02eb64 100644 --- a/contrib/stgit-completion.bash +++ b/contrib/stgit-completion.bash @@ -18,6 +18,7 @@ _stg_commands=" diff clean clone + coalesce commit cp edit @@ -238,6 +239,7 @@ _stg () # repository commands id) _stg_patches $command _all_patches ;; # stack commands + coalesce) _stg_patches $command _applied_patches ;; float) _stg_patches $command _all_patches ;; goto) _stg_patches $command _all_other_patches ;; hide) _stg_patches $command _unapplied_patches ;; diff --git a/stgit/commands/coalesce.py b/stgit/commands/coalesce.py new file mode 100644 index 0000000..c4c1cf8 --- /dev/null +++ b/stgit/commands/coalesce.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- + +__copyright__ = """ +Copyright (C) 2007, Karl Hasselström <kha@xxxxxxxxxxx> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License version 2 as +published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +""" + +from optparse import make_option +from stgit.out import * +from stgit import utils +from stgit.commands import common +from stgit.lib import git, transaction + +help = 'coalesce two or more patches into one' +usage = """%prog [options] <patches> + +Coalesce two or more patches, creating one big patch that contains all +their changes. The patches must all be applied, and must be +consecutive.""" + +directory = common.DirectoryHasRepositoryLib() +options = [make_option('-n', '--name', help = 'name of coalesced patch'), + make_option('-m', '--message', + help = 'commit message of coalesced patch')] + +def _coalesce(stack, name, msg, patches): + applied = stack.patchorder.applied + + # Make sure the patches are consecutive. + applied_ix = dict((applied[i], i) for i in xrange(len(applied))) + ixes = list(sorted(applied_ix[p] for p in patches)) + i0, i1 = ixes[0], ixes[-1] + if i1 - i0 + 1 != len(patches): + raise common.CmdException('The patches must be consecutive') + + # Make a commit for the coalesced patch. + def bad_name(pn): + return pn not in patches and stack.patches.exists(pn) + if name and bad_name(name): + raise common.CmdException('Patch name "%s" already taken') + ps = [stack.patches.get(pn) for pn in applied[i0:i1+1]] + if msg == None: + msg = '\n\n'.join('%s\n\n%s' % (p.name.ljust(70, '-'), + p.commit.data.message) + for p in ps) + msg = utils.edit_string(msg, '.stgit-coalesce.txt').strip() + if not name: + name = utils.make_patch_name(msg, bad_name) + cd = git.Commitdata(tree = ps[-1].commit.data.tree, + parents = ps[0].commit.data.parents, message = msg) + + # Rewrite refs. + trans = transaction.StackTransaction(stack, 'stg coalesce') + for pn in applied[i0:i1+1]: + trans.patches[pn] = None + parent = trans.patches[name] = stack.repository.commit(cd) + trans.applied = applied[:i0] + trans.applied.append(name) + for pn in applied[i1+1:]: + p = stack.patches.get(pn) + parent = trans.patches[pn] = stack.repository.commit( + p.commit.data.set_parent(parent)) + trans.applied.append(pn) + trans.run() + +def func(parser, options, args): + stack = directory.repository.current_stack + applied = set(stack.patchorder.applied) + patches = set(common.parse_patches(args, list(stack.patchorder.applied))) + if len(patches) < 2: + raise common.CmdException('Need at least two patches') + _coalesce(stack, options.name, options.message, patches) diff --git a/stgit/main.py b/stgit/main.py index deaac91..384803b 100644 --- a/stgit/main.py +++ b/stgit/main.py @@ -64,6 +64,7 @@ commands = Commands({ 'diff': 'diff', 'clean': 'clean', 'clone': 'clone', + 'coalesce': 'coalesce', 'commit': 'commit', 'edit': 'edit', 'export': 'export', @@ -108,6 +109,7 @@ stackcommands = ( 'applied', 'branch', 'clean', + 'coalesce', 'commit', 'float', 'goto', diff --git a/stgit/utils.py b/stgit/utils.py index b3f6232..688276c 100644 --- a/stgit/utils.py +++ b/stgit/utils.py @@ -189,6 +189,17 @@ def call_editor(filename): raise EditorException, 'editor failed, exit code: %d' % err out.done() +def edit_string(s, filename): + f = file(filename, 'w') + f.write(s) + f.close() + call_editor(filename) + f = file(filename) + s = f.read() + f.close() + os.remove(filename) + return s + def patch_name_from_msg(msg): """Return a string to be used as a patch name. This is generated from the top line of the string passed as argument.""" diff --git a/t/t2600-coalesce.sh b/t/t2600-coalesce.sh new file mode 100755 index 0000000..f13a309 --- /dev/null +++ b/t/t2600-coalesce.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +test_description='Run "stg coalesce"' + +. ./test-lib.sh + +test_expect_success 'Initialize StGit stack' ' + stg init && + for i in 0 1 2 3; do + stg new p$i -m "foo $i" && + echo "foo $i" >> foo.txt && + git add foo.txt && + stg refresh + done +' + +test_expect_success 'Coalesce some patches' ' + [ "$(echo $(stg applied))" = "p0 p1 p2 p3" ] && + [ "$(echo $(stg unapplied))" = "" ] && + stg coalesce --name=q0 --message="wee woo" p1 p2 && + [ "$(echo $(stg applied))" = "p0 q0 p3" ] && + [ "$(echo $(stg unapplied))" = "" ] +' + +test_expect_success 'Coalesce at stack top' ' + stg coalesce --name=q1 --message="wee woo wham" q0 p3 && + [ "$(echo $(stg applied))" = "p0 q1" ] && + [ "$(echo $(stg unapplied))" = "" ] +' + +test_done - To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html