Handle rename of a file/symlink modified already in this commit
authorIan Clatworthy <ian.clatworthy@canonical.com>
Mon, 24 Aug 2009 02:40:39 +0000 (12:40 +1000)
committerIan Clatworthy <ian.clatworthy@canonical.com>
Mon, 24 Aug 2009 02:40:39 +0000 (12:40 +1000)
bzr_commit_handler.py
tests/test_generic_processor.py

index 30b18bce61850144c9e3cd063675aa40bd26d9cf..74264fe2002c3f1025c92461134377c49da48df0 100644 (file)
@@ -56,7 +56,12 @@ class GenericCommitHandler(processor.CommitHandler):
         # This tracks path->file-id for things we're creating this commit.
         # If the same path is created multiple times, we need to warn the
         # user and add it just once.
+        # If a path is added then renamed or copied, we need to handle that.
         self._new_file_ids = {}
+        # This tracks path->file-id for things we're modifying this commit.
+        # If a path is modified then renamed or copied, we need the make
+        # sure we grab the new content.
+        self._modified_file_ids = {}
         # This tracks the paths for things we're deleting this commit.
         # If the same path is added or the destination of a rename say,
         # then a fresh file-id is required.
@@ -374,11 +379,12 @@ class GenericCommitHandler(processor.CommitHandler):
                 kind, path)
 
     def _rename_item(self, old_path, new_path, inv):
-        existing = self._new_file_ids.get(old_path)
+        existing = self._new_file_ids.get(old_path) or \
+            self._modified_file_ids.get(old_path)
         if existing:
-            # We've only just added this path earlier in this commit.
-            # Change the add of old_path to an add of new_path
-            self._rename_pending_add(old_path, new_path, existing)
+            # We've only just added/modified this path earlier in this commit.
+            # Change the add/modify of old_path to an add of new_path
+            self._rename_pending_change(old_path, new_path, existing)
             return
 
         file_id = inv.path2id(old_path)
@@ -700,6 +706,7 @@ class InventoryDeltaCommitHandler(GenericCommitHandler):
 
     def record_changed(self, path, ie, parent_id=None):
         self._add_entry((path, path, ie.file_id, ie))
+        self._modified_file_ids[path] = ie.file_id
 
     def record_delete(self, path, ie):
         self._add_entry((path, None, ie.file_id, None))
@@ -720,8 +727,8 @@ class InventoryDeltaCommitHandler(GenericCommitHandler):
         new_ie.revision = self.revision_id
         self._add_entry((old_path, new_path, file_id, new_ie))
 
-    def _rename_pending_add(self, old_path, new_path, file_id):
-        """Instead of adding old-path, add new-path instead."""
+    def _rename_pending_change(self, old_path, new_path, file_id):
+        """Instead of adding/modifying old-path, add new-path instead."""
         # note: delta entries look like (old, new, file-id, ie)
         old_ie = self._delta_entries_by_fileid[file_id][3]
 
@@ -729,8 +736,11 @@ class InventoryDeltaCommitHandler(GenericCommitHandler):
         # deletion of newly created parents that could now become empty.
         self.record_delete(old_path, old_ie)
 
-        # Update the dictionary used for tracking new file-ids
-        del self._new_file_ids[old_path]
+        # Update the dictionaries used for tracking new file-ids
+        if old_path in self._new_file_ids:
+            del self._new_file_ids[old_path]
+        else:
+            del self._modified_file_ids[old_path]
         self._new_file_ids[new_path] = file_id
 
         # Create the new InventoryEntry
index 38421cdbce897422b47fe49968cb6de9eb894357..30c35271bf2fe9f0c94d4f3a38044047d0299fee 100644 (file)
@@ -876,6 +876,121 @@ class TestImportToPackRenameToDeleted(TestCaseForGenericProcessor):
         self.assertSymlinkTarget(branch, revtree2, new_path, "aaa")
 
 
