Rev 3603: Add support for -x or --exclude to bzr commit, fixing bug 3117. (Robert Collins) in http://people.ubuntu.com/~robertc/baz2.0/3117

Robert Collins robertc at robertcollins.net
Mon Aug 4 08:30:32 BST 2008


At http://people.ubuntu.com/~robertc/baz2.0/3117

------------------------------------------------------------
revno: 3603
revision-id: robertc at robertcollins.net-20080804072951-tdue3y4bp9893yx9
parent: pqm at pqm.ubuntu.com-20080804032751-myaykx8azatkvlf8
committer: Robert Collins <robertc at robertcollins.net>
branch nick: 3117
timestamp: Mon 2008-08-04 17:29:51 +1000
message:
  Add support for -x or --exclude to bzr commit, fixing bug 3117. (Robert Collins)
modified:
  NEWS                           NEWS-20050323055033-4e00b5db738777ff
  bzrlib/builtins.py             builtins.py-20050830033751-fc01482b9ca23183
  bzrlib/commit.py               commit.py-20050511101309-79ec1a0168e0e825
  bzrlib/tests/blackbox/test_commit.py test_commit.py-20060212094538-ae88fc861d969db0
  bzrlib/tests/workingtree_implementations/test_commit.py test_commit.py-20060421013633-1610ec2331c8190f
=== modified file 'NEWS'
--- a/NEWS	2008-08-01 04:06:18 +0000
+++ b/NEWS	2008-08-04 07:29:51 +0000
@@ -23,6 +23,9 @@
     * ``bzr check`` can now be told which elements at a location it should
       check.  (Daniel Watkins)
 
+    * Commit now supports ``--exclude`` (or ``-x``) to exclude some files
+      from the commit. (Robert Collins, #3117)
+
     * Give a more specific error when target branch is not reachable.
       (James Westby)
 
@@ -94,15 +97,6 @@
 
   INTERNALS:
 
-    * Make it easier to introduce new WorkingTree formats.
-      (Ian Clatworthy)
-
-    * The code for exporting trees was refactored not to use the
-      deprecated ``InventoryEntry`` methods. (Ian Clatworthy)
-
-    * RuleSearchers return () instead of [] now when there are no matches.
-      (Ian Clatworthy)
-
     * ``bzrlib.branchbuilder.BranchBuilder`` is now much more capable of
       putting together a real history without having to create a full
       WorkingTree. It is recommended that tests that are not directly
@@ -110,6 +104,19 @@
       ``BranchBuilder.build_snapshot`` or
       ``TestCaseWithMemoryTree.make_branch_builder``.  (John Arbash Meinel)
 
+    * ``bzrlib.builtins.internal_tree_files`` broken into two giving a new
+      helper ``safe_relpath_files`` - used by the new ``exclude``
+      parameter to commit. (Robert Collins)
+
+    * Make it easier to introduce new WorkingTree formats.
+      (Ian Clatworthy)
+
+    * The code for exporting trees was refactored not to use the
+      deprecated ``InventoryEntry`` methods. (Ian Clatworthy)
+
+    * RuleSearchers return () instead of [] now when there are no matches.
+      (Ian Clatworthy)
+
 
 bzr 1.6beta3 2008-07-17
 -----------------------

=== modified file 'bzrlib/builtins.py'
--- a/bzrlib/builtins.py	2008-07-29 08:40:05 +0000
+++ b/bzrlib/builtins.py	2008-08-04 07:29:51 +0000
@@ -87,13 +87,27 @@
     if file_list is None or len(file_list) == 0:
         return WorkingTree.open_containing(default_branch)[0], file_list
     tree = WorkingTree.open_containing(osutils.realpath(file_list[0]))[0]
+    return tree, safe_relpath_files(tree, file_list)
+
+
+def safe_relpath_files(tree, file_list):
+    """Convert file_list into a list of relpaths in tree.
+
+    :param tree: A tree to operate on.
+    :param file_list: A list of user provided paths or None.
+    :return: A list of relative paths.
+    :raises errors.PathNotChild: When a provided path is in a different tree
+        than tree.
+    """
+    if file_list is None:
+        return None
     new_list = []
     for filename in file_list:
         try:
             new_list.append(tree.relpath(osutils.dereference_path(filename)))
         except errors.PathNotChild:
             raise errors.FileInWrongBranch(tree.branch, filename)
-    return tree, new_list
+    return new_list
 
 
 # TODO: Make sure no commands unconditionally use the working directory as a
@@ -2106,6 +2120,12 @@
     committed.  If a directory is specified then the directory and everything 
     within it is committed.
 
+    When excludes are given, they take precedence over selected files.
+    For example, too commit only changes within foo, but not changes within
+    foo/bar::
+
+      bzr commit foo -x foo/bar
+
     If author of the change is not the same person as the committer, you can
     specify the author's name using the --author option. The name should be
     in the same format as a committer-id, e.g. "John Doe <jdoe at example.com>".
@@ -2141,6 +2161,8 @@
     _see_also = ['bugs', 'uncommit']
     takes_args = ['selected*']
     takes_options = [
+            ListOption('exclude', type=str, short_name='x',
+                help="Do not consider changes made to a given path."),
             Option('message', type=unicode,
                    short_name='m',
                    help="Description of the new revision."),
@@ -2195,7 +2217,7 @@
 
     def run(self, message=None, file=None, verbose=False, selected_list=None,
             unchanged=False, strict=False, local=False, fixes=None,
-            author=None, show_diff=False):
+            author=None, show_diff=False, exclude=None):
         from bzrlib.errors import (
             PointlessCommit,
             ConflictsInTree,
@@ -2245,7 +2267,7 @@
                 raise errors.BzrCommandError(
                     "please specify either --message or --file")
             if file:
-                my_message = codecs.open(file, 'rt', 
+                my_message = codecs.open(file, 'rt',
                                          bzrlib.user_encoding).read()
             if my_message == "":
                 raise errors.BzrCommandError("empty commit message specified")
@@ -2256,7 +2278,8 @@
                         specific_files=selected_list,
                         allow_pointless=unchanged, strict=strict, local=local,
                         reporter=None, verbose=verbose, revprops=properties,
-                        author=author)
+                        author=author,
+                        exclude=safe_relpath_files(tree, exclude))
         except PointlessCommit:
             # FIXME: This should really happen before the file is read in;
             # perhaps prepare the commit; get the message; then actually commit

=== modified file 'bzrlib/commit.py'
--- a/bzrlib/commit.py	2008-04-24 07:22:53 +0000
+++ b/bzrlib/commit.py	2008-08-04 07:29:51 +0000
@@ -204,7 +204,8 @@
                reporter=None,
                config=None,
                message_callback=None,
-               recursive='down'):
+               recursive='down',
+               exclude=None):
         """Commit working copy as a new revision.
 
         :param message: the commit message (it or message_callback is required)
@@ -232,6 +233,9 @@
         :param verbose: if True and the reporter is not None, report everything
         :param recursive: If set to 'down', commit in any subtrees that have
             pending changes of any sort during this commit.
+        :param exclude: None or a list of relative paths to exclude from the
+            commit. Pending changes to excluded files will be ignored by the
+            commit. 
         """
         mutter('preparing to commit')
 
