Rev 5753: Merge the 2.3 branch changes up to 2.4. The final changes are in http://bazaar.launchpad.net/~jameinel/bzr/2.4-transform-cache-sha-740932

John Arbash Meinel john at arbash-meinel.com
Mon Apr 4 13:37:47 UTC 2011


At http://bazaar.launchpad.net/~jameinel/bzr/2.4-transform-cache-sha-740932

------------------------------------------------------------
revno: 5753 [merge]
revision-id: john at arbash-meinel.com-20110404133725-5poi07a3wk5re7hm
parent: pqm at pqm.ubuntu.com-20110402231100-l6p99hxfe6722vir
parent: john at arbash-meinel.com-20110404123413-vmd0xo9yrx6f3uza
committer: John Arbash Meinel <john at arbash-meinel.com>
branch nick: 2.4-transform-cache-sha-740932
timestamp: Mon 2011-04-04 15:37:25 +0200
message:
  Merge the 2.3 branch changes up to 2.4. The final changes are
  too invasive to do in a stable series (IMO).
modified:
  bzrlib/_walkdirs_win32.pyx     _walkdirs_win32.pyx-20080716220454-kweh3tgxez5dvw2l-2
  bzrlib/config.py               config.py-20051011043216-070c74f4e9e338e8
  bzrlib/crash.py                crash.py-20090812083334-d6volool4lktdjcx-1
  bzrlib/dirstate.py             dirstate.py-20060728012006-d6mvoihjb3je9peu-1
  bzrlib/lockdir.py              lockdir.py-20060220222025-98258adf27fbdda3
  bzrlib/osutils.py              osutils.py-20050309040759-eeaff12fbf77ac86
  bzrlib/plugin.py               plugin.py-20050622060424-829b654519533d69
  bzrlib/tests/__init__.py       selftest.py-20050531073622-8d0e3c8845c97a64
  bzrlib/tests/test_config.py    testconfig.py-20051011041908-742d0c15d8d8c8eb
  bzrlib/tests/test_crash.py     test_crash.py-20090820042958-jglgza3wrn03ha9e-2
  bzrlib/tests/test_transform.py test_transaction.py-20060105172520-b3ffb3946550e6c4
  bzrlib/transform.py            transform.py-20060105172343-dd99e54394d91687
  bzrlib/workingtree.py          workingtree.py-20050511021032-29b6ec0a681e02e3
  bzrlib/workingtree_4.py        workingtree_4.py-20070208044105-5fgpc5j3ljlh5q6c-1
  doc/developers/testing.txt     testing.txt-20080812140359-i70zzh6v2z7grqex-1
  doc/en/release-notes/bzr-2.3.txt NEWS-20050323055033-4e00b5db738777ff
-------------- next part --------------
=== modified file 'bzrlib/_walkdirs_win32.pyx'
--- a/bzrlib/_walkdirs_win32.pyx	2010-02-17 17:11:16 +0000
+++ b/bzrlib/_walkdirs_win32.pyx	2011-04-04 11:24:50 +0000
@@ -1,4 +1,4 @@
-# Copyright (C) 2008, 2009, 2010 Canonical Ltd
+# Copyright (C) 2008-2011 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
@@ -69,9 +69,13 @@
 
 
 import operator
+import os
 import stat
 
-from bzrlib import osutils, _readdir_py
+from bzrlib import _readdir_py
+
+cdef object osutils
+osutils = None
 
 
 cdef class _Win32Stat:
@@ -170,6 +174,8 @@
 
     def top_prefix_to_starting_dir(self, top, prefix=""):
         """See DirReader.top_prefix_to_starting_dir."""
+        if osutils is None:
+            from bzrlib import osutils
         return (osutils.safe_utf8(prefix), None, None, None,
                 osutils.safe_unicode(top))
 
@@ -250,3 +256,37 @@
                 #       earlier Exception, so for now, I'm ignoring this
         dirblock.sort(key=operator.itemgetter(1))
         return dirblock
