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