Rev 1: New plugin which can merge projects into subdirs. in http://bzr.arbash-meinel.com/plugins/merge_into
John Arbash Meinel
john at arbash-meinel.com
Thu Feb 1 22:29:21 GMT 2007
At http://bzr.arbash-meinel.com/plugins/merge_into
------------------------------------------------------------
revno: 1
revision-id: john at arbash-meinel.com-20070201222920-u2pk8hbozkli4lq0
committer: John Arbash Meinel <john at arbash-meinel.com>
branch nick: merge_into
timestamp: Thu 2007-02-01 16:29:20 -0600
message:
New plugin which can merge projects into subdirs.
This provides a new command:
bzr merge-into ../other/branch subdir
Which lets you merge another project into a new subdirectory of the
current one.
added:
__init__.py __init__.py-20070131211431-n3aub0cnlzpa49ww-1
merge_into.py merge_into.py-20070131214328-dide73mgytkt6kk2-1
test_bb_merge_into.py test_bb_merge_into.p-20070131214328-dide73mgytkt6kk2-2
test_merge_into.py test_merge_into.py-20070131214328-dide73mgytkt6kk2-3
-------------- next part --------------
=== added file '__init__.py'
--- a/__init__.py 1970-01-01 00:00:00 +0000
+++ b/__init__.py 2007-02-01 22:29:20 +0000
@@ -0,0 +1,59 @@
+# Copyright (C) 2007 Canonical Ltd
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# 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
+
+"""Provide the 'merge-into' command.
+
+This command allows you to merge other branches into subdirectories, rather
+than always merging into root, and then needing to be moved around.
+"""
+
+from bzrlib import (
+ commands,
+ )
+
+
+class cmd_merge_into(commands.Command):
+ """Merge a branch into a subdirectory of the current one.
+
+ LOCATION is the branch that will be merged into this one.
+ SUBDIR is the subdirectory that will be used for merging.
+
+ After running 'bzr merge OTHER SUBDIR' all of the files from OTHER will be
+ present underneath the subdirectory SUBDIR.
+ """
+
+ takes_args = ['location', 'subdir']
+ takes_options = []
+
+ def run(self, location=None, subdir=None):
+ import merge_into
+ merge_into.merge_into_helper(location, subdir)
+
+
+commands.register_command(cmd_merge_into)
+
+
+def test_suite():
+ from bzrlib import tests
+ import test_merge_into
+ import test_bb_merge_into
+
+ suite = tests.TestSuite()
+ loader = tests.TestLoader()
+ suite.addTests(loader.loadTestsFromModule(test_merge_into))
+ suite.addTests(loader.loadTestsFromModule(test_bb_merge_into))
+
+ return suite
=== added file 'merge_into.py'
--- a/merge_into.py 1970-01-01 00:00:00 +0000
+++ b/merge_into.py 2007-02-01 22:29:20 +0000
@@ -0,0 +1,166 @@
+# Copyright (C) 2007 Canonical Ltd
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# 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
+
+"""The guts of the 'merge_into' command."""
+
+from bzrlib import (
+ branch,
+ errors,
+ generate_ids,
+ inventory,
+ merge,
+ osutils,
+ repository,
+ trace,
+ workingtree,
+ )
+
+
+class MergeIntoMerger(merge.Merger):
+ """Merger that understands other_tree will be merged into a subdir.
+
+ This also changes the Merger api so that it uses real Branch, revision_id,
+ and RevisonTree objects, rather than using revision specs.
+ """
+
+ target_subdir = ''
+
+ def __init__(self, this_tree, other_branch, other_tree, target_subdir):
+ """Create a new MergeIntoMerger object.
+
+ :param this_tree: The tree that we will be merging into.
+ :param other_branch: The Branch we will be merging from.
+ :param other_tree: The RevisionTree object we want to merge.
+ :param target_subdir: The relative path where we want to merge
+ other_tree into this_tree
+ """
+ # It is assumed that we are merging a tree that is not in our current
+ # ancestry, which means we are using the "EmptyTree" as our basis.
+ null_ancestor_tree = this_tree.branch.repository.revision_tree(None)
+ super(MergeIntoMerger, self).__init__(
+ this_branch=this_tree.branch,
+ this_tree=this_tree,
+ other_tree=other_tree,
+ base_tree=null_ancestor_tree,
+ )
+ self._target_subdir = target_subdir
+ self.other_branch = other_branch
+ self.other_rev_id = other_tree.get_revision_id()
+ self.other_basis = self.other_rev_id
+ self.base_is_ancestor = True
+ self.backup_files = True
+ self.merge_type = merge.Merge3Merger
+ self.set_interesting_files([])
+ self.show_base = False
+ self.reprocess = False
+ self.interesting_ids = None
+ self.merge_type = Wrapper(Merge3MergeIntoMerger,
+ target_subdir=self._target_subdir)
+ self._finish_init()
+
+ def _finish_init(self):
+ """Now that member variables are set, finish initializing."""
+
+ # This is usually done in set_other(), but we already set it as part of
+ # the constructor.
+ self.this_branch.fetch(self.other_branch,
+ last_revision=self.other_basis)
+
+
+class Merge3MergeIntoMerger(merge.Merge3Merger):
+ """This handles the file-by-file merging."""
+
+ def __init__(self, *args, **kwargs):
+ # All of the interesting work happens during Merge3Merger.__init__(),
+ # so we have have to hack in to get our extra parameters set.
+ self._target_subdir = kwargs.pop('target_subdir')
+ this_tree = kwargs.get('this_tree')
+ other_tree = kwargs.get('other_tree')
+ self._fix_other_tree(this_tree, other_tree)
+
+ super(Merge3MergeIntoMerger, self).__init__(*args, **kwargs)
+
+ def _fix_other_tree(self, this_tree, other_tree):
+ """We need to pretend that other_tree's root is actually not at ''."""
+ parent_dir, name = osutils.split(self._target_subdir)
+ parent_id = this_tree.path2id(parent_dir)
+
+ root_ie = other_tree.inventory.root
+ root_ie.parent_id = parent_id
+ root_ie.name = name
+
+ if root_ie.file_id in this_tree:
+ new_file_id = generate_ids.gen_file_id(name)
+ trace.mutter('munging root_ie.file_id: %s => %s', root_ie.file_id,
+ new_file_id)
+ del other_tree.inventory._byid[root_ie.file_id]
+ root_ie.file_id = new_file_id
+ other_tree.inventory._byid[new_file_id] = root_ie
+ # We need to fake a new id for root_ie
+ for child_ie in root_ie.children.itervalues():
+ child_ie.parent_id = new_file_id
+
+ def fix_root(self):
+ """Clean up the roots for the final inventory."""
+ # In the main bzrlib code, this forces the new tree to use the same
+ # tree root as the old tree. But merge-into explicitly doesn't want
+ # that. So the first portion is just a copy of the old code, and then
+ # we change the rest.
+ try:
+ self.tt.final_kind(self.tt.root)
+ except NoSuchFile:
+ self.tt.cancel_deletion(self.tt.root)
+ if self.tt.final_file_id(self.tt.root) is None:
+ self.tt.version_file(self.tt.tree_file_id(self.tt.root),
+ self.tt.root)
+ # All we do is skip the step which used to sanitize the root id.
+
+
+class Wrapper(object):
+ """Wrap a class to provide extra parameters."""
+
+ # Merger.do_merge() sets up its own set of parameters to pass to the
+ # 'merge_type' member. And it is difficult override do_merge without
+ # re-writing the whole thing, so instead we create a wrapper which will
+ # pass the extra parameters.
+
+ def __init__(self, merge_type, **kwargs):
+ self._extra_kwargs = kwargs
+ self._merge_type = merge_type
+
+ def __call__(self, *args, **kwargs):
+ kwargs.update(self._extra_kwargs)
+ return self._merge_type(*args, **kwargs)
+
+ def __getattr__(self, name):
+ return getattr(self._merge_type, name)
+
+def merge_into_helper(location, subdir):
+ """Handle the command line functionality, etc."""
+ wt, relpath = workingtree.WorkingTree.open_containing('.')
+ branch_to_merge = branch.Branch.open(location)
+ revision_to_merge = branch_to_merge.last_revision()
+
+ target_tree = branch_to_merge.basis_tree()
+
+ merger = MergeIntoMerger(this_tree=wt,
+ other_tree=target_tree,
+ other_branch=branch_to_merge,
+ target_subdir=subdir,
+ )
+ conflicts = merger.do_merge()
+ merger.set_pending()
+ return conflicts
=== added file 'test_bb_merge_into.py'
--- a/test_bb_merge_into.py 1970-01-01 00:00:00 +0000
+++ b/test_bb_merge_into.py 2007-02-01 22:29:20 +0000
@@ -0,0 +1,111 @@
+# Copyright (C) 2007 Canonical Ltd
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# 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
+
+"""Blackbox testing for merge_into functionality."""
+
+from bzrlib import (
+ inventory,
+ tests,
+ )
+
+
+class TestMergeInto(tests.TestCaseWithTransport):
+
+ def setup_two_branches(self, custom_root_ids=True):
+ """Setup 2 branches, one will be a library, the other a project."""
+ if custom_root_ids:
+ proj_root = 'project-root-id'
+ lib_root = 'lib1-root-id'
+ else:
+ proj_root = lib_root = inventory.ROOT_ID
+
+ project_wt = self.make_branch_and_tree('project')
+ self.build_tree(['project/README', 'project/dir/',
+ 'project/dir/file.c'])
+ project_wt.add(['', 'README', 'dir', 'dir/file.c'],
+ [proj_root, 'readme-id', 'dir-id', 'file.c-id'])
+ project_wt.commit('Initial project', rev_id='project-1')
+
+ lib_wt = self.make_branch_and_tree('lib1')
+ self.build_tree(['lib1/README', 'lib1/Makefile',
+ 'lib1/foo.c'])
+ lib_wt.add(['', 'README', 'Makefile', 'foo.c'],
+ [lib_root, 'readme-lib-id', 'makefile-lib-id',
+ 'foo.c-lib-id'])
+ lib_wt.commit('Initial lib project', rev_id='lib-1')
+
+ return project_wt, lib_wt
+
+ def test_merge_into_newdir_with_unique_roots(self):
+ project_wt, lib_wt = self.setup_two_branches()
+
+ self.run_bzr('merge-into', '../lib1', 'lib1',
+ working_dir='project')
+
+ # This shouldn't be necessary, but it seems required to get the
+ # inventory reloaded
+ project_wt.read_working_inventory()
+ project_wt.lock_read()
+ try:
+ # The lib-1 revision should be merged into this one
+ self.assertEqual(['project-1', 'lib-1'],
+ project_wt.get_parent_ids())
+ files = [(path, status, kind, file_id)
+ for path, status, kind, file_id, ie
+ in project_wt.list_files(include_root=True)]
+ exp_files = [('', 'V', 'directory', 'project-root-id'),
+ ('README', 'V', 'file', 'readme-id'),
+ ('dir', 'V', 'directory', 'dir-id'),
+ ('dir/file.c', 'V', 'file', 'file.c-id'),
+ ('lib1', 'V', 'directory', 'lib1-root-id'),
+ ('lib1/Makefile', 'V', 'file', 'makefile-lib-id'),
+ ('lib1/README', 'V', 'file', 'readme-lib-id'),
+ ('lib1/foo.c', 'V', 'file', 'foo.c-lib-id'),
+ ]
+ self.assertEqual(exp_files, files)
+ finally:
+ project_wt.unlock()
+
+ def test_merge_into_newdir_with_repeat_roots(self):
+ project_wt, lib_wt = self.setup_two_branches(custom_root_ids=False)
+
+ self.run_bzr('merge-into', '../lib1', 'lib1',
+ working_dir='project')
+
+ # This shouldn't be necessary, but it seems required to get the
+ # inventory reloaded
+ project_wt.read_working_inventory()
+ project_wt.lock_read()
+ try:
+ # The lib-1 revision should be merged into this one
+ self.assertEqual(['project-1', 'lib-1'],
+ project_wt.get_parent_ids())
+ new_lib1_id = project_wt.inventory.path2id('lib1')
+ files = [(path, status, kind, file_id)
+ for path, status, kind, file_id, ie
+ in project_wt.list_files(include_root=True)]
+ exp_files = [('', 'V', 'directory', inventory.ROOT_ID),
+ ('README', 'V', 'file', 'readme-id'),
+ ('dir', 'V', 'directory', 'dir-id'),
+ ('dir/file.c', 'V', 'file', 'file.c-id'),
+ ('lib1', 'V', 'directory', new_lib1_id),
+ ('lib1/Makefile', 'V', 'file', 'makefile-lib-id'),
+ ('lib1/README', 'V', 'file', 'readme-lib-id'),
+ ('lib1/foo.c', 'V', 'file', 'foo.c-lib-id'),
+ ]
+ self.assertEqual(exp_files, files)
+ finally:
+ project_wt.unlock()
=== added file 'test_merge_into.py'
--- a/test_merge_into.py 1970-01-01 00:00:00 +0000
+++ b/test_merge_into.py 2007-02-01 22:29:20 +0000
@@ -0,0 +1,19 @@
+# Copyright (C) 2007 Canonical Ltd
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# 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
+
+"""Whitebox testing for merge_into functionality."""
+
+from bzrlib import tests
More information about the bazaar-commits
mailing list