+
+
+def lstat(path):
+    """Equivalent to os.lstat, except match Win32ReadDir._get_stat_value.
+    """
+    return wrap_stat(os.lstat(path))
+
+
+def fstat(fd):
+    """Like os.fstat, except match Win32ReadDir._get_stat_value
+
+    :seealso: wrap_stat
+    """
+    return wrap_stat(os.fstat(fd))
+
+
+def wrap_stat(st):
+    """Return a _Win32Stat object, based on the given stat result.
+
+    On Windows, os.fstat(open(fname).fileno()) != os.lstat(fname). This is
+    generally because os.lstat and os.fstat differ in what they put into st_ino
+    and st_dev. What gets set where seems to also be dependent on the python
+    version. So we always set it to 0 to avoid worrying about it.
+    """
+    cdef _Win32Stat statvalue
+    statvalue = _Win32Stat()
+    statvalue.st_mode = st.st_mode
+    statvalue.st_ctime = st.st_ctime
+    statvalue.st_mtime = st.st_mtime
+    statvalue.st_atime = st.st_atime
+    statvalue._st_size = st.st_size
+    statvalue.st_ino = 0
+    statvalue.st_dev = 0
+    return statvalue

=== modified file 'bzrlib/config.py'
--- a/bzrlib/config.py	2011-03-31 13:35:54 +0000
+++ b/bzrlib/config.py	2011-04-04 13:37:25 +0000
@@ -63,6 +63,7 @@
 """
 
 import os
+import string
 import sys
 
 from bzrlib import commands
@@ -441,21 +442,21 @@
         the concrete policy type is checked, and finally
         $EMAIL is examined.
         If no username can be found, errors.NoWhoami exception is raised.
-
-        TODO: Check it's reasonably well-formed.
         """
         v = os.environ.get('BZR_EMAIL')
         if v:
             return v.decode(osutils.get_user_encoding())
-
         v = self._get_user_id()
         if v:
             return v
-
         v = os.environ.get('EMAIL')
         if v:
             return v.decode(osutils.get_user_encoding())
-
+        name, email = _auto_user_id()
+        if name and email:
+            return '%s <%s>' % (name, email)
+        elif email:
+            return email
         raise errors.NoWhoami()
 
     def ensure_username(self):
@@ -1407,6 +1408,86 @@
         return os.path.expanduser('~/.cache')
 
 
+def _get_default_mail_domain():
+    """If possible, return the assumed default email domain.
+
+    :returns: string mail domain, or None.
+    """
+    if sys.platform == 'win32':
+        # No implementation yet; patches welcome
+        return None
+    try:
+        f = open('/etc/mailname')
+    except (IOError, OSError), e:
+        return None
+    try:
+        domain = f.read().strip()
+        return domain
+    finally:
+        f.close()
+
+
+def _auto_user_id():
+    """Calculate automatic user identification.
+
+    :returns: (realname, email), either of which may be None if they can't be
+    determined.
+
+    Only used when none is set in the environment or the id file.
+
+    This only returns an email address if we can be fairly sure the 
+    address is reasonable, ie if /etc/mailname is set on unix.
+
+    This doesn't use the FQDN as the default domain because that may be 
+    slow, and it doesn't use the hostname alone because that's not normally 
+    a reasonable address.
+    """
+    if sys.platform == 'win32':
+        # No implementation to reliably determine Windows default mail
+        # address; please add one.
+        return None, None
+
+    default_mail_domain = _get_default_mail_domain()
+    if not default_mail_domain:
+        return None, None
+
+    import pwd
+    uid = os.getuid()
+    try:
+        w = pwd.getpwuid(uid)
+    except KeyError:
+        mutter('no passwd entry for uid %d?' % uid)
+        return None, None
+
+    # we try utf-8 first, because on many variants (like Linux),
+    # /etc/passwd "should" be in utf-8, and because it's unlikely to give
+    # false positives.  (many users will have their user encoding set to
+    # latin-1, which cannot raise UnicodeError.)
+    try:
+        gecos = w.pw_gecos.decode('utf-8')
+        encoding = 'utf-8'
+    except UnicodeError:
+        try:
+            encoding = osutils.get_user_encoding()
+            gecos = w.pw_gecos.decode(encoding)
+        except UnicodeError, e:
+            mutter("cannot decode passwd entry %s" % w)
+            return None, None
+    try:
+        username = w.pw_name.decode(encoding)
+    except UnicodeError, e:
+        mutter("cannot decode passwd entry %s" % w)
+        return None, None
+
+    comma = gecos.find(',')
+    if comma == -1:
+        realname = gecos
+    else:
+        realname = gecos[:comma]
+
+    return realname, (username + '@' + default_mail_domain)
+
+
 def parse_username(username):
     """Parse e-mail username and return a (name, address) tuple."""
     match = re.match(r'(.*?)\s*<?([\w+.-]+@[\w+.-]+)>?', username)

