Rev 4576: Merge bzr.dev. in http://people.canonical.com/~robertc/baz2.0/integration

Robert Collins robertc at robertcollins.net
Mon Aug 3 05:37:47 BST 2009


At http://people.canonical.com/~robertc/baz2.0/integration

------------------------------------------------------------
revno: 4576 [merge]
revision-id: robertc at robertcollins.net-20090803043737-h02sf93wnnlff5tb
parent: robertc at robertcollins.net-20090803033647-vr5uljwpi2f6rdya
parent: pqm at pqm.ubuntu.com-20090731162211-zvddnooijve9nbmu
committer: Robert Collins <robertc at robertcollins.net>
branch nick: integration
timestamp: Mon 2009-08-03 14:37:37 +1000
message:
  Merge bzr.dev.
modified:
  NEWS                           NEWS-20050323055033-4e00b5db738777ff
  bzr                            bzr.py-20050313053754-5485f144c7006fa6
  bzrlib/benchmarks/bench_knit.py bench_knit.py-20070509145850-pan5jnd3hl7mfdya-1
  bzrlib/breakin.py              breakin.py-20070417043829-so46nevf978u713k-1
  bzrlib/diff.py                 diff.py-20050309040759-26944fbbf2ebbf36
  bzrlib/export/dir_exporter.py  dir_exporter.py-20051114235828-b51397f56bc7b117
  bzrlib/knit.py                 knit.py-20051212171256-f056ac8f0fbe1bd9
  bzrlib/remote.py               remote.py-20060720103555-yeeg2x51vn0rbtdp-1
  bzrlib/smart/message.py        message.py-20080222013625-ncqmh3nrxjkxab87-1
  bzrlib/tests/blackbox/test_breakin.py test_breakin.py-20070424043903-qyy6zm4pj3h4sbp3-1
  bzrlib/tests/test_export.py    test_export.py-20090220201010-tpbxssdnezsvu9pk-1
  bzrlib/tests/test_knit.py      test_knit.py-20051212171302-95d4c00dd5f11f2b
  bzrlib/tests/test_remote.py    test_remote.py-20060720103555-yeeg2x51vn0rbtdp-2
  doc/en/developer-guide/HACKING.txt HACKING-20050805200004-2a5dc975d870f78c
