# Bazaar merge directive format 2 (Bazaar 0.90)
# revision_id: mhammond@skippinet.com.au-20081221102412-\
#   21b17ndgi4b94qgh
# target_branch: file:///O:/src/bazaar/bzr/bzr.dev/
# testament_sha1: 5372011db1d3d8f065ae5c40afc7069b807de255
# timestamp: 2008-12-21 21:24:15 +1100
# base_revision_id: pqm@pqm.ubuntu.com-20081219200818-g19t5zbtkj52bwqp
# 
# Begin patch
=== modified file 'NEWS'
--- NEWS	2008-12-19 20:08:18 +0000
+++ NEWS	2008-12-21 10:08:01 +0000
@@ -34,6 +34,12 @@
     * ``bzr revision-info`` now supports a -d option to specify an
       alternative branch. (Michael Hudson)
 
+    * When working on a case-insensitive case-preserving file-systems, as
+      commonly found with Windows, bzr will often ignore the case of the
+      arguments specified by the user in preference to the case of an existing
+      item on the file-system or in the inventory to help prevent
+      counter-intuitive behaviour on Windows.
+
   BUG FIXES:
   
     * Fix a problem with CIFS client/server lag on windows colliding with

=== modified file 'bzrlib/builtins.py'
--- bzrlib/builtins.py	2008-12-16 16:05:28 +0000
+++ bzrlib/builtins.py	2008-12-21 07:42:20 +0000
@@ -210,6 +210,8 @@
                 no_pending = True
         # A specific path within a tree was given.
         elif relfile_list is not None:
+            # convert the names to canonical versions stored in the inventory
+            relfile_list = tree.get_canonical_inventory_paths(relfile_list)
             no_pending = True
         show_tree_status(tree, show_ids=show_ids,
                          specific_files=relfile_list, revision=revision,
@@ -675,13 +677,18 @@
                 into_existing = False
             else:
                 inv = tree.inventory
-                from_id = tree.path2id(rel_names[0])
+                # 'fix' the case of a potential 'from'
+                from_id = tree.path2id(
+                            tree.get_canonical_inventory_path(rel_names[0]))
                 if (not osutils.lexists(names_list[0]) and
                     from_id and inv.get_file_kind(from_id) == "directory"):
                     into_existing = False
         # move/rename
         if into_existing:
             # move into existing directory
+            # All entries reference existing inventory items, so fix them up
+            # for cicp file-systems.
+            rel_names = tree.get_canonical_inventory_paths(rel_names)
             for pair in tree.move(rel_names[:-1], rel_names[-1], after=after):
                 self.outf.write("%s => %s\n" % pair)
         else:
@@ -689,8 +696,51 @@
                 raise errors.BzrCommandError('to mv multiple files the'
                                              ' destination must be a versioned'
                                              ' directory')
-            tree.rename_one(rel_names[0], rel_names[1], after=after)
-            self.outf.write("%s => %s\n" % (rel_names[0], rel_names[1]))
+
+            # for cicp file-systems: the src references an existing inventory
+            # item:
+            src = tree.get_canonical_inventory_path(rel_names[0])
+            # Find the canonical version of the destination:  In all cases, the
+            # parent of the target must be in the inventory, so we fetch the
+            # canonical version from there (we do not always *use* the
+            # canonicalized tail portion - we may be attempting to rename the
+            # case of the tail)
+            canon_dest = tree.get_canonical_inventory_path(rel_names[1])
+            dest_parent = osutils.dirname(canon_dest)
+            spec_tail = osutils.basename(rel_names[1])
+            # For a CICP file-system, we need to avoid creating 2 inventory
+            # entries that differ only by case.  So regardless of the case
+            # we *want* to use (ie, specified by the user or the file-system),
+            # we must always choose to use the case of any existing inventory
+            # items.  The only exception to this is when we are attempting a
+            # case-only rename (ie, canonical versions of src and dest are
+            # the same)
+            dest_id = tree.path2id(canon_dest)
+            if dest_id is None or tree.path2id(src) == dest_id:
+                # No existing item we care about, so work out what case we
+                # are actually going to use.
+                if after:
+                    # If 'after' is specified, the tail must refer to a file on disk.
+                    if dest_parent:
+                        dest_parent_fq = osutils.pathjoin(tree.basedir, dest_parent)
+                    else:
+                        # pathjoin with an empty tail adds a slash, which breaks
+                        # relpath :(
+                        dest_parent_fq = tree.basedir
+    
+                    dest_tail = osutils.canonical_relpath(
+                                    dest_parent_fq,
+                                    osutils.pathjoin(dest_parent_fq, spec_tail))
+                else:
+                    # not 'after', so case as specified is used
+                    dest_tail = spec_tail
+            else:
+                # Use the existing item so 'mv' fails with AlreadyVersioned.
+                dest_tail = os.path.basename(canon_dest)
+            dest = osutils.pathjoin(dest_parent, dest_tail)
+            mutter("attempting to move %s => %s", src, dest)
+            tree.rename_one(src, dest, after=after)
+            self.outf.write("%s => %s\n" % (src, dest))
 
 
 class cmd_pull(Command):
@@ -1222,7 +1272,7 @@
         tree, file_list = tree_files(file_list)
 
         if file_list is not None:
-            file_list = [f for f in file_list]
+            file_list = tree.get_canonical_inventory_paths(file_list)
 
         tree.lock_write()
         try:
@@ -2413,6 +2463,8 @@
             # as just default commit in that tree, and succeed even though
             # selected-file merge commit is not done yet
             selected_list = []
+        if selected_list:
+            selected_list = tree.get_canonical_inventory_paths(selected_list)
 
         if fixes is None:
             fixes = []

=== modified file 'bzrlib/mutabletree.py'
--- bzrlib/mutabletree.py	2008-11-18 21:50:10 +0000
+++ bzrlib/mutabletree.py	2008-12-21 07:42:20 +0000
@@ -75,6 +75,12 @@
     A mutable tree always has an associated Branch and BzrDir object - the
     branch and bzrdir attributes.
     """
+    def __init__(self, *args, **kw):
+        super(MutableTree, self).__init__(*args, **kw)
+        # Is this tree on a case-insensitive or case-preserving file-system?
+        # Sub-classes may initialize to False if they detect they are being
+        # used on media which doesn't differentiate the case of names.
+        self.case_sensitive = True
 
     @needs_tree_write_lock
     def add(self, files, ids=None, kinds=None):
@@ -283,6 +289,12 @@
         :return: None
         """
 
+    def fix_case_of_inventory_path(self, path):
+        """If our tree isn't case sensitive, return the canonical path"""
+        if not self.case_sensitive:
+            path = self.get_canonical_inventory_path(path)
+        return path
+
     @needs_write_lock
     def put_file_bytes_non_atomic(self, file_id, bytes):
         """Update the content of a file in the tree.
@@ -347,9 +359,10 @@
 
         # validate user file paths and convert all paths to tree 
         # relative : it's cheaper to make a tree relative path an abspath
-        # than to convert an abspath to tree relative.
-        for filepath in file_list:
-            rf = _FastPath(self.relpath(filepath))
+        # than to convert an abspath to tree relative, and its cheaper to
+        # perform the canonicalization in bulk.
+        for filepath in osutils.canonical_relpaths(self.basedir, file_list):
+            rf = _FastPath(filepath)
             # validate user parameters. Our recursive code avoids adding new files
             # that need such validation 
             if self.is_control_filename(rf.raw_path):
@@ -410,7 +423,8 @@
             else:
                 # without the parent ie, use the relatively slower inventory 
                 # probing method
-                versioned = inv.has_filename(directory.raw_path)
+                versioned = inv.has_filename(
+                        self.fix_case_of_inventory_path(directory.raw_path))
 
             if kind == 'directory':
                 try:
@@ -449,7 +463,8 @@
                 else:
                     # without the parent ie, use the relatively slower inventory 
                     # probing method
-                    this_id = inv.path2id(directory.raw_path)
+                    this_id = inv.path2id(
+                            self.fix_case_of_inventory_path(directory.raw_path))
                     if this_id is None:
                         this_ie = None
                     else:
@@ -581,7 +596,7 @@
         added = []
     else:
         # slower but does not need parent_ie
-        if inv.has_filename(path.raw_path):
+        if inv.has_filename(tree.fix_case_of_inventory_path(path.raw_path)):
             return []
         # its really not there : add the parent
         # note that the dirname use leads to some extra str copying etc but as

=== modified file 'bzrlib/osutils.py'
--- bzrlib/osutils.py	2008-12-11 19:37:06 +0000
+++ bzrlib/osutils.py	2008-12-21 10:24:12 +0000
@@ -962,6 +962,64 @@
         return ''
 
 
+def _cicp_canonical_relpath(base, path):
+    """Return the canonical path relative to base.
+
+    Like relpath, but on case-insensitive-case-preserving file-systems, this
+    will return the relpath as stored on the file-system rather than in the
+    case specified in the input string, for all existing portions of the path.
+
+    This will cause O(N) behaviour if called for every path in a tree; if you
+    have a number of paths to convert, you should use canonical_relpaths().
+
+    TODO: it should be possible to optimize this for Windows by using the
+    win32 API FindFiles function to look for the specified name - but using
+    os.listdir() still gives us the correct, platform agnostic semantics in
+    the short term.
+    """
+    rel = relpath(base, path)
+    # '.' will have been turned into ''
+    if not rel:
+        return rel
+
+    abs_base = abspath(base)
+    current = abs_base
+    _listdir = os.listdir
+
+    # use an explicit iterator so we can easily consume the rest on early exit.
+    bit_iter = rel.split('/')
+    for bit in bit_iter:
+        lbit = bit.lower()
+        for look in _listdir(current):
+            if lbit == look.lower():
+                current = pathjoin(current, look)
+                break
+        else:
+            # got to the end, nothing matched, so we just return the
+            # non-existing bits as they were specified (the filename may be
+            # the target of a move, for example).
+            current = pathjoin(current, bit, *list(bit_iter))
+            break
+    return current[len(abs_base)+1:]
+
+# XXX - TODO - we need better detection/integration of case-insensitive
+# file-systems; Linux often sees FAT32 devices, for example, so could
+# probably benefit from the same basic support there.  For now though, only
+# Windows gets that support, and it gets it for *all* file-systems!
+if sys.platform == "win32":
+    canonical_relpath = _cicp_canonical_relpath
+else:
+    canonical_relpath = relpath
+
+def canonical_relpaths(base, paths):
+    """Create an iterable to canonicalize a sequence of relative paths.
+
+    The intent is for this implementation to use a cache, vastly speeding
+    up multiple transformations in the same directory.
+    """
+    # but for now, we haven't optimized...
+    return [canonical_relpath(base, p) for p in paths]
+
 def safe_unicode(unicode_or_utf8_string):
     """Coerce unicode_or_utf8_string into unicode.
 

=== modified file 'bzrlib/tests/__init__.py'
--- bzrlib/tests/__init__.py	2008-12-19 20:08:18 +0000
+++ bzrlib/tests/__init__.py	2008-12-21 09:49:46 +0000
@@ -3326,12 +3326,41 @@
 UTF8Filesystem = _UTF8Filesystem()
 
 
+class _CaseInsCasePresFilenameFeature(Feature):
+    """Is the file-system case insensitive, but case-preserving?"""
+
+    def _probe(self):
+        fileno, name = tempfile.mkstemp(prefix='MixedCase')
+        try:
+            # first check truly case-preserving for created files, then check
+            # case insensitive when opening existing files.
+            name = osutils.normpath(name)
+            base, rel = osutils.split(name)
+            found_rel = osutils.canonical_relpath(base, name)
+            return (found_rel==rel
+                    and os.path.isfile(name.upper())
+                    and os.path.isfile(name.lower()))
+        finally:
+            os.close(fileno)
+            os.remove(name)
+
+    def feature_name(self):
+        return "case-insensitive case-preserving filesystem"
+
+CaseInsCasePresFilenameFeature = _CaseInsCasePresFilenameFeature()
+
+
 class _CaseInsensitiveFilesystemFeature(Feature):
-    """Check if underlying filesystem is case-insensitive
-    (e.g. on Windows, Cygwin, MacOS)
+    """Check if underlying filesystem is case-insensitive but *not* case
+    preserving.
     """
+    # Note that on Windows, Cygwin, MacOS etc, the file-systems are far
+    # more likely to be case preserving, so this case is rare.
 
     def _probe(self):
+        if CaseInsCasePresFilenameFeature.available():
+            return False
+
         if TestCaseWithMemoryTransport.TEST_ROOT is None:
             root = osutils.mkdtemp(prefix='testbzr-', suffix='.tmp')
             TestCaseWithMemoryTransport.TEST_ROOT = root
@@ -3350,3 +3379,4 @@
         return 'case-insensitive filesystem'
 
 CaseInsensitiveFilesystemFeature = _CaseInsensitiveFilesystemFeature()
+

=== modified file 'bzrlib/tests/blackbox/__init__.py'
--- bzrlib/tests/blackbox/__init__.py	2008-12-03 05:31:27 +0000
+++ bzrlib/tests/blackbox/__init__.py	2008-12-21 07:42:20 +0000
@@ -65,6 +65,7 @@
                      'bzrlib.tests.blackbox.test_dump_btree',
                      'bzrlib.tests.blackbox.test_exceptions',
                      'bzrlib.tests.blackbox.test_export',
+                     'bzrlib.tests.blackbox.test_filesystem_cicp',
                      'bzrlib.tests.blackbox.test_find_merge_base',
                      'bzrlib.tests.blackbox.test_help',
                      'bzrlib.tests.blackbox.test_hooks',

=== added file 'bzrlib/tests/blackbox/test_filesystem_cicp.py'
--- bzrlib/tests/blackbox/test_filesystem_cicp.py	1970-01-01 00:00:00 +0000
+++ bzrlib/tests/blackbox/test_filesystem_cicp.py	2008-12-21 07:40:59 +0000
@@ -0,0 +1,225 @@
+# Copyright (C) 2008 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
+#
+
+"""Tests variations of case-insensitive and case-preserving file-systems."""
+
+import os
+
+from bzrlib.tests.blackbox import ExternalBase
+from bzrlib.tests import CaseInsCasePresFilenameFeature, KnownFailure
+from bzrlib.osutils import canonical_relpath
+
+class TestCICPBase(ExternalBase):
+    """Base class for tests on a case-insensitive, case-preserving filesystem.
+    """
+
+    _test_needs_features = [CaseInsCasePresFilenameFeature]
+
+    def _make_mixed_case_tree(self):
+        """Make a working tree with mixed-case filenames."""
+        wt = self.make_branch_and_tree('.')
+        # create a file on disk with the mixed-case parent and base name
+        self.build_tree(['CamelCaseParent/', 'lowercaseparent/'])
+        self.build_tree_contents([('CamelCaseParent/CamelCase', 'camel case'),
+                                  ('lowercaseparent/lowercase', 'lower case'),
+                                  ('lowercaseparent/mixedCase', 'mixedCasecase'),
+                                 ])
+        return wt
+
+    def check_error_output(self, retcode, output, *args):
+        got = self.run_bzr(retcode=retcode, *args)[1]
+        self.failUnlessEqual(got, output)
+
+class TestAdd(TestCICPBase):
+    def test_add_simple(self):
+        """Test add always uses the case of the filename reported by the os."""
+        wt = self.make_branch_and_tree('.')
+        # create a file on disk with the mixed-case name
+        self.build_tree(['CamelCase'])
+
+        self.check_output('added CamelCase\n', 'add camelcase')
+
+    def test_add_subdir(self):
+        """test_add_simple but with subdirectories tested too."""
+        wt = self.make_branch_and_tree('.')
+        # create a file on disk with the mixed-case parent and base name
+        self.build_tree(['CamelCaseParent/', 'CamelCaseParent/CamelCase'])
+
+        self.check_output('added CamelCaseParent\nadded CamelCaseParent/CamelCase\n',
+                          'add camelcaseparent/camelcase')
+
+    def test_add_implied(self):
+        """test add with no args sees the correct names."""
+        wt = self.make_branch_and_tree('.')
+        # create a file on disk with the mixed-case parent and base name
+        self.build_tree(['CamelCaseParent/', 'CamelCaseParent/CamelCase'])
+
+        self.check_output('added CamelCaseParent\nadded CamelCaseParent/CamelCase\n',
+                          'add')
+
+    def test_re_add(self):
+        """Test than when a file has 'unintentionally' changed case, we can't
+        add a new entry using the new case."""
+        wt = self.make_branch_and_tree('.')
+        # create a file on disk with the mixed-case name
+        self.build_tree(['MixedCase'])
+        self.check_output('added MixedCase\n', 'add MixedCase')
+        # 'accidently' rename the file on disk
+        os.rename('MixedCase', 'mixedcase')
+        self.check_output('', 'add mixedcase')
+
+    def test_re_add_dir(self):
+        # like re-add, but tests when the operation is on a directory.
+        """Test than when a file has 'unintentionally' changed case, we can't
+        add a new entry using the new case."""
+        wt = self.make_branch_and_tree('.')
+        # create a file on disk with the mixed-case name
+        self.build_tree(['MixedCaseParent/', 'MixedCaseParent/MixedCase'])
+        self.check_output('added MixedCaseParent\nadded MixedCaseParent/MixedCase\n',
+                          'add MixedCaseParent')
+        # 'accidently' rename the directory on disk
+        os.rename('MixedCaseParent', 'mixedcaseparent')
+        self.check_output('', 'add mixedcaseparent')
+
+
+class TestMove(TestCICPBase):
+    def test_mv_newname(self):
+        wt = self._make_mixed_case_tree()
+        self.run_bzr('add')
+        self.run_bzr('ci -m message')
+
+        self.check_output('CamelCaseParent/CamelCase => CamelCaseParent/NewCamelCase\n',
+                          'mv camelcaseparent/camelcase camelcaseparent/NewCamelCase')
+
+    def test_mv_newname_after(self):
+        wt = self._make_mixed_case_tree()
+        self.run_bzr('add')
+        self.run_bzr('ci -m message')
+        os.rename('CamelCaseParent/CamelCase', 'CamelCaseParent/NewCamelCase')
+
+        # In this case we can specify the incorrect case for the destination,
+        # as we use --after, so the file-system is sniffed.
+        self.check_output('CamelCaseParent/CamelCase => CamelCaseParent/NewCamelCase\n',
+                          'mv --after camelcaseparent/camelcase camelcaseparent/newcamelcase')
+
+    def test_mv_newname_exists(self):
+        # test a mv, but when the target already exists with a name that
+        # differs only by case.
+        wt = self._make_mixed_case_tree()
+        self.run_bzr('add')
+        self.run_bzr('ci -m message')
+        ex = 'bzr: ERROR: Could not move CamelCase => lowercase: lowercaseparent/lowercase is already versioned.\n'
+        self.check_error_output(3, ex, 'mv camelcaseparent/camelcase LOWERCASEPARENT/LOWERCASE')
+
+    def test_mv_newname_exists_after(self):
+        # test a 'mv --after', but when the target already exists with a name
+        # that differs only by case.  Note that this is somewhat unlikely
+        # but still reasonable.
+        wt = self._make_mixed_case_tree()
+        self.run_bzr('add')
+        self.run_bzr('ci -m message')
+        # Remove the source and create a destination file on disk with a different case.
+        # bzr should report that the filename is already versioned.
+        os.unlink('CamelCaseParent/CamelCase')
+        os.rename('lowercaseparent/lowercase', 'lowercaseparent/LOWERCASE')
+        ex = 'bzr: ERROR: Could not move CamelCase => lowercase: lowercaseparent/lowercase is already versioned.\n'
+        self.check_error_output(3, ex, 'mv --after camelcaseparent/camelcase LOWERCASEPARENT/LOWERCASE')
+
+    def test_mv_newname_root(self):
+        wt = self._make_mixed_case_tree()
+        self.run_bzr('add')
+        self.run_bzr('ci -m message')
+
+        self.check_output('CamelCaseParent => NewCamelCaseParent\n',
+                          'mv camelcaseparent NewCamelCaseParent')
+
+    def test_mv_newname_root_after(self):
+        wt = self._make_mixed_case_tree()
+        self.run_bzr('add')
+        self.run_bzr('ci -m message')
+        os.rename('CamelCaseParent', 'NewCamelCaseParent')
+
+        # In this case we can specify the incorrect case for the destination,
+        # as we use --after, so the file-system is sniffed.
+        self.check_output('CamelCaseParent => NewCamelCaseParent\n',
+                          'mv --after camelcaseparent newcamelcaseparent')
+
+    def test_mv_newcase(self):
+        wt = self._make_mixed_case_tree()
+        self.run_bzr('add')
+        self.run_bzr('ci -m message')
+
+        # perform a mv to the new case - we expect bzr to accept the new
+        # name, as specified, and rename the file on the file-system too.
+        self.check_output('CamelCaseParent/CamelCase => CamelCaseParent/camelCase\n',
+                          'mv camelcaseparent/camelcase camelcaseparent/camelCase')
+        self.failUnlessEqual(canonical_relpath(wt.basedir, 'camelcaseparent/camelcase'),
+                             'CamelCaseParent/camelCase')
+
+    def test_mv_newcase_after(self):
+        wt = self._make_mixed_case_tree()
+        self.run_bzr('add')
+        self.run_bzr('ci -m message')
+
+        # perform a mv to the new case - we must ensure the file-system has the
+        # new case first.
+        os.rename('CamelCaseParent/CamelCase', 'CamelCaseParent/camelCase')
+        self.check_output('CamelCaseParent/CamelCase => CamelCaseParent/camelCase\n',
+                          'mv --after camelcaseparent/camelcase camelcaseparent/camelCase')
+        # bzr should not have renamed the file to a different case
+        self.failUnlessEqual(canonical_relpath(wt.basedir, 'camelcaseparent/camelcase'),
+                             'CamelCaseParent/camelCase')
+
+    def test_mv_multiple(self):
+        wt = self._make_mixed_case_tree()
+        self.run_bzr('add')
+        self.run_bzr('ci -m message')
+        self.check_output('lowercaseparent/lowercase => CamelCaseParent/lowercase\n'
+                          'lowercaseparent/mixedCase => CamelCaseParent/mixedCase\n',
+                          'mv LOWercaseparent/LOWercase LOWercaseparent/MIXEDCase camelcaseparent')
+
+
+class TestMisc(TestCICPBase):
+    def test_status(self):
+        wt = self._make_mixed_case_tree()
+        self.run_bzr('add')
+
+        self.check_output('added:\n  CamelCaseParent/CamelCase\n  lowercaseparent/lowercase\n',
+                          'status camelcaseparent/camelcase LOWERCASEPARENT/LOWERCASE')
+
+    def test_ci(self):
+        wt = self._make_mixed_case_tree()
+        self.run_bzr('add')
+
+        got = self.run_bzr('ci -m message camelcaseparent LOWERCASEPARENT')[1]
+        for expected in ['CamelCaseParent', 'lowercaseparent',
+                         'CamelCaseParent/CamelCase', 'lowercaseparent/lowercase']:
+            self.assertContainsRe(got, 'added ' + expected + '\n')
+
+    def test_rm(self):
+        wt = self._make_mixed_case_tree()
+        self.run_bzr('add')
+        self.run_bzr('ci -m message')
+
+        got = self.run_bzr('rm camelcaseparent LOWERCASEPARENT')[1]
+        for expected in ['lowercaseparent/lowercase', 'CamelCaseParent/CamelCase']:
+            self.assertContainsRe(got, 'deleted ' + expected + '\n')
+
+
+    # The following commands need tests and/or cicp lovin':
+    # update, remove, file_id, file_path, diff, log, touching_revisions, ls,
+    # ignore, cat, revert.

=== modified file 'bzrlib/tests/test_transform.py'
--- bzrlib/tests/test_transform.py	2008-10-26 20:40:39 +0000
+++ bzrlib/tests/test_transform.py	2008-12-21 09:48:09 +0000
@@ -42,7 +42,6 @@
 from bzrlib.osutils import file_kind, pathjoin
 from bzrlib.merge import Merge3Merger, Merger
 from bzrlib.tests import (
-    CaseInsensitiveFilesystemFeature,
     HardlinkFeature,
     SymlinkFeature,
     TestCase,

=== modified file 'bzrlib/tests/tree_implementations/test_inv.py'
--- bzrlib/tests/tree_implementations/test_inv.py	2008-07-24 16:08:28 +0000
+++ bzrlib/tests/tree_implementations/test_inv.py	2008-12-21 07:33:08 +0000
@@ -124,3 +124,47 @@
         self.addCleanup(tree.unlock)
         self.assertEqual(set([]), tree.paths2ids(['file'],
                          require_versioned=False))
+
+    def test_canonical_path(self):
+        work_tree = self.make_branch_and_tree('tree')
+        self.build_tree(['tree/dir/', 'tree/dir/file'])
+        work_tree.add(['dir', 'dir/file'])
+        work_tree.commit('commit 1')
+        self.assertEqual(work_tree.get_canonical_inventory_path('Dir/File'), 'dir/file')
+
+    def test_canonical_path_before_commit(self):
+        work_tree = self.make_branch_and_tree('tree')
+        self.build_tree(['tree/dir/', 'tree/dir/file'])
+        work_tree.add(['dir', 'dir/file'])
+        # note: not committed.
+        self.assertEqual(work_tree.get_canonical_inventory_path('Dir/File'), 'dir/file')
+
+    def test_canonical_path_dir(self):
+        # check it works when asked for just the directory portion.
+        work_tree = self.make_branch_and_tree('tree')
+        self.build_tree(['tree/dir/', 'tree/dir/file'])
+        work_tree.add(['dir', 'dir/file'])
+        work_tree.commit('commit 1')
+        self.assertEqual(work_tree.get_canonical_inventory_path('Dir'), 'dir')
+
+    def test_canonical_path_root(self):
+        work_tree = self.make_branch_and_tree('tree')
+        self.build_tree(['tree/dir/', 'tree/dir/file'])
+        work_tree.add(['dir', 'dir/file'])
+        work_tree.commit('commit 1')
+        self.assertEqual(work_tree.get_canonical_inventory_path(''), '')
+        self.assertEqual(work_tree.get_canonical_inventory_path('/'), '/')
+
+    def test_canonical_path_invalid_all(self):
+        work_tree = self.make_branch_and_tree('tree')
+        self.build_tree(['tree/dir/', 'tree/dir/file'])
+        work_tree.add(['dir', 'dir/file'])
+        work_tree.commit('commit 1')
+        self.assertEqual(work_tree.get_canonical_inventory_path('foo/bar'), 'foo/bar')
+
+    def test_canonical_invalid_child(self):
+        work_tree = self.make_branch_and_tree('tree')
+        self.build_tree(['tree/dir/', 'tree/dir/file'])
+        work_tree.add(['dir', 'dir/file'])
+        work_tree.commit('commit 1')
+        self.assertEqual(work_tree.get_canonical_inventory_path('Dir/None'), 'dir/None')

=== modified file 'bzrlib/tree.py'
--- bzrlib/tree.py	2008-10-28 09:16:22 +0000
+++ bzrlib/tree.py	2008-12-21 07:30:12 +0000
@@ -31,7 +31,7 @@
     symbol_versioning,
     )
 from bzrlib.decorators import needs_read_lock
-from bzrlib.errors import BzrError, BzrCheckError
+from bzrlib.errors import BzrError, BzrCheckError, NoSuchId
 from bzrlib import errors
 from bzrlib.inventory import Inventory, InventoryFile
 from bzrlib.inter import InterObject
@@ -345,6 +345,63 @@
         """
         raise NotImplementedError(self.get_symlink_target)
 
+    def get_canonical_inventory_paths(self, paths):
+        """Returns a list with each item the first path that
+        case-insensitively matches the specified input paths.
+
+        If a path matches exactly, it is returned. If no path matches exactly
+        but more than one path matches case-insensitively, it is implementation
+        defined which is returned.
+
+        If no path matches case-insensitively, the input path is returned, but
+        with as many path entries that do exist changed to their canonical form.
+
+        :param paths: A sequence of paths relative to the root of the tree.
+        :return: A list of paths, with each item the corresponding input path
+        adjusted to account for existing elements that match case
+        insensitively.
+        """
+        return list(self._yield_canonical_inventory_paths(paths))
+
+    def get_canonical_inventory_path(self, path):
+        """A convenience version of get_canonical_inventory_path which
+        takes a single path.
+
+        If you need to resolve many names from the same tree, you should
+        use get_canonical_inventory_paths() to avoid O(N) behaviour.
+        """
+        return self._yield_canonical_inventory_paths([path]).next()
+
+    def _yield_canonical_inventory_paths(self, paths):
+        for path in paths:
+            # First, if the path as specified exists exactly, just use it.
+            if self.path2id(path) is not None:
+                yield path
+                continue
+            # go walkin...
+            cur_id = self.get_root_id()
+            cur_path = ''
+            bit_iter = iter(path.split("/"))
+            for elt in bit_iter:
+                lelt = elt.lower()
+                for child in self.iter_children(cur_id):
+                    try:
+                        child_base = os.path.basename(self.id2path(child))
+                        if child_base.lower() == lelt:
+                            cur_id = child
+                            cur_path = osutils.pathjoin(cur_path, child_base)
+                            break
+                    except NoSuchId:
+                        # before a change is committed we can see this error...
+                        continue
+                else:
+                    # got to the end of this directory and no entries matched.
+                    # Return what matched so far, plus the rest as specified.
+                    cur_path = osutils.pathjoin(cur_path, elt, *list(bit_iter))
+                    break
+            yield cur_path
+        # all done.
+
     def get_root_id(self):
         """Return the file_id for the root of this tree."""
         raise NotImplementedError(self.get_root_id)

=== added file 'doc/developers/case-insensitive-file-systems.txt'
--- doc/developers/case-insensitive-file-systems.txt	1970-01-01 00:00:00 +0000
+++ doc/developers/case-insensitive-file-systems.txt	2008-12-21 07:33:23 +0000
@@ -0,0 +1,93 @@
+Case Insensitive File Systems
+=============================
+
+Bazaar must be portable across operating-systems and file-systems.  While the
+primary file-system for an operating-system might have some particular
+characteristics, it's not necessary that *all* file-systems for that
+operating-system will have the same characteristics.
+
+For example, the FAT32 file-system is most commonly found on Windows operating
+systems, and has the characteristics usually associated with a Windows
+file-system.  However, USB devices means FAT32 file-systems are often used
+with Linux, so the current operating system doesn't necessarily reflect the
+capabilities of the file-system.
+
+Bazaar supports 3 kinds of file-systems, each to different degrees.
+
+* Case-sensitive file-systems: This is the file-system generally used on
+  Linux - 2 files can differ only by case, and the exact case must be used
+  when opening a file.
+
+* Case-insensitive, case-preserving (cicp) file-systems: This is the
+  file-system generally used on Windows; FAT32 is an example of such a
+  file-system.  Although existing files can be opened using any case, the
+  exact case used to create the file is preserved and available for programs
+  to query.  Two files that differ only by case is not allowed.
+
+* Case-insensitive: This is the file-system used by very old Windows versions
+  and is rarely encountered "in the wild".  Two files that differ only by
+  case is not allowed and the case used to create a file is not preserved.
+  
+As can be implied by the above descriptions, only the first two are considered
+relevant to a modern Bazaar.
+
+For more details, including use cases, please see
+http://bazaar-vcs.org/CasePreservingWorkingTreeUseCases
+
+Handling these file-systems
+---------------------------
+
+The fundamental problem handling these file-systems is that the user may
+specify a file name or inventory item with an "incorrect" case - where
+"incorrect" simply means different than what is stored - from the user's
+point-of-view, the filename is still correct, as it can be used to open, edit
+delete etc the item.
+
+The approach Bazaar takes is to "fixup" each of the command-line arguments
+which refer to a filename or an inventory item - where "fixup" means to
+adjust the case specified by the user so it exactly matches an existing item.
+
+There are two places this match can be performed against - the file-system
+and the Bazaar inventory.  When looking at the file-system, it is generally
+impossible to have 2 names that differ only by case, so there is no ambiguity.
+The inventory doesn't have the same rules, but it is expected that projects
+which with to work with Windows would, by convention, avoid filenames that
+differ only by case.
+
+The rules for such fixups turn out to be quite simple:
+
+* If an argument refers to an existing inventory item, we fixup the argument
+  using the inventory.  This is, basically, all commands that take a filename
+  or directory argument *other* than 'add'
+
+* If an argument refers to an existing filename for the creation of an
+  inventory item (eg, add), then the case of the existing file on the disk
+  will be used.  However, Bazaar must still check the inventory to prevent
+  accidentally creating 2 inventory items that differ only by case.
+
+* If an argument results in the creation of a *new* filename (eg, a move
+  destination), the argument will be used as specified.  Bzr will create
+  a file and inventory item that exactly matches the case specified.
+
+Implementation of support for these file-systems
+------------------------------------------------
+
+From the description above, it can be seen the implementation is fairly
+simple and need not intrude on the internals of Bazaar too much; most of
+the time it is simply converting a string specified by the user to the
+"canonical" form as stored in either the inventory or filesystem.  These
+boil down to the following new API functions:
+
+* osutils.canonical_relpath() - like osutils.relpath() but adjust the case
+  of the result to match any existing items.
+
+* Tree.get_canonical_inventory_path - somewhat like Tree.get_symlink_target(),
+  Tree.get_file_by_path() etc; returns a name with the case adjusted to match
+  existing inventory items.
+
+NOTE: It is likely these names will change.
+
+The only complication is the requirement that Bazaar not allow the creation
+of items that differ only by case on such file-systems.  For this requirement,
+case-insensitive and cicp file-systems can be treated the same.  The
+'case_sensitive' attribute on a MutableTree is used to control this behaviour.

# Begin bundle
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