=== modified file 'bzrlib/crash.py'
--- a/bzrlib/crash.py	2011-01-20 23:07:25 +0000
+++ b/bzrlib/crash.py	2011-04-04 13:37:25 +0000
@@ -84,19 +84,27 @@
     """Report a bug by just printing a message to the user."""
     trace.print_exception(exc_info, err_file)
     err_file.write('\n')
-    err_file.write('bzr %s on python %s (%s)\n' % \
-                       (bzrlib.__version__,
-                        bzrlib._format_version_tuple(sys.version_info),
-                        platform.platform(aliased=1)))
-    err_file.write('arguments: %r\n' % sys.argv)
-    err_file.write(
+    import textwrap
+    def print_wrapped(l):
+        err_file.write(textwrap.fill(l,
+            width=78, subsequent_indent='    ') + '\n')
+    print_wrapped('bzr %s on python %s (%s)\n' % \
+        (bzrlib.__version__,
+        bzrlib._format_version_tuple(sys.version_info),
+        platform.platform(aliased=1)))
+    print_wrapped('arguments: %r\n' % sys.argv)
+    print_wrapped(textwrap.fill(
+        'plugins: ' + plugin.format_concise_plugin_list(),
+        width=78,
+        subsequent_indent='    ',
+        ) + '\n')
+    print_wrapped(
         'encoding: %r, fsenc: %r, lang: %r\n' % (
             osutils.get_user_encoding(), sys.getfilesystemencoding(),
             os.environ.get('LANG')))
-    err_file.write("plugins:\n")
-    err_file.write(_format_plugin_list())
+    # We used to show all the plugins here, but it's too verbose.
     err_file.write(
-        "\n\n"
+        "\n"
         "*** Bazaar has encountered an internal error.  This probably indicates a\n"
         "    bug in Bazaar.  You can help us fix it by filing a bug report at\n"
         "        https://bugs.launchpad.net/bzr/+filebug\n"

=== modified file 'bzrlib/dirstate.py'
--- a/bzrlib/dirstate.py	2011-01-25 22:54:08 +0000
+++ b/bzrlib/dirstate.py	2011-04-04 13:37:25 +0000
@@ -265,6 +265,17 @@
         # return '%X.%X' % (int(st.st_mtime), st.st_mode)
 
 
+def _unpack_stat(packed_stat):
+    """Turn a packed_stat back into the stat fields.
+
+    This is meant as a debugging tool, should not be used in real code.
+    """
+    (st_size, st_mtime, st_ctime, st_dev, st_ino,
+     st_mode) = struct.unpack('>LLLLLL', binascii.a2b_base64(packed_stat))
+    return dict(st_size=st_size, st_mtime=st_mtime, st_ctime=st_ctime,
+                st_dev=st_dev, st_ino=st_ino, st_mode=st_mode)
+
+
 class SHA1Provider(object):
     """An interface for getting sha1s of a file."""
 
@@ -1734,8 +1745,8 @@
                 self._sha_cutoff_time()
             if (stat_value.st_mtime < self._cutoff_time
                 and stat_value.st_ctime < self._cutoff_time):
-                entry[1][0] = ('f', sha1, entry[1][0][2], entry[1][0][3],
-                    packed_stat)
+                entry[1][0] = ('f', sha1, stat_value.st_size, entry[1][0][3],
+                               packed_stat)
                 self._dirblock_state = DirState.IN_MEMORY_MODIFIED
 
     def _sha_cutoff_time(self):

=== modified file 'bzrlib/lockdir.py'
--- a/bzrlib/lockdir.py	2011-01-12 20:31:15 +0000
+++ b/bzrlib/lockdir.py	2011-03-31 09:01:27 +0000
@@ -537,6 +537,17 @@
             hook(hook_result)
         return result
 
+    def lock_url_for_display(self):
+        """Give a nicely-printable representation of the URL of this lock."""
+        # As local lock urls are correct we display them.
+        # We avoid displaying remote lock urls.
+        lock_url = self.transport.abspath(self.path)
+        if lock_url.startswith('file://'):
+            lock_url = lock_url.split('.bzr/')[0]
+        else:
+            lock_url = ''
+        return lock_url
+
     def wait_lock(self, timeout=None, poll=None, max_attempts=None):
         """Wait a certain period for a lock.
 
@@ -566,6 +577,7 @@
         deadline_str = None
         last_info = None
         attempt_count = 0
+        lock_url = self.lock_url_for_display()
         while True:
             attempt_count += 1
             try:
@@ -590,13 +602,6 @@
                 if deadline_str is None:
                     deadline_str = time.strftime('%H:%M:%S',
                                                  time.localtime(deadline))
-                # As local lock urls are correct we display them.
-                # We avoid displaying remote lock urls.
-                lock_url = self.transport.abspath(self.path)
-                if lock_url.startswith('file://'):
-                    lock_url = lock_url.split('.bzr/')[0]
-                else:
-                    lock_url = ''
                 user, hostname, pid, time_ago = formatted_info
                 msg = ('%s lock %s '        # lock_url
                     'held by '              # start

=== modified file 'bzrlib/osutils.py'
--- a/bzrlib/osutils.py	2011-02-24 16:13:39 +0000
+++ b/bzrlib/osutils.py	2011-04-04 13:37:25 +0000
@@ -392,6 +392,12 @@
 # These were already lazily imported into local scope
 # mkdtemp = tempfile.mkdtemp
 # rmtree = shutil.rmtree
+lstat = os.lstat
+fstat = os.fstat
+
+def wrap_stat(st):
+    return st
+
 
 MIN_ABS_PATHLENGTH = 1
 
@@ -407,6 +413,14 @@
     getcwd = _win32_getcwd
     mkdtemp = _win32_mkdtemp
     rename = _win32_rename
+    try:
+        from bzrlib import _walkdirs_win32
+    except ImportError:
+        pass
+    else:
+        lstat = _walkdirs_win32.lstat
+        fstat = _walkdirs_win32.fstat
+        wrap_stat = _walkdirs_win32.wrap_stat
 
     MIN_ABS_PATHLENGTH = 3
 

=== modified file 'bzrlib/plugin.py'
--- a/bzrlib/plugin.py	2011-01-20 01:02:34 +0000
+++ b/bzrlib/plugin.py	2011-04-04 13:37:25 +0000
@@ -451,6 +451,17 @@
     return result
 
 
+def format_concise_plugin_list():
+    """Return a string holding a concise list of plugins and their version.
+    """
+    items = []
+    for name, a_plugin in sorted(plugins().items()):
+        items.append("%s[%s]" %
+            (name, a_plugin.__version__))
+    return ', '.join(items)
+
+
+
 class PluginsHelpIndex(object):
     """A help index that returns help topics for plugins."""
 

=== modified file 'bzrlib/tests/__init__.py'
--- a/bzrlib/tests/__init__.py	2011-03-30 11:34:31 +0000
+++ b/bzrlib/tests/__init__.py	2011-04-04 13:37:25 +0000
@@ -1267,11 +1267,15 @@
                          'st_mtime did not match')
         self.assertEqual(expected.st_ctime, actual.st_ctime,
                          'st_ctime did not match')
-        if sys.platform != 'win32':
+        if sys.platform == 'win32':
             # On Win32 both 'dev' and 'ino' cannot be trusted. In python2.4 it
             # is 'dev' that varies, in python 2.5 (6?) it is st_ino that is
-            # odd. Regardless we shouldn't actually try to assert anything
-            # about their values
+            # odd. We just force it to always be 0 to avoid any problems.
+            self.assertEqual(0, expected.st_dev)
+            self.assertEqual(0, actual.st_dev)
+            self.assertEqual(0, expected.st_ino)
+            self.assertEqual(0, actual.st_ino)
+        else:
             self.assertEqual(expected.st_dev, actual.st_dev,
                              'st_dev did not match')
             self.assertEqual(expected.st_ino, actual.st_ino,

=== modified file 'bzrlib/tests/test_config.py'
--- a/bzrlib/tests/test_config.py	2011-02-25 12:12:39 +0000
+++ b/bzrlib/tests/test_config.py	2011-04-04 13:37:25 +0000
@@ -42,6 +42,7 @@
     )
 from bzrlib.tests import (
     features,
+    TestSkipped,
     scenarios,
     )
 from bzrlib.util.configobj import configobj
@@ -2469,3 +2470,25 @@
 # test_user_prompted ?
 class TestAuthenticationRing(tests.TestCaseWithTransport):
     pass
+
+
+class TestAutoUserId(tests.TestCase):
+    """Test inferring an automatic user name."""
+
+    def test_auto_user_id(self):
+        """Automatic inference of user name.
+        
+        This is a bit hard to test in an isolated way, because it depends on
+        system functions that go direct to /etc or perhaps somewhere else.
+        But it's reasonable to say that on Unix, with an /etc/mailname, we ought
+        to be able to choose a user name with no configuration.
+        """
+        if sys.platform == 'win32':
+            raise TestSkipped("User name inference not implemented on win32")
+        realname, address = config._auto_user_id()
+        if os.path.exists('/etc/mailname'):
+            self.assertTrue(realname)
+            self.assertTrue(address)
+        else:
+            self.assertEquals((None, None), (realname, address))
+

=== modified file 'bzrlib/tests/test_crash.py'
--- a/bzrlib/tests/test_crash.py	2011-01-18 00:41:29 +0000
+++ b/bzrlib/tests/test_crash.py	2011-04-04 13:37:25 +0000
@@ -15,12 +15,12 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
 
+import doctest
+import os
 from StringIO import StringIO
 import sys
 
-
-import os
-
+from testtools.matchers import DocTestMatches
 
 from bzrlib import (
     config,
@@ -80,3 +80,45 @@
         self.assertContainsRe(
             report,
             'Failed to load plugin foo')
+
+
+class TestNonApportReporting(tests.TestCase):
+    """Reporting of crash-type bugs without apport.
+    
+    This should work in all environments.
+    """
+
+    def setup_fake_plugins(self):
+        def fake_plugins():
+            fake = plugin.PlugIn('fake_plugin', plugin)
+            fake.version_info = lambda: (1, 2, 3)
+            return {"fake_plugin": fake}
+        self.overrideAttr(plugin, 'plugins', fake_plugins)
+
+    def test_report_bug_legacy(self):
+        self.setup_fake_plugins()
+        err_file = StringIO()
+        try:
+            raise AssertionError("my error")
+        except AssertionError, e:
+            pass
+        crash.report_bug_legacy(sys.exc_info(), err_file)
+        self.assertThat(
+            err_file.getvalue(),
+            DocTestMatches("""\
+bzr: ERROR: exceptions.AssertionError: my error
+
+Traceback (most recent call last):
+  ...
+AssertionError: my error
+
+bzr ... on python ...
+arguments: ...
+plugins: fake_plugin[1.2.3]
+encoding: ...
+
+*** Bazaar has encountered an internal error.  This probably indicates a
+    bug in Bazaar.  You can help us fix it by filing a bug report at
+        https://bugs.launchpad.net/bzr/+filebug
+    including this traceback and a description of the problem.
+""", flags=doctest.ELLIPSIS|doctest.REPORT_UDIFF))

=== modified file 'bzrlib/tests/test_transform.py'
--- a/bzrlib/tests/test_transform.py	2011-01-13 01:29:41 +0000
+++ b/bzrlib/tests/test_transform.py	2011-04-04 12:34:13 +0000
@@ -2062,6 +2062,42 @@
         self.assertEqual('file.moved', target.id2path('lower-id'))
         self.assertEqual('FILE', target.id2path('upper-id'))
 
+    def test_build_tree_observes_sha(self):
+        source = self.make_branch_and_tree('source')
+        self.build_tree(['source/file1', 'source/dir/', 'source/dir/file2'])
+        source.add(['file1', 'dir', 'dir/file2'],
+                   ['file1-id', 'dir-id', 'file2-id'])
+        source.commit('new files')
+        target = self.make_branch_and_tree('target')
+        target.lock_write()
+        self.addCleanup(target.unlock)
+        # We make use of the fact that DirState caches its cutoff time. So we
+        # set the 'safe' time to one minute in the future.
+        state = target.current_dirstate()
+        state._cutoff_time = time.time() + 60
+        build_tree(source.basis_tree(), target)
+        entry1_sha = osutils.sha_file_by_name('source/file1')
+        entry2_sha = osutils.sha_file_by_name('source/dir/file2')
+        # entry[1] is the state information, entry[1][0] is the state of the
+        # working tree, entry[1][0][1] is the sha value for the current working
+        # tree
+        entry1 = state._get_entry(0, path_utf8='file1')
+        self.assertEqual(entry1_sha, entry1[1][0][1])
+        # The 'size' field must also be set.
+        self.assertEqual(25, entry1[1][0][2])
+        entry1_state = entry1[1][0]
+        entry2 = state._get_entry(0, path_utf8='dir/file2')
+        self.assertEqual(entry2_sha, entry2[1][0][1])
+        self.assertEqual(29, entry2[1][0][2])
+        entry2_state = entry2[1][0]
+        # Now, make sure that we don't have to re-read the content. The
+        # packed_stat should match exactly.
+        self.assertEqual(entry1_sha, target.get_file_sha1('file1-id', 'file1'))
+        self.assertEqual(entry2_sha,
+                         target.get_file_sha1('file2-id', 'dir/file2'))
+        self.assertEqual(entry1_state, entry1[1][0])
+        self.assertEqual(entry2_state, entry2[1][0])
+
 
 class TestCommitTransform(tests.TestCaseWithTransport):
 

=== modified file 'bzrlib/transform.py'
--- a/bzrlib/transform.py	2011-01-13 05:01:38 +0000
+++ b/bzrlib/transform.py	2011-04-04 13:37:25 +0000
@@ -105,6 +105,8 @@
         self._new_parent = {}
         # mapping of trans_id with new contents -> new file_kind
         self._new_contents = {}
+        # mapping of trans_id => (sha1 of content, stat_value)
+        self._observed_sha1s = {}
         # Set of trans_ids whose contents will be removed
         self._removed_contents = set()
         # Mapping of trans_id -> new execute-bit value
@@ -1271,12 +1273,26 @@
                 f.close()
                 os.unlink(name)
                 raise
-
-            f.writelines(contents)
+            if contents.__class__ is list:
+                sha_digest = osutils.sha_strings(contents)
+                f.writelines(contents)
+            else:
+                sha_value = osutils.sha()
+                def observe_sha1(contents):
+                    sha_value_update = sha_value.update
+                    for content in contents:
+                        sha_value_update(content)
+                        yield content
+                f.writelines(observe_sha1(contents))
+                sha_digest = sha_value.hexdigest()
         finally:
             f.close()
         self._set_mtime(name)
         self._set_mode(trans_id, mode_id, S_ISREG)
+        # It is unfortunate we have to use lstat instead of fstat, but we just
+        # used utime and chmod on the file, so we need the accurate final
+        # details.
+        self._observed_sha1s[trans_id] = (sha_digest, osutils.lstat(name))
 
     def _read_file_chunks(self, trans_id):
         cur_file = open(self._limbo_name(trans_id), 'rb')
@@ -1341,6 +1357,8 @@
     def cancel_creation(self, trans_id):
         """Cancel the creation of new file contents."""
         del self._new_contents[trans_id]
+        if trans_id in self._observed_sha1s:
+            del self._observed_sha1s[trans_id]
         children = self._limbo_children.get(trans_id)
         # if this is a limbo directory with children, move them before removing
         # the directory
@@ -1702,6 +1720,7 @@
         finally:
             child_pb.finished()
         self._tree.apply_inventory_delta(inventory_delta)
+        self._apply_observed_sha1s()
         self._done = True
         self.finalize()
         return _TransformResults(modified_paths, self.rename_count)
@@ -1827,6 +1846,9 @@
                             raise
                     else:
                         self.rename_count += 1
+                    # TODO: if trans_id in self._observed_sha1s, we should
+                    #       re-stat the final target, since ctime will be
+                    #       updated by the change.
                 if (trans_id in self._new_contents or
                     self.path_changed(trans_id)):
                     if trans_id in self._new_contents:
@@ -1838,6 +1860,29 @@
         self._new_contents.clear()
         return modified_paths
 
+    def _apply_observed_sha1s(self):
+        """After we have finished renaming everything, update observed sha1s
+
+        This has to be done after self._tree.apply_inventory_delta, otherwise
+        it doesn't know anything about the files we are updating. Also, we want
+        to do this as late as possible, so that most entries end up cached.
+        """
+        # TODO: this doesn't update the stat information for directories. So
+        #       the first 'bzr status' will still need to rewrite
+        #       .bzr/checkout/dirstate. However, we at least don't need to
+        #       re-read all of the files.
+        # TODO: If the operation took a while, we could do a time.sleep(3) here
+        #       to allow the clock to tick over and ensure we won't have any
+        #       problems. (we could observe start time, and finish time, and if
+        #       it is less than eg 10% overhead, add a sleep call.)
+        paths = FinalPaths(self)
+        for trans_id, observed in self._observed_sha1s.iteritems():
+            path = paths.get_path(trans_id)
+            # We could get the file_id, but dirstate prefers to use the path
+            # anyway, and it is 'cheaper' to determine.
+            # file_id = self._new_id[trans_id]
+            self._tree._observed_sha1(None, path, observed)
+
 
 class TransformPreview(DiskTreeTransform):
     """A TreeTransform for generating preview trees.

=== modified file 'bzrlib/workingtree.py'
--- a/bzrlib/workingtree.py	2011-03-07 15:33:31 +0000
+++ b/bzrlib/workingtree.py	2011-04-04 13:37:25 +0000
@@ -526,7 +526,7 @@
         return self.get_file_with_stat(file_id, path, filtered=filtered)[0]
 
     def get_file_with_stat(self, file_id, path=None, filtered=True,
-        _fstat=os.fstat):
+                           _fstat=osutils.fstat):
         """See Tree.get_file_with_stat."""
         if path is None:
             path = self.id2path(file_id)

=== modified file 'bzrlib/workingtree_4.py'
--- a/bzrlib/workingtree_4.py	2011-02-11 17:58:56 +0000
+++ b/bzrlib/workingtree_4.py	2011-04-04 13:37:25 +0000
@@ -369,7 +369,7 @@
         state = self.current_dirstate()
         if stat_value is None:
             try:
-                stat_value = os.lstat(file_abspath)
+                stat_value = osutils.lstat(file_abspath)
             except OSError, e:
                 if e.errno == errno.ENOENT:
                     return None
@@ -478,7 +478,7 @@
             self._must_be_locked()
             if not path:
                 path = self.id2path(file_id)
-            mode = os.lstat(self.abspath(path)).st_mode
+            mode = osutils.lstat(self.abspath(path)).st_mode
             return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
 
     def all_file_ids(self):

=== modified file 'doc/developers/testing.txt'
--- a/doc/developers/testing.txt	2011-02-03 00:39:52 +0000
+++ b/doc/developers/testing.txt	2011-04-04 13:37:25 +0000
@@ -339,6 +339,11 @@
 
   __ http://docs.python.org/lib/module-doctest.html
 
+There is an `assertDoctestExampleMatches` method in
+`bzrlib.tests.TestCase` that allows you to match against doctest-style
+string templates (including ``...`` to skip sections) from regular Python
+tests.
+
 
 Shell-like tests
 ----------------

=== modified file 'doc/en/release-notes/bzr-2.3.txt'
--- a/doc/en/release-notes/bzr-2.3.txt	2011-03-24 11:41:42 +0000
+++ b/doc/en/release-notes/bzr-2.3.txt	2011-04-04 13:37:25 +0000
@@ -39,15 +39,37 @@
 .. Fixes for situations where bzr would previously crash or give incorrect
    or undesirable results.
 
+* Bazaar now infers the default user email address on Unix from the local
+  account name plus the contents of ``/etc/mailname`` if that file exists.
+  In particular, this means that committing as root through etckeeper will
+  normally not require running ``bzr whoami`` first.
+  (Martin Pool, #616878)
+
 * ``bzr push`` into a repository (that doesn't have a branch), will no
   longer copy all revisions in the repository. Only the ones in the
   ancestry of the source branch, like it does in all other cases.
   (John Arbash Meinel, #465517)
 
+* Fix ``UnboundLocalError: local variable 'lock_url' in wait_lock`` error,
+  especially while trying to save configuration from QBzr.
+  (Martin Pool, #733136)
+
 * Fix "Unable to obtain lock" error when pushing to a bound branch if tags
   had changed.  Bazaar was attempting to open and lock the master branch
   twice in this case.  (Andrew Bennetts, #733350)
 
+* ``TreeTransform`` now calls ``_observed_sha1`` for content that it
+  creates. This means that ``merge`` and ``checkout`` should be able to
+  cache the sha values, and avoid re-reading the files on the next ``bzr
+  status`` that gets run. Further, on Windows we now properly suppress
+  ``st_dev`` and ``st_ino`` so that commit also avoids re-doing the work.
+  (John Arbash Meinel, #740932)
+
+* When reporting a crash without apport, don't print the full list of
+  plugins because it's often too long.
+  (Martin Pool, #716389)
+
+
 Documentation
 *************
 
@@ -79,7 +101,6 @@
   (<http://psf.upfronthosting.co.za/roundup/tracker/issue8194> should be fixed
   in python > 2.7.1).  (Vincent Ladeuil, #654733)
 
-
 bzr 2.3.1
 #########
 



More information about the bazaar-commits mailing list