=== modified file 'NEWS'
--- a/NEWS	2009-07-29 04:57:50 +0000
+++ b/NEWS	2009-08-03 04:37:37 +0000
@@ -60,10 +60,20 @@
   to reading and then rewriting the whole file, such as TahoeLAFS.  (This
   fallback may be slow for some access patterns.)  (Nils Durner, #294709)
 
+* Encode the paths in ``mbcs`` encoding on Windows when spawning an
+  external diff client. This at least allows supporting filenames that are
+  not ascii, but are present in the current locale. Ideally we would be
+  able to pass the Unicode path, but that would be client dependent.
+  (John Arbash Meinel, #382709)
+
 * Fixed a NameError that occurs when merging or pulling from a URL that
   causes a redirection loop when bzr tries to read a URL as a bundle.
   (Andrew Bennetts, #400847)
   
+* Fixed export to existing directory: if directory is empty then export 
+  will succeed, otherwise it fails with error.
+  (Alexander Belchenko, #406174)
+
 * Fixed spurious "Source branch does not support stacking" warning when
   pushing. (Andrew Bennetts, #388908)
 
@@ -78,6 +88,13 @@
   lots of backtraces about ``UnknownSmartMethod``, ``do_chunk`` or
   ``do_end``.  (Andrew Bennetts, #338561)
   
+* Streaming from bzr servers where there is a chain of stacked branches
+  (A stacked on B stacked on C) will now work. (Robert Collins, #406597)
+
+* The optional ``_knit_load_data_pyx`` C extension was never being
+  imported.  This caused significant slowdowns when reading data from
+  repositories.  (Andrew Bennetts, #405653)
+  
 * There was a bug in ``osutils.relpath`` that was only triggered on
   Windows. Essentially if you were at the root of a drive, and did
   something to a branch/repo on another drive, we would go into an
@@ -94,6 +111,15 @@
 * Can now rename/move files even if they have been removed from the inventory.
   (Marius Kruger)
 
+* Pushing branches with tags via ``bzr://`` and ``bzr+ssh://`` is much
+  faster, using a new ``Branch.set_tags_bytes`` smart server verb rather
+  than VFS methods.  For example, pushes of small branches with tags take
+  11 rather than 18 smart server requests.  (Andrew Bennetts, #398608)
+
+* Sending Ctrl-Break on Windows will now drop you into the debugger, in
+  the same way that sending Ctrl-\\ does on other platforms.
+  (John Arbash Meinel)
+
 * The internal core code that handles specific file operations like
   ``bzr st FILENAME`` or ``bzr commit FILENAME`` has been changed to
   include the parent directories if they have altered, and when a
@@ -101,10 +127,6 @@
   fixes a number of causes for ``InconsistentDelta`` errors, and permits
   faster commit of specific paths. (Robert Collins, #347649)
 
-* Pushing branches with tags via ``bzr://`` and ``bzr+ssh://`` is much
-  faster, using a new ``Branch.set_tags_bytes`` smart server verb rather
-  than VFS methods.  For example, pushes of small branches with tags take
-  11 rather than 18 smart server requests.  (Andrew Bennetts, #398608)
 
 Documentation
 *************

=== modified file 'bzr'
--- a/bzr	2009-07-13 02:20:36 +0000
+++ b/bzr	2009-07-30 23:54:26 +0000
@@ -122,7 +122,7 @@
 bzrlib.inspect_for_copy.import_copy_with_hacked_inspect()
 
 import bzrlib.breakin
-bzrlib.breakin.hook_sigquit()
+bzrlib.breakin.hook_debugger_to_signal()
 
 import bzrlib.decorators
 if ('--lsprof' in sys.argv

=== modified file 'bzrlib/benchmarks/bench_knit.py'
--- a/bzrlib/benchmarks/bench_knit.py	2009-03-23 14:59:43 +0000
+++ b/bzrlib/benchmarks/bench_knit.py	2009-07-28 08:09:13 +0000
@@ -65,7 +65,7 @@
         def reset():
             knit._load_data = orig
         self.addCleanup(reset)
-        from bzrlib._knit_load_data_c import _load_data_c
+        from bzrlib._knit_load_data_pyx import _load_data_c
         knit._load_data = _load_data_c
 
     def setup_load_data_py(self):

=== modified file 'bzrlib/breakin.py'
--- a/bzrlib/breakin.py	2009-03-23 14:59:43 +0000
+++ b/bzrlib/breakin.py	2009-07-30 23:54:26 +0000
@@ -1,4 +1,4 @@
-# Copyright (C) 2006, 2007 Canonical Ltd
+# Copyright (C) 2006, 2007, 2009 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
@@ -17,25 +17,77 @@
 import os
 import signal
 
+
+_breakin_signal_number = None
+_breakin_signal_name = None
+
+
 def _debug(signal_number, interrupted_frame):
     import pdb
     import sys
-    sys.stderr.write("** SIGQUIT received, entering debugger\n"
+    sys.stderr.write("** %s received, entering debugger\n"
             "** Type 'c' to continue or 'q' to stop the process\n"
-            "** Or SIGQUIT again to quit (and possibly dump core)\n"
-            )
+            "** Or %s again to quit (and possibly dump core)\n"
+            % (_breakin_signal_name, _breakin_signal_name))
+    # It seems that on Windows, when sys.stderr is to a PIPE, then we need to
+    # flush. Not sure why it is buffered, but that seems to be the case.
+    sys.stderr.flush()
     # restore default meaning so that you can kill the process by hitting it
     # twice
-    signal.signal(signal.SIGQUIT, signal.SIG_DFL)
+    signal.signal(_breakin_signal_number, signal.SIG_DFL)
     try:
         pdb.set_trace()
     finally:
-        signal.signal(signal.SIGQUIT, _debug)
+        signal.signal(_breakin_signal_number, _debug)
 
 
 def hook_sigquit():
-    # when sigquit (C-\) is received go into pdb
-    if (os.environ.get('BZR_SIGQUIT_PDB', '1') == '0'
-        or getattr(signal, 'SIGQUIT', None) is None):
-        return
-    signal.signal(signal.SIGQUIT, _debug)
+    # We import this late because breakin.py is loaded as part of the main
+    # 'bzr' script, so we want it to load as little as possible until things
+    # are up and running
+    from bzrlib import symbol_versioning, trace
+    trace.mutter_callsite(2, 'Deprecated function called')
+    symbol_versioning.warn(symbol_versioning.deprecation_string(
+        hook_sigquit, symbol_versioning.deprecated_in((1, 18, 0))),
+        DeprecationWarning, stacklevel=2)
+
+    return hook_debugger_to_signal()
+
+
+def determine_signal():
+    global _breakin_signal_number
+    global _breakin_signal_name
+    if _breakin_signal_number is not None:
+        return _breakin_signal_number
+    # Note: As near as I can tell, Windows is the only one to define SIGBREAK,
+    #       and other platforms defined SIGQUIT. There doesn't seem to be a
+    #       platform that defines both.
+    #       -- jam 2009-07-30
+    sigquit = getattr(signal, 'SIGQUIT', None)
+    sigbreak = getattr(signal, 'SIGBREAK', None)
+    if sigquit is not None:
+        _breakin_signal_number = sigquit
+        _breakin_signal_name = 'SIGQUIT'
+    elif sigbreak is not None:
+        _breakin_signal_number = sigbreak
+        _breakin_signal_name = 'SIGBREAK'
+
+    return _breakin_signal_number
+
+
+def hook_debugger_to_signal():
+    """Add a signal handler so we drop into the debugger.
+
+    On Linux and Mac, this is hooked into SIGQUIT (C-\\) on Windows, this is
+    hooked into SIGBREAK (C-Pause).
+    """
+
+    # when sigquit (C-\) or sigbreak (C-Pause) is received go into pdb
+    if os.environ.get('BZR_SIGQUIT_PDB', '1') == '0':
+        # User explicitly requested we don't support this
+        return
+    sig = determine_signal()
+    if sig is None:
+        return
+    # print 'hooking into %s' % (_breakin_signal_name,)
+    signal.signal(sig, _debug)

=== modified file 'bzrlib/diff.py'
--- a/bzrlib/diff.py	2009-06-10 03:56:49 +0000
+++ b/bzrlib/diff.py	2009-07-29 21:35:05 +0000
@@ -171,6 +171,11 @@
 
         if not diff_opts:
             diff_opts = []
+        if sys.platform == 'win32':
+            # Popen doesn't do the proper encoding for external commands
+            # Since we are dealing with an ANSI api, use mbcs encoding
+            old_filename = old_filename.encode('mbcs')
+            new_filename = new_filename.encode('mbcs')
         diffcmd = ['diff',
                    '--label', old_filename,
                    old_abspath,

=== modified file 'bzrlib/export/dir_exporter.py'
--- a/bzrlib/export/dir_exporter.py	2009-07-23 16:30:49 +0000
+++ b/bzrlib/export/dir_exporter.py	2009-07-29 13:46:55 +0000
@@ -17,7 +17,7 @@
 """Export a Tree to a non-versioned directory.
 """
 
-
+import errno
 import os
 import StringIO
 
@@ -43,7 +43,15 @@
            left in a half-assed state.
     """
     mutter('export version %r', tree)
-    os.mkdir(dest)
+    try:
+        os.mkdir(dest)
+    except OSError, e:
+        if e.errno == errno.EEXIST:
+            # check if directory empty
+            if os.listdir(dest) != []:
+                raise errors.BzrError("Can't export tree to non-empty directory.")
+        else:
+            raise
     for dp, ie in _export_iter_entries(tree, subdir):
         fullpath = osutils.pathjoin(dest, dp)
         if ie.kind == "file":

=== modified file 'bzrlib/knit.py'
--- a/bzrlib/knit.py	2009-07-15 17:51:40 +0000
+++ b/bzrlib/knit.py	2009-07-28 08:09:13 +0000
@@ -3621,6 +3621,6 @@
                     to_process.extend(self._process_pending(key))
 
 try:
-    from bzrlib._knit_load_data_c import _load_data_c as _load_data
+    from bzrlib._knit_load_data_pyx import _load_data_c as _load_data
 except ImportError:
     from bzrlib._knit_load_data_py import _load_data_py as _load_data

=== modified file 'bzrlib/remote.py'
--- a/bzrlib/remote.py	2009-07-27 08:02:52 +0000
+++ b/bzrlib/remote.py	2009-07-30 04:27:05 +0000
@@ -1733,8 +1733,17 @@
         if (self.from_repository._fallback_repositories and
             self.to_format._fetch_order == 'topological'):
             return self._real_stream(self.from_repository, search)
-        return self.missing_parents_chain(search, [self.from_repository] +
-            self.from_repository._fallback_repositories)
+        sources = []
+        seen = set()
+        repos = [self.from_repository]
+        while repos:
+            repo = repos.pop(0)
+            if repo in seen:
+                continue
+            seen.add(repo)
+            repos.extend(repo._fallback_repositories)
+            sources.append(repo)
+        return self.missing_parents_chain(search, sources)
 
     def _real_stream(self, repo, search):
         """Get a stream for search from repo.

=== modified file 'bzrlib/smart/message.py'
--- a/bzrlib/smart/message.py	2009-07-06 09:45:49 +0000
+++ b/bzrlib/smart/message.py	2009-07-28 22:27:04 +0000
@@ -330,7 +330,7 @@
         while not self.finished_reading:
             while self._bytes_parts:
                 bytes_part = self._bytes_parts.popleft()
-                if 'hpss' in debug.debug_flags:
+                if 'hpssdetail' in debug.debug_flags:
                     mutter('              %d byte part read', len(bytes_part))
                 yield bytes_part
             self._read_more()

=== modified file 'bzrlib/tests/blackbox/test_breakin.py'
--- a/bzrlib/tests/blackbox/test_breakin.py	2009-03-23 14:59:43 +0000
+++ b/bzrlib/tests/blackbox/test_breakin.py	2009-07-31 14:56:55 +0000
@@ -1,4 +1,4 @@
-# Copyright (C) 2006, 2007 Canonical Ltd
+# Copyright (C) 2006, 2007, 2009 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
@@ -16,6 +16,11 @@
 
 """Blackbox tests for debugger breakin"""
 
+try:
+    import ctypes
+    have_ctypes = True
+except ImportError:
+    have_ctypes = False
 import errno
 import os
 import signal
@@ -24,6 +29,7 @@
 import time
 
 from bzrlib import (
+    breakin,
     errors,
     tests,
     )
@@ -34,9 +40,65 @@
     # wait() waiting for the child to exit when it's not going to.
 
     def setUp(self):
-        if sys.platform == 'win32':
-            raise tests.TestSkipped('breakin signal not tested on win32')
         super(TestBreakin, self).setUp()
+        if breakin.determine_signal() is None:
+            raise tests.TestSkipped('this platform is missing SIGQUIT'
+                                    ' or SIGBREAK')
+        if sys.platform == 'win32':
+            # Windows doesn't have os.kill, and we catch the SIGBREAK signal.
+            # We trigger SIGBREAK via a Console api so we need ctypes to access
+            # the function
+            if not have_ctypes:
+                raise tests.UnavailableFeature('ctypes')
+            self._send_signal = self._send_signal_win32
+        else:
+            self._send_signal = self._send_signal_via_kill
+
+    def _send_signal_via_kill(self, pid, sig_type):
+        if sig_type == 'break':
+            sig_num = signal.SIGQUIT
+        elif sig_type == 'kill':
+            sig_num = signal.SIGKILL
+        else:
+            raise ValueError("unknown signal type: %s" % (sig_type,))
+        os.kill(pid, sig_num)
+
+    def _send_signal_win32(self, pid, sig_type):
+        """Send a 'signal' on Windows.
+
+        Windows doesn't really have signals in the same way. All it really
+        supports is:
+            1) Sending SIGINT to the *current* process group (so self, and all
+                children of self)
+            2) Sending SIGBREAK to a process that shares the current console,
+                which can be in its own process group.
+        So we have start_bzr_subprocess create a new process group for the
+        spawned process (via a flag to Popen), and then we map
+            SIGQUIT to GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT)
+            SIGKILL to TerminateProcess
+        """
+        if sig_type == 'break':
+            CTRL_BREAK_EVENT = 1
+            # CTRL_C_EVENT = 0
+            ret = ctypes.windll.kernel32.GenerateConsoleCtrlEvent(
+                    CTRL_BREAK_EVENT, pid)
+            if ret == 0: #error
+                err = ctypes.FormatError()
+                raise RuntimeError('failed to send CTRL_BREAK: %s'
+                                   % (err,))
+        elif sig_type == 'kill':
+            # Does the exit code matter? For now we are just setting it to
+            # something other than 0
+            exit_code = breakin.determine_signal()
+            ctypes.windll.kernel32.TerminateProcess(pid, exit_code)
+
+    def _popen(self, *args, **kwargs):
+        if sys.platform == 'win32':
+            CREATE_NEW_PROCESS_GROUP = 512
+            # This allows us to send a signal to the child, *without* also
+            # sending it to ourselves
+            kwargs['creationflags'] = CREATE_NEW_PROCESS_GROUP
+        return super(TestBreakin, self)._popen(*args, **kwargs)
 
     def _dont_SIGQUIT_on_darwin(self):
         if sys.platform == 'darwin':
@@ -54,13 +116,17 @@
         for i in range(100):
             time.sleep(0.1)
             if sig is not None:
-                os.kill(pid, sig)
+                self._send_signal(pid, sig)
             # Use WNOHANG to ensure we don't get blocked, doing so, we may
             # leave the process continue after *we* die...
+            # Win32 doesn't support WNOHANG, so we just pass 0
+            opts = getattr(os, 'WNOHANG', 0)
             try:
-                # note: waitpid is different on win32, but this test only runs
-                # on unix
-                pid_killed, returncode = os.waitpid(pid, os.WNOHANG)
+                # TODO: waitpid doesn't work well on windows, we might consider
+                #       using WaitForSingleObject(proc._handle, TIMEOUT)
+                #       instead. Most notably, the WNOHANG isn't allowed, so
+                #       this can hang indefinitely.
+                pid_killed, returncode = os.waitpid(pid, opts)
                 if (pid_killed, returncode) != (0, 0):
                     if sig is not None:
                         # high bit in low byte says if core was dumped; we
@@ -88,8 +154,11 @@
         # wait for it to get started, and print the 'listening' line
         proc.stderr.readline()
         # first sigquit pops into debugger
-        os.kill(proc.pid, signal.SIGQUIT)
+        self._send_signal(proc.pid, 'break')
         # Wait for the debugger to acknowledge the signal reception
+        # Note that it is possible for this to deadlock if the child doesn't
+        # acknowlege the signal and write to stderr. Perhaps we should try
+        # os.read(proc.stderr.fileno())?
         err = proc.stderr.readline()
         self.assertContainsRe(err, r'entering debugger')
         # Now that the debugger is entered, we can ask him to quit
@@ -99,7 +168,7 @@
         dead, sig = self._wait_for_process(proc.pid)
         if not dead:
             # The process didn't finish, let's kill it before reporting failure
-            dead, sig = self._wait_for_process(proc.pid, signal.SIGKILL)
+            dead, sig = self._wait_for_process(proc.pid, 'kill')
             if dead:
                 raise tests.KnownFailure(
                     "subprocess wasn't terminated, it had to be killed")
@@ -115,15 +184,17 @@
         # wait for it to get started, and print the 'listening' line
         proc.stderr.readline()
         # break into the debugger
-        os.kill(proc.pid, signal.SIGQUIT)
+        self._send_signal(proc.pid, 'break')
         # Wait for the debugger to acknowledge the signal reception (since we
         # want to send a second signal, we ensure it doesn't get lost by
         # validating the first get received and produce its effect).
         err = proc.stderr.readline()
         self.assertContainsRe(err, r'entering debugger')
-        dead, sig = self._wait_for_process(proc.pid, signal.SIGQUIT)
-        self.assertTrue((dead and sig == signal.SIGQUIT),
-                        msg="subprocess wasn't terminated by repeated SIGQUIT")
+        dead, sig = self._wait_for_process(proc.pid, 'break')
+        self.assertTrue(dead)
+        # Either the child was dead before we could read its status, or the
+        # child was dead from the signal we sent it.
+        self.assertTrue(sig in (None, breakin.determine_signal()))
 
     def test_breakin_disabled(self):
         self._dont_SIGQUIT_on_darwin()
@@ -132,5 +203,5 @@
         # wait for it to get started, and print the 'listening' line
         proc.stderr.readline()
         # first hit should just kill it
-        os.kill(proc.pid, signal.SIGQUIT)
+        self._send_signal(proc.pid, 'break')
         proc.wait()

=== modified file 'bzrlib/tests/test_export.py'
--- a/bzrlib/tests/test_export.py	2009-07-23 18:36:54 +0000
+++ b/bzrlib/tests/test_export.py	2009-07-29 13:46:55 +0000
@@ -18,6 +18,7 @@
 
 
 from bzrlib import (
+    errors,
     export,
     osutils,
     tests,
@@ -42,3 +43,22 @@
         wt.add(['link'])
         export.export(wt, 'target', format="dir")
         self.failUnlessExists('target/link')
+
+    def test_dir_export_to_existing_empty_dir_success(self):
+        self.build_tree(['source/', 'source/a', 'source/b/', 'source/b/c'])
+        wt = self.make_branch_and_tree('source')
+        wt.add(['a', 'b', 'b/c'])
+        wt.commit('1')
+        self.build_tree(['target/'])
+        export.export(wt, 'target', format="dir")
+        self.failUnlessExists('target/a')
+        self.failUnlessExists('target/b')
+        self.failUnlessExists('target/b/c')
+
+    def test_dir_export_to_existing_nonempty_dir_fail(self):
+        self.build_tree(['source/', 'source/a', 'source/b/', 'source/b/c'])
+        wt = self.make_branch_and_tree('source')
+        wt.add(['a', 'b', 'b/c'])
+        wt.commit('1')
+        self.build_tree(['target/', 'target/foo'])
+        self.assertRaises(errors.BzrError, export.export, wt, 'target', format="dir")

=== modified file 'bzrlib/tests/test_knit.py'
--- a/bzrlib/tests/test_knit.py	2009-07-24 19:22:25 +0000
+++ b/bzrlib/tests/test_knit.py	2009-07-28 08:09:13 +0000
@@ -73,13 +73,13 @@
 
     def _probe(self):
         try:
-            import bzrlib._knit_load_data_c
+            import bzrlib._knit_load_data_pyx
         except ImportError:
             return False
         return True
 
     def feature_name(self):
-        return 'bzrlib._knit_load_data_c'
+        return 'bzrlib._knit_load_data_pyx'
 
 CompiledKnitFeature = _CompiledKnitFeature()
 
@@ -1316,7 +1316,7 @@
         def reset():
             knit._load_data = orig
         self.addCleanup(reset)
-        from bzrlib._knit_load_data_c import _load_data_c
+        from bzrlib._knit_load_data_pyx import _load_data_c
         knit._load_data = _load_data_c
         allow_writes = lambda: mode == 'w'
         return _KndxIndex(transport, mapper, lambda:None, allow_writes, lambda:True)

=== modified file 'bzrlib/tests/test_remote.py'
--- a/bzrlib/tests/test_remote.py	2009-07-16 05:22:50 +0000
+++ b/bzrlib/tests/test_remote.py	2009-07-30 04:27:05 +0000
@@ -2679,11 +2679,13 @@
                     result.append(content.key[-1])
         return result
 
-    def get_ordered_revs(self, format, order):
+    def get_ordered_revs(self, format, order, branch_factory=None):
         """Get a list of the revisions in a stream to format format.
 
         :param format: The format of the target.
         :param order: the order that target should have requested.
+        :param branch_factory: A callable to create a trunk and stacked branch
+            to fetch from. If none, self.prepare_stacked_remote_branch is used.
         :result: The revision ids in the stream, in the order seen,
             the topological order of revisions in the source.
         """
@@ -2691,7 +2693,9 @@
         target_repository_format = unordered_format.repository_format
         # Cross check
         self.assertEqual(order, target_repository_format._fetch_order)
-        trunk, stacked = self.prepare_stacked_remote_branch()
+        if branch_factory is None:
+            branch_factory = self.prepare_stacked_remote_branch
+        _, stacked = branch_factory()
         source = stacked.repository._get_source(target_repository_format)
         tip = stacked.last_revision()
         revs = stacked.repository.get_ancestry(tip)
@@ -2716,6 +2720,24 @@
         # from the server, then one from the backing branch.
         self.assertLength(2, self.hpss_calls)
 
+    def test_stacked_on_stacked_get_stream_unordered(self):
+        # Repository._get_source.get_stream() from a stacked repository which
+        # is itself stacked yields the full data from all three sources.
+        def make_stacked_stacked():
+            _, stacked = self.prepare_stacked_remote_branch()
+            tree = stacked.bzrdir.sprout('tree3', stacked=True
+                ).open_workingtree()
+            tree.commit('more local changes are better')
+            branch = Branch.open(self.get_url('tree3'))
+            branch.lock_read()
+            return None, branch
+        rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered',
+            branch_factory=make_stacked_stacked)
+        self.assertEqual(set(expected_revs), set(rev_ord))
+        # Getting unordered results should have made a streaming data request
+        # from the server, and one from each backing repo
+        self.assertLength(3, self.hpss_calls)
+
     def test_stacked_get_stream_topological(self):
         # Repository._get_source.get_stream() from a stacked repository with
         # topological sorting yields the full data from both stacked and

=== modified file 'doc/en/developer-guide/HACKING.txt'
--- a/doc/en/developer-guide/HACKING.txt	2009-06-15 07:22:34 +0000
+++ b/doc/en/developer-guide/HACKING.txt	2009-07-31 14:52:10 +0000
@@ -1167,10 +1167,12 @@
 then bzr will go into pdb post-mortem mode when an unhandled exception
 occurs.
 
-If you send a SIGQUIT signal to bzr, which can be done by pressing
-Ctrl-\\ on Unix, bzr will go into the debugger immediately.  You can
-continue execution by typing ``c``.  This can be disabled if necessary
-by setting the environment variable ``BZR_SIGQUIT_PDB=0``.
+If you send a SIGQUIT or SIGBREAK signal to bzr then it will drop into the
+debugger immediately. SIGQUIT can be generated by pressing Ctrl-\\ on
+Unix.  SIGBREAK is generated with Ctrl-Pause on Windows (some laptops have
+this as Fn-Pause).  You can continue execution by typing ``c``.  This can
+be disabled if necessary by setting the environment variable
+``BZR_SIGQUIT_PDB=0``.
 
 
 Debug Flags




More information about the bazaar-commits mailing list