+class TestImportToPackRenameModified(TestCaseForGenericProcessor):
+    """Test rename of a path previously modified in this commit."""
+
+    def get_command_iter(self, old_path, new_path, kind='file'):
+        # Revno 1: create a file or symlink
+        # Revno 2: modify then rename it
+        def command_list():
+            author = ['', 'bugs@a.com', time.time(), time.timezone]
+            committer = ['', 'elmer@a.com', time.time(), time.timezone]
+            def files_one():
+                yield commands.FileModifyCommand(old_path, kind, False,
+                        None, "aaa")
+            yield commands.CommitCommand('head', '1', author,
+                committer, "commit 1", None, [], files_one)
+            def files_two():
+                yield commands.FileModifyCommand(old_path, kind, False,
+                        None, "bbb")
+                yield commands.FileRenameCommand(old_path, new_path)
+            yield commands.CommitCommand('head', '2', author,
+                committer, "commit 2", ":1", [], files_two)
+        return command_list
+
+    def test_rename_of_modified_file_in_root(self):
+        handler, branch = self.get_handler()
+        old_path = 'a'
+        new_path = 'b'
+        handler.process(self.get_command_iter(old_path, new_path))
+        revtree0, revtree1 = self.assertChanges(branch, 1,
+            expected_added=[(old_path,)])
+        # Note: the delta doesn't show the modification?
+        # The actual new content is validated in the assertions following.
+        revtree1, revtree2 = self.assertChanges(branch, 2,
+            expected_renamed=[(old_path, new_path)])
+        self.assertContent(branch, revtree1, old_path, "aaa")
+        self.assertContent(branch, revtree2, new_path, "bbb")
+        self.assertRevisionRoot(revtree1, old_path)
+        self.assertRevisionRoot(revtree2, new_path)
+
+    def test_rename_of_modified_symlink_in_root(self):
+        handler, branch = self.get_handler()
+        old_path = 'a'
+        new_path = 'b'
+        handler.process(self.get_command_iter(old_path, new_path, 'symlink'))
+        revtree0, revtree1 = self.assertChanges(branch, 1,
+            expected_added=[(old_path,)])
+        # Note: the delta doesn't show the modification?
+        # The actual new content is validated in the assertions following.
+        revtree1, revtree2 = self.assertChanges(branch, 2,
+            expected_renamed=[(old_path, new_path)])
+        self.assertSymlinkTarget(branch, revtree1, old_path, "aaa")
+        self.assertSymlinkTarget(branch, revtree2, new_path, "bbb")
+        self.assertRevisionRoot(revtree1, old_path)
+        self.assertRevisionRoot(revtree2, new_path)
+
+    def test_rename_of_modified_file_in_subdir(self):
+        handler, branch = self.get_handler()
+        old_path = 'd/a'
+        new_path = 'd/b'
+        handler.process(self.get_command_iter(old_path, new_path))
+        revtree0, revtree1 = self.assertChanges(branch, 1,
+            expected_added=[('d',), (old_path,)])
+        # Note: the delta doesn't show the modification?
+        # The actual new content is validated in the assertions following.
+        revtree1, revtree2 = self.assertChanges(branch, 2,
+            expected_renamed=[(old_path, new_path)])
+        self.assertContent(branch, revtree1, old_path, "aaa")
+        self.assertContent(branch, revtree2, new_path, "bbb")
+
+    def test_rename_of_modified_symlink_in_subdir(self):
+        handler, branch = self.get_handler()
+        old_path = 'd/a'
+        new_path = 'd/b'
+        handler.process(self.get_command_iter(old_path, new_path, 'symlink'))
+        revtree0, revtree1 = self.assertChanges(branch, 1,
+            expected_added=[('d',), (old_path,)])
+        # Note: the delta doesn't show the modification?
+        # The actual new content is validated in the assertions following.
+        revtree1, revtree2 = self.assertChanges(branch, 2,
+            expected_renamed=[(old_path, new_path)])
+        self.assertSymlinkTarget(branch, revtree1, old_path, "aaa")
+        self.assertSymlinkTarget(branch, revtree2, new_path, "bbb")
+
+    def test_rename_of_modified_file_to_new_dir(self):
+        handler, branch = self.get_handler()
+        old_path = 'd1/a'
+        new_path = 'd2/b'
+        handler.process(self.get_command_iter(old_path, new_path))
+        revtree0, revtree1 = self.assertChanges(branch, 1,
+            expected_added=[('d1',), (old_path,)])
+        # Note: the delta doesn't show the modification?
+        # The actual new content is validated in the assertions following.
+        revtree1, revtree2 = self.assertChanges(branch, 2,
+            expected_renamed=[(old_path, new_path)],
+            expected_added=[('d2',)],
+            expected_removed=[('d1',)])
+        self.assertContent(branch, revtree1, old_path, "aaa")
+        self.assertContent(branch, revtree2, new_path, "bbb")
+
+    def test_rename_of_modified_symlink_to_new_dir(self):
+        handler, branch = self.get_handler()
+        old_path = 'd1/a'
+        new_path = 'd2/b'
+        handler.process(self.get_command_iter(old_path, new_path, 'symlink'))
+        revtree0, revtree1 = self.assertChanges(branch, 1,
+            expected_added=[('d1',), (old_path,)])
+        # Note: the delta doesn't show the modification?
+        # The actual new content is validated in the assertions following.
+        revtree1, revtree2 = self.assertChanges(branch, 2,
+            expected_renamed=[(old_path, new_path)],
+            expected_added=[('d2',)],
+            expected_removed=[('d1',)])
+        self.assertSymlinkTarget(branch, revtree1, old_path, "aaa")
+        self.assertSymlinkTarget(branch, revtree2, new_path, "bbb")
+
+
 class TestImportToPackRenameTricky(TestCaseForGenericProcessor):
 
     def file_command_iter(self, path1, old_path2, new_path2, kind='file'):
@@ -1364,6 +1479,9 @@ class TestImportToRichRootRenameNew(TestImportToPackRenameNew):
 class TestImportToRichRootRenameToDeleted(TestImportToPackRenameToDeleted):
     branch_format = "1.9-rich-root"
 
+class TestImportToRichRootRenameModified(TestImportToPackRenameModified):
+    branch_format = "1.9-rich-root"
+
 class TestImportToRichRootRenameTricky(TestImportToPackRenameTricky):
     branch_format = "1.9-rich-root"
 
@@ -1412,6 +1530,9 @@ try:
     class TestImportToChkRenameToDeleted(TestImportToPackRenameToDeleted):
         branch_format = "2a"
 
+    class TestImportToChkRenameModified(TestImportToPackRenameModified):
+        branch_format = "2a"
+
     class TestImportToChkRenameTricky(TestImportToPackRenameTricky):
         branch_format = "2a"