@@ -255,6 +259,11 @@
         self.bound_branch = None
         self.any_entries_changed = False
         self.any_entries_deleted = False
+        if exclude is not None:
+            self.exclude = sorted(
+                minimum_path_selection(exclude))
+        else:
+            self.exclude = []
         self.local = local
         self.master_branch = None
         self.master_locked = False
@@ -329,12 +338,15 @@
             self.pb.show_count = True
             self.pb.show_bar = True
 
+            self.basis_inv = self.basis_tree.inventory
+            self._gather_parents()
             # After a merge, a selected file commit is not supported.
             # See 'bzr help merge' for an explanation as to why.
-            self.basis_inv = self.basis_tree.inventory
-            self._gather_parents()
             if len(self.parents) > 1 and self.specific_files:
                 raise errors.CannotCommitSelectedFileMerge(self.specific_files)
+            # Excludes are a form of selected file commit.
+            if len(self.parents) > 1 and self.exclude:
+                raise errors.CannotCommitSelectedFileMerge(self.exclude)
 
             # Collect the changes
             self._set_progress_stage("Collecting changes",
@@ -648,28 +660,30 @@
         # in bugs like #46635.  Any reason not to use/enhance Tree.changes_from?
         # ADHB 11-07-2006
 
+        exclude = self.exclude
         specific_files = self.specific_files
         mutter("Selecting files for commit with filter %s", specific_files)
 
         # Build the new inventory
-        self._populate_from_inventory(specific_files)
+        self._populate_from_inventory()
 
         # If specific files are selected, then all un-selected files must be
         # recorded in their previous state. For more details, see
         # https://lists.ubuntu.com/archives/bazaar/2007q3/028476.html.
-        if specific_files:
+        if specific_files or exclude:
             for path, old_ie in self.basis_inv.iter_entries():
                 if old_ie.file_id in self.builder.new_inventory:
                     # already added - skip.
                     continue
-                if is_inside_any(specific_files, path):
-                    # was inside the selected path, if not present it has been
-                    # deleted so skip.
+                if (is_inside_any(specific_files, path)
+                    and not is_inside_any(exclude, path)):
+                    # was inside the selected path, and not excluded - if not
+                    # present it has been deleted so skip.
                     continue
+                # From here down it was either not selected, or was excluded:
                 if old_ie.kind == 'directory':
                     self._next_progress_entry()
-                # not in final inv yet, was not in the selected files, so is an
-                # entry to be preserved unaltered.
+                # We preserve the entry unaltered.
                 ie = old_ie.copy()
                 # Note: specific file commits after a merge are currently
                 # prohibited. This test is for sanity/safety in case it's
@@ -697,13 +711,15 @@
                 self._basis_delta.append((path, None, file_id, None))
                 self.reporter.deleted(path)
 
-    def _populate_from_inventory(self, specific_files):
+    def _populate_from_inventory(self):
         """Populate the CommitBuilder by walking the working tree inventory."""
         if self.strict:
             # raise an exception as soon as we find a single unknown.
             for unknown in self.work_tree.unknowns():
                 raise StrictCommitFailed()
-               
+        
+        specific_files = self.specific_files
+        exclude = self.exclude
         report_changes = self.reporter.is_verbose()
         deleted_ids = []
         # A tree of paths that have been deleted. E.g. if foo/bar has been
@@ -712,6 +728,8 @@
         # XXX: Note that entries may have the wrong kind because the entry does
         # not reflect the status on disk.
         work_inv = self.work_tree.inventory
+        # NB: entries will include entries within the excluded ids/paths
+        # because iter_entries_by_dir has no 'exclude' facility today.
         entries = work_inv.iter_entries_by_dir(
             specific_file_ids=self.specific_file_ids, yield_parents=True)
         for path, existing_ie in entries:
@@ -739,6 +757,10 @@
                 if deleted_dict is not None:
                     # the path has a deleted parent, do not add it.
                     continue
+            if exclude and is_inside_any(exclude, path):
+                # Skip - it is to be considered by the final copy-from-basis
+                # step.
+                continue
             content_summary = self.work_tree.path_content_summary(path)
             # Note that when a filter of specific files is given, we must only
             # skip/record deleted files matching that filter.

=== modified file 'bzrlib/tests/blackbox/test_commit.py'
--- a/bzrlib/tests/blackbox/test_commit.py	2007-12-10 16:39:00 +0000
+++ b/bzrlib/tests/blackbox/test_commit.py	2008-08-04 07:29:51 +0000
@@ -331,6 +331,34 @@
         self.build_tree_contents([('u1/hosts', 'merge resolution\n')])
         self.run_bzr('commit -m checkin-merge-of-the-offline-work-from-u1 u1')
 
+    def test_commit_exclude_excludes_modified_files(self):
+        """Commit -x foo should ignore changes to foo."""
+        tree = self.make_branch_and_tree('.')
+        self.build_tree(['a', 'b', 'c'])
+        tree.smart_add(['.'])
+        out, err = self.run_bzr(['commit', '-m', 'test', '-x', 'b'])
+        self.assertFalse('added b' in out)
+        self.assertFalse('added b' in err)
+        # If b was ignored it will still be 'added' in status.
+        out, err = self.run_bzr(['added'])
+        self.assertEqual('b\n', out)
+        self.assertEqual('', err)
+
+    def test_commit_exclude_twice_uses_both_rules(self):
+        """Commit -x foo -x bar should ignore changes to foo and bar."""
+        tree = self.make_branch_and_tree('.')
+        self.build_tree(['a', 'b', 'c'])
+        tree.smart_add(['.'])
+        out, err = self.run_bzr(['commit', '-m', 'test', '-x', 'b', '-x', 'c'])
+        self.assertFalse('added b' in out)
+        self.assertFalse('added c' in out)
+        self.assertFalse('added b' in err)
+        self.assertFalse('added c' in err)
+        # If b was ignored it will still be 'added' in status.
+        out, err = self.run_bzr(['added'])
+        self.assertEqual('b\nc\n', out)
+        self.assertEqual('', err)
+
     def test_commit_respects_spec_for_removals(self):
         """Commit with a file spec should only commit removals that match"""
         t = self.make_branch_and_tree('.')

=== modified file 'bzrlib/tests/workingtree_implementations/test_commit.py'
--- a/bzrlib/tests/workingtree_implementations/test_commit.py	2008-04-20 08:21:39 +0000
+++ b/bzrlib/tests/workingtree_implementations/test_commit.py	2008-08-04 07:29:51 +0000
@@ -195,6 +195,31 @@
                           ('xyz/m', 'm-id'),
                          ], paths)
 
+    def test_commit_exclude_pending_merge_fails(self):
+        """Excludes are a form of partial commit."""
+        wt = self.make_branch_and_tree('.')
+        self.build_tree(['foo'])
+        wt.add('foo')
+        wt.commit('commit one')
+        wt2 = wt.bzrdir.sprout('to').open_workingtree()
+        wt2.commit('change_right')
+        wt.merge_from_branch(wt2.branch)
+        self.assertRaises(errors.CannotCommitSelectedFileMerge,
+            wt.commit, 'test', exclude=['foo'])
+
+    def test_commit_exclude_excludes_modified_files(self):
+        tree = self.make_branch_and_tree('.')
+        self.build_tree(['a', 'b', 'c'])
+        tree.smart_add(['.'])
+        tree.commit('test', exclude=['b', 'c'])
+        # If b was ignored it will still be 'added' in status.
+        tree.lock_read()
+        self.addCleanup(tree.unlock)
+        changes = list(tree.iter_changes(tree.basis_tree()))
+        self.assertEqual(2, len(changes))
+        self.assertEqual((None, 'b'), changes[0][1])
+        self.assertEqual((None, 'c'), changes[1][1])
+
     def test_commit_sets_last_revision(self):
         tree = self.make_branch_and_tree('tree')
         committed_id = tree.commit('foo', rev_id='foo')




More information about the bazaar-commits mailing list