Status: | Merged |
---|---|
Approved by: | Martin Pool |
Approved revision: | no longer in the source branch. |
Merged at revision: | 6047 |
Proposed branch: | lp:~mbp/bzr/integration |
Merge into: | lp:bzr |
Diff against target: |
643 lines (+314/-35) 18 files modified
bzrlib/config.py (+12/-0) bzrlib/dirstate.py (+38/-26) bzrlib/errors.py (+13/-0) bzrlib/help_topics/en/configuration.txt (+14/-0) bzrlib/index.py (+5/-0) bzrlib/osutils.py (+11/-0) bzrlib/repofmt/pack_repo.py (+14/-4) bzrlib/tests/__init__.py (+23/-0) bzrlib/tests/test_http.py (+66/-0) bzrlib/tests/test_selftest.py (+12/-0) bzrlib/tests/test_transport.py (+19/-0) bzrlib/transport/__init__.py (+25/-1) bzrlib/transport/http/__init__.py (+1/-1) bzrlib/transport/http/response.py (+8/-1) bzrlib/transport/local.py (+1/-2) doc/developers/testing.txt (+17/-0) doc/en/release-notes/bzr-2.3.txt (+3/-0) doc/en/release-notes/bzr-2.4.txt (+32/-0) |
To merge this branch: | bzr merge lp:~mbp/bzr/integration |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
bzr-core | Pending | ||
Review via email: mp+70097@code.launchpad.net |
Commit message
merge 2.3 and 2.4 to trunk
Description of the change
merge up 2.3 and 2.4 into trunk
To post a comment you must log in.
Revision history for this message
Martin Pool (mbp) wrote : | # |
lp:~mbp/bzr/integration
updated
- 6047. By Canonical.com Patch Queue Manager <email address hidden>
-
(mbp) merge 2.3 and 2.4 to trunk (Martin Pool)
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'bzrlib/config.py' |
2 | --- bzrlib/config.py 2011-07-23 16:33:38 +0000 |
3 | +++ bzrlib/config.py 2011-08-02 01:26:10 +0000 |
4 | @@ -2291,6 +2291,15 @@ |
5 | 'editor', Option('editor'), |
6 | help='The command called to launch an editor to enter a message.') |
7 | |
8 | +option_registry.register( |
9 | + 'dirstate.fdatasync', Option('dirstate.fdatasync', default=True), |
10 | + help='Flush dirstate changes onto physical disk?') |
11 | + |
12 | +option_registry.register( |
13 | + 'repository.fdatasync', |
14 | + Option('repository.fdatasync', default=True), |
15 | + help='Flush repository changes onto physical disk?') |
16 | + |
17 | |
18 | class Section(object): |
19 | """A section defines a dict of option name => value. |
20 | @@ -2821,6 +2830,9 @@ |
21 | class LocationStack(_CompatibleStack): |
22 | |
23 | def __init__(self, location): |
24 | + """Make a new stack for a location and global configuration. |
25 | + |
26 | + :param location: A URL prefix to """ |
27 | lstore = LocationStore() |
28 | matcher = LocationMatcher(lstore, location) |
29 | gstore = GlobalStore() |
30 | |
31 | === modified file 'bzrlib/dirstate.py' |
32 | --- bzrlib/dirstate.py 2011-05-19 18:20:37 +0000 |
33 | +++ bzrlib/dirstate.py 2011-08-02 01:26:10 +0000 |
34 | @@ -232,6 +232,7 @@ |
35 | |
36 | from bzrlib import ( |
37 | cache_utf8, |
38 | + config, |
39 | debug, |
40 | errors, |
41 | inventory, |
42 | @@ -239,6 +240,7 @@ |
43 | osutils, |
44 | static_tuple, |
45 | trace, |
46 | + urlutils, |
47 | ) |
48 | |
49 | |
50 | @@ -448,6 +450,8 @@ |
51 | self._known_hash_changes = set() |
52 | # How many hash changed entries can we have without saving |
53 | self._worth_saving_limit = worth_saving_limit |
54 | + self._config_stack = config.LocationStack(urlutils.local_path_to_url( |
55 | + path)) |
56 | |
57 | def __repr__(self): |
58 | return "%s(%r)" % \ |
59 | @@ -2508,33 +2512,41 @@ |
60 | # IN_MEMORY_HASH_MODIFIED, we should only fail quietly if we fail |
61 | # to save an IN_MEMORY_HASH_MODIFIED, and fail *noisily* if we |
62 | # fail to save IN_MEMORY_MODIFIED |
63 | - if self._worth_saving(): |
64 | - grabbed_write_lock = False |
65 | - if self._lock_state != 'w': |
66 | - grabbed_write_lock, new_lock = self._lock_token.temporary_write_lock() |
67 | - # Switch over to the new lock, as the old one may be closed. |
68 | + if not self._worth_saving(): |
69 | + return |
70 | + |
71 | + grabbed_write_lock = False |
72 | + if self._lock_state != 'w': |
73 | + grabbed_write_lock, new_lock = self._lock_token.temporary_write_lock() |
74 | + # Switch over to the new lock, as the old one may be closed. |
75 | + # TODO: jam 20070315 We should validate the disk file has |
76 | + # not changed contents, since temporary_write_lock may |
77 | + # not be an atomic operation. |
78 | + self._lock_token = new_lock |
79 | + self._state_file = new_lock.f |
80 | + if not grabbed_write_lock: |
81 | + # We couldn't grab a write lock, so we switch back to a read one |
82 | + return |
83 | + try: |
84 | + lines = self.get_lines() |
85 | + self._state_file.seek(0) |
86 | + self._state_file.writelines(lines) |
87 | + self._state_file.truncate() |
88 | + self._state_file.flush() |
89 | + self._maybe_fdatasync() |
90 | + self._mark_unmodified() |
91 | + finally: |
92 | + if grabbed_write_lock: |
93 | + self._lock_token = self._lock_token.restore_read_lock() |
94 | + self._state_file = self._lock_token.f |
95 | # TODO: jam 20070315 We should validate the disk file has |
96 | - # not changed contents. Since temporary_write_lock may |
97 | - # not be an atomic operation. |
98 | - self._lock_token = new_lock |
99 | - self._state_file = new_lock.f |
100 | - if not grabbed_write_lock: |
101 | - # We couldn't grab a write lock, so we switch back to a read one |
102 | - return |
103 | - try: |
104 | - lines = self.get_lines() |
105 | - self._state_file.seek(0) |
106 | - self._state_file.writelines(lines) |
107 | - self._state_file.truncate() |
108 | - self._state_file.flush() |
109 | - self._mark_unmodified() |
110 | - finally: |
111 | - if grabbed_write_lock: |
112 | - self._lock_token = self._lock_token.restore_read_lock() |
113 | - self._state_file = self._lock_token.f |
114 | - # TODO: jam 20070315 We should validate the disk file has |
115 | - # not changed contents. Since restore_read_lock may |
116 | - # not be an atomic operation. |
117 | + # not changed contents. Since restore_read_lock may |
118 | + # not be an atomic operation. |
119 | + |
120 | + def _maybe_fdatasync(self): |
121 | + """Flush to disk if possible and if not configured off.""" |
122 | + if self._config_stack.get('dirstate.fdatasync'): |
123 | + osutils.fdatasync(self._state_file.fileno()) |
124 | |
125 | def _worth_saving(self): |
126 | """Is it worth saving the dirstate or not?""" |
127 | |
128 | === modified file 'bzrlib/errors.py' |
129 | --- bzrlib/errors.py 2011-07-16 20:06:11 +0000 |
130 | +++ bzrlib/errors.py 2011-08-02 01:26:10 +0000 |
131 | @@ -1737,6 +1737,19 @@ |
132 | InvalidHttpResponse.__init__(self, path, msg) |
133 | |
134 | |
135 | +class HttpBoundaryMissing(InvalidHttpResponse): |
136 | + """A multipart response ends with no boundary marker. |
137 | + |
138 | + This is a special case caused by buggy proxies, described in |
139 | + <https://bugs.launchpad.net/bzr/+bug/198646>. |
140 | + """ |
141 | + |
142 | + _fmt = "HTTP MIME Boundary missing for %(path)s: %(msg)s" |
143 | + |
144 | + def __init__(self, path, msg): |
145 | + InvalidHttpResponse.__init__(self, path, msg) |
146 | + |
147 | + |
148 | class InvalidHttpContentType(InvalidHttpResponse): |
149 | |
150 | _fmt = 'Invalid http Content-type "%(ctype)s" for %(path)s: %(msg)s' |
151 | |
152 | === modified file 'bzrlib/help_topics/en/configuration.txt' |
153 | --- bzrlib/help_topics/en/configuration.txt 2011-07-11 10:53:46 +0000 |
154 | +++ bzrlib/help_topics/en/configuration.txt 2011-08-02 01:26:10 +0000 |
155 | @@ -415,6 +415,13 @@ |
156 | committed revisions only when the branch requires them. ``never`` will refuse |
157 | to sign newly committed revisions, even if the branch requires signatures. |
158 | |
159 | +dirstate.fdatasync |
160 | +~~~~~~~~~~~~~~~~~~ |
161 | + |
162 | +If true (default), working tree metadata changes are flushed through the |
163 | +OS buffers to physical disk. This is somewhat slower, but means data |
164 | +should not be lost if the machine crashes. See also repository.fdatasync. |
165 | + |
166 | gpg_signing_key |
167 | ~~~~~~~~~~~ |
168 | |
169 | @@ -505,6 +512,13 @@ |
170 | :mapi: Use your preferred e-mail client on Windows. |
171 | :xdg-email: Use xdg-email to run your preferred mail program |
172 | |
173 | +repository.fdatasync |
174 | +~~~~~~~~~~~~~~~~~~~~ |
175 | + |
176 | +If true (default), repository changes are flushed through the OS buffers |
177 | +to physical disk. This is somewhat slower, but means data should not be |
178 | +lost if the machine crashes. See also dirstate.fdatasync. |
179 | + |
180 | submit_branch |
181 | ~~~~~~~~~~~~~ |
182 | |
183 | |
184 | === modified file 'bzrlib/index.py' |
185 | --- bzrlib/index.py 2011-05-19 09:32:38 +0000 |
186 | +++ bzrlib/index.py 2011-08-02 01:26:10 +0000 |
187 | @@ -245,6 +245,11 @@ |
188 | """ |
189 | |
190 | def finish(self): |
191 | + """Finish the index. |
192 | + |
193 | + :returns: cStringIO holding the full context of the index as it |
194 | + should be written to disk. |
195 | + """ |
196 | lines = [_SIGNATURE] |
197 | lines.append(_OPTION_NODE_REFS + str(self.reference_lists) + '\n') |
198 | lines.append(_OPTION_KEY_ELEMENTS + str(self._key_length) + '\n') |
199 | |
200 | === modified file 'bzrlib/osutils.py' |
201 | --- bzrlib/osutils.py 2011-06-16 18:34:26 +0000 |
202 | +++ bzrlib/osutils.py 2011-08-02 01:26:10 +0000 |
203 | @@ -2487,3 +2487,14 @@ |
204 | is_local_pid_dead = win32utils.is_local_pid_dead |
205 | else: |
206 | is_local_pid_dead = _posix_is_local_pid_dead |
207 | + |
208 | + |
209 | +def fdatasync(fileno): |
210 | + """Flush file contents to disk if possible. |
211 | + |
212 | + :param fileno: Integer OS file handle. |
213 | + :raises TransportNotPossible: If flushing to disk is not possible. |
214 | + """ |
215 | + fn = getattr(os, 'fdatasync', getattr(os, 'fsync', None)) |
216 | + if fn is not None: |
217 | + fn(fileno) |
218 | |
219 | === modified file 'bzrlib/repofmt/pack_repo.py' |
220 | --- bzrlib/repofmt/pack_repo.py 2011-05-27 12:01:22 +0000 |
221 | +++ bzrlib/repofmt/pack_repo.py 2011-08-02 01:26:10 +0000 |
222 | @@ -25,6 +25,7 @@ |
223 | from bzrlib import ( |
224 | chk_map, |
225 | cleanup, |
226 | + config, |
227 | debug, |
228 | graph, |
229 | osutils, |
230 | @@ -478,7 +479,8 @@ |
231 | # visible is smaller. On the other hand none will be seen until |
232 | # they're in the names list. |
233 | self.index_sizes = [None, None, None, None] |
234 | - self._write_index('revision', self.revision_index, 'revision', suspend) |
235 | + self._write_index('revision', self.revision_index, 'revision', |
236 | + suspend) |
237 | self._write_index('inventory', self.inventory_index, 'inventory', |
238 | suspend) |
239 | self._write_index('text', self.text_index, 'file texts', suspend) |
240 | @@ -488,7 +490,8 @@ |
241 | self.index_sizes.append(None) |
242 | self._write_index('chk', self.chk_index, |
243 | 'content hash bytes', suspend) |
244 | - self.write_stream.close() |
245 | + self.write_stream.close( |
246 | + want_fdatasync=self._pack_collection.config_stack.get('repository.fdatasync')) |
247 | # Note that this will clobber an existing pack with the same name, |
248 | # without checking for hash collisions. While this is undesirable this |
249 | # is something that can be rectified in a subsequent release. One way |
250 | @@ -537,8 +540,14 @@ |
251 | transport = self.upload_transport |
252 | else: |
253 | transport = self.index_transport |
254 | - self.index_sizes[self.index_offset(index_type)] = transport.put_file( |
255 | - index_name, index.finish(), mode=self._file_mode) |
256 | + index_tempfile = index.finish() |
257 | + index_bytes = index_tempfile.read() |
258 | + write_stream = transport.open_write_stream(index_name, |
259 | + mode=self._file_mode) |
260 | + write_stream.write(index_bytes) |
261 | + write_stream.close( |
262 | + want_fdatasync=self._pack_collection.config_stack.get('repository.fdatasync')) |
263 | + self.index_sizes[self.index_offset(index_type)] = len(index_bytes) |
264 | if 'pack' in debug.debug_flags: |
265 | # XXX: size might be interesting? |
266 | mutter('%s: create_pack: wrote %s index: %s%s t+%6.3fs', |
267 | @@ -822,6 +831,7 @@ |
268 | set(all_combined).difference([combined_idx])) |
269 | # resumed packs |
270 | self._resumed_packs = [] |
271 | + self.config_stack = config.LocationStack(self.transport.base) |
272 | |
273 | def __repr__(self): |
274 | return '%s(%r)' % (self.__class__.__name__, self.repo) |
275 | |
276 | === modified file 'bzrlib/tests/__init__.py' |
277 | --- bzrlib/tests/__init__.py 2011-07-25 11:19:19 +0000 |
278 | +++ bzrlib/tests/__init__.py 2011-08-02 01:26:10 +0000 |
279 | @@ -1728,6 +1728,9 @@ |
280 | def overrideAttr(self, obj, attr_name, new=_unitialized_attr): |
281 | """Overrides an object attribute restoring it after the test. |
282 | |
283 | + :note: This should be used with discretion; you should think about |
284 | + whether it's better to make the code testable without monkey-patching. |
285 | + |
286 | :param obj: The object that will be mutated. |
287 | |
288 | :param attr_name: The attribute name we want to preserve/override in |
289 | @@ -1758,6 +1761,26 @@ |
290 | self.addCleanup(osutils.set_or_unset_env, name, value) |
291 | return value |
292 | |
293 | + def recordCalls(self, obj, attr_name): |
294 | + """Monkeypatch in a wrapper that will record calls. |
295 | + |
296 | + The monkeypatch is automatically removed when the test concludes. |
297 | + |
298 | + :param obj: The namespace holding the reference to be replaced; |
299 | + typically a module, class, or object. |
300 | + :param attr_name: A string for the name of the attribute to |
301 | + patch. |
302 | + :returns: A list that will be extended with one item every time the |
303 | + function is called, with a tuple of (args, kwargs). |
304 | + """ |
305 | + calls = [] |
306 | + |
307 | + def decorator(*args, **kwargs): |
308 | + calls.append((args, kwargs)) |
309 | + return orig(*args, **kwargs) |
310 | + orig = self.overrideAttr(obj, attr_name, decorator) |
311 | + return calls |
312 | + |
313 | def _cleanEnvironment(self): |
314 | for name, value in isolated_environ.iteritems(): |
315 | self.overrideEnv(name, value) |
316 | |
317 | === modified file 'bzrlib/tests/test_http.py' |
318 | --- bzrlib/tests/test_http.py 2011-06-14 01:26:41 +0000 |
319 | +++ bzrlib/tests/test_http.py 2011-08-02 01:26:10 +0000 |
320 | @@ -1048,6 +1048,72 @@ |
321 | self.assertEqual('single', t._range_hint) |
322 | |
323 | |
324 | +class TruncatedBeforeBoundaryRequestHandler( |
325 | + http_server.TestingHTTPRequestHandler): |
326 | + """Truncation before a boundary, like in bug 198646""" |
327 | + |
328 | + _truncated_ranges = 1 |
329 | + |
330 | + def get_multiple_ranges(self, file, file_size, ranges): |
331 | + self.send_response(206) |
332 | + self.send_header('Accept-Ranges', 'bytes') |
333 | + boundary = 'tagada' |
334 | + self.send_header('Content-Type', |
335 | + 'multipart/byteranges; boundary=%s' % boundary) |
336 | + boundary_line = '--%s\r\n' % boundary |
337 | + # Calculate the Content-Length |
338 | + content_length = 0 |
339 | + for (start, end) in ranges: |
340 | + content_length += len(boundary_line) |
341 | + content_length += self._header_line_length( |
342 | + 'Content-type', 'application/octet-stream') |
343 | + content_length += self._header_line_length( |
344 | + 'Content-Range', 'bytes %d-%d/%d' % (start, end, file_size)) |
345 | + content_length += len('\r\n') # end headers |
346 | + content_length += end - start # + 1 |
347 | + content_length += len(boundary_line) |
348 | + self.send_header('Content-length', content_length) |
349 | + self.end_headers() |
350 | + |
351 | + # Send the multipart body |
352 | + cur = 0 |
353 | + for (start, end) in ranges: |
354 | + if cur + self._truncated_ranges >= len(ranges): |
355 | + # Abruptly ends the response and close the connection |
356 | + self.close_connection = 1 |
357 | + return |
358 | + self.wfile.write(boundary_line) |
359 | + self.send_header('Content-type', 'application/octet-stream') |
360 | + self.send_header('Content-Range', 'bytes %d-%d/%d' |
361 | + % (start, end, file_size)) |
362 | + self.end_headers() |
363 | + self.send_range_content(file, start, end - start + 1) |
364 | + cur += 1 |
365 | + # Final boundary |
366 | + self.wfile.write(boundary_line) |
367 | + |
368 | + |
369 | +class TestTruncatedBeforeBoundary(TestSpecificRequestHandler): |
370 | + """Tests the case of bug 198646, disconnecting before a boundary.""" |
371 | + |
372 | + _req_handler_class = TruncatedBeforeBoundaryRequestHandler |
373 | + |
374 | + def setUp(self): |
375 | + super(TestTruncatedBeforeBoundary, self).setUp() |
376 | + self.build_tree_contents([('a', '0123456789')],) |
377 | + |
378 | + def test_readv_with_short_reads(self): |
379 | + server = self.get_readonly_server() |
380 | + t = self.get_readonly_transport() |
381 | + # Force separate ranges for each offset |
382 | + t._bytes_to_read_before_seek = 0 |
383 | + ireadv = iter(t.readv('a', ((0, 1), (2, 1), (4, 2), (9, 1)))) |
384 | + self.assertEqual((0, '0'), ireadv.next()) |
385 | + self.assertEqual((2, '2'), ireadv.next()) |
386 | + self.assertEqual((4, '45'), ireadv.next()) |
387 | + self.assertEqual((9, '9'), ireadv.next()) |
388 | + |
389 | + |
390 | class LimitedRangeRequestHandler(http_server.TestingHTTPRequestHandler): |
391 | """Errors out when range specifiers exceed the limit""" |
392 | |
393 | |
394 | === modified file 'bzrlib/tests/test_selftest.py' |
395 | --- bzrlib/tests/test_selftest.py 2011-07-11 06:47:32 +0000 |
396 | +++ bzrlib/tests/test_selftest.py 2011-08-02 01:26:10 +0000 |
397 | @@ -1666,6 +1666,18 @@ |
398 | test.run(unittest.TestResult()) |
399 | self.assertEqual('original', obj.test_attr) |
400 | |
401 | + def test_recordCalls(self): |
402 | + from bzrlib.tests import test_selftest |
403 | + calls = self.recordCalls( |
404 | + test_selftest, '_add_numbers') |
405 | + self.assertEqual(test_selftest._add_numbers(2, 10), |
406 | + 12) |
407 | + self.assertEquals(calls, [((2, 10), {})]) |
408 | + |
409 | + |
410 | +def _add_numbers(a, b): |
411 | + return a + b |
412 | + |
413 | |
414 | class _MissingFeature(features.Feature): |
415 | def _probe(self): |
416 | |
417 | === modified file 'bzrlib/tests/test_transport.py' |
418 | --- bzrlib/tests/test_transport.py 2011-07-25 11:19:19 +0000 |
419 | +++ bzrlib/tests/test_transport.py 2011-08-02 01:26:10 +0000 |
420 | @@ -740,6 +740,25 @@ |
421 | self.assertEquals(t.local_abspath(''), here) |
422 | |
423 | |
424 | +class TestLocalTransportWriteStream(tests.TestCaseWithTransport): |
425 | + |
426 | + def test_local_fdatasync_calls_fdatasync(self): |
427 | + """Check fdatasync on a stream tries to flush the data to the OS. |
428 | + |
429 | + We can't easily observe the external effect but we can at least see |
430 | + it's called. |
431 | + """ |
432 | + t = self.get_transport('.') |
433 | + calls = self.recordCalls(os, 'fdatasync') |
434 | + w = t.open_write_stream('out') |
435 | + w.write('foo') |
436 | + w.fdatasync() |
437 | + with open('out', 'rb') as f: |
438 | + # Should have been flushed. |
439 | + self.assertEquals(f.read(), 'foo') |
440 | + self.assertEquals(len(calls), 1, calls) |
441 | + |
442 | + |
443 | class TestWin32LocalTransport(tests.TestCase): |
444 | |
445 | def test_unc_clone_to_root(self): |
446 | |
447 | === modified file 'bzrlib/transport/__init__.py' |
448 | --- bzrlib/transport/__init__.py 2011-07-27 03:03:49 +0000 |
449 | +++ bzrlib/transport/__init__.py 2011-08-02 01:26:10 +0000 |
450 | @@ -27,6 +27,7 @@ |
451 | """ |
452 | |
453 | from cStringIO import StringIO |
454 | +import os |
455 | import sys |
456 | |
457 | from bzrlib.lazy_import import lazy_import |
458 | @@ -227,10 +228,24 @@ |
459 | def _close(self): |
460 | """A hook point for subclasses that need to take action on close.""" |
461 | |
462 | - def close(self): |
463 | + def close(self, want_fdatasync=False): |
464 | + if want_fdatasync: |
465 | + try: |
466 | + self.fdatasync() |
467 | + except errors.TransportNotPossible: |
468 | + pass |
469 | self._close() |
470 | del _file_streams[self.transport.abspath(self.relpath)] |
471 | |
472 | + def fdatasync(self): |
473 | + """Force data out to physical disk if possible. |
474 | + |
475 | + :raises TransportNotPossible: If this transport has no way to |
476 | + flush to disk. |
477 | + """ |
478 | + raise errors.TransportNotPossible( |
479 | + "%s cannot fdatasync" % (self.transport,)) |
480 | + |
481 | |
482 | class FileFileStream(FileStream): |
483 | """A file stream object returned by open_write_stream. |
484 | @@ -245,6 +260,15 @@ |
485 | def _close(self): |
486 | self.file_handle.close() |
487 | |
488 | + def fdatasync(self): |
489 | + """Force data out to physical disk if possible.""" |
490 | + self.file_handle.flush() |
491 | + try: |
492 | + fileno = self.file_handle.fileno() |
493 | + except AttributeError: |
494 | + raise errors.TransportNotPossible() |
495 | + osutils.fdatasync(fileno) |
496 | + |
497 | def write(self, bytes): |
498 | osutils.pump_string_file(bytes, self.file_handle) |
499 | |
500 | |
501 | === modified file 'bzrlib/transport/http/__init__.py' |
502 | --- bzrlib/transport/http/__init__.py 2011-05-27 07:39:41 +0000 |
503 | +++ bzrlib/transport/http/__init__.py 2011-08-02 01:26:10 +0000 |
504 | @@ -271,7 +271,7 @@ |
505 | cur_offset_and_size = iter_offsets.next() |
506 | |
507 | except (errors.ShortReadvError, errors.InvalidRange, |
508 | - errors.InvalidHttpRange), e: |
509 | + errors.InvalidHttpRange, errors.HttpBoundaryMissing), e: |
510 | mutter('Exception %r: %s during http._readv',e, e) |
511 | if (not isinstance(e, errors.ShortReadvError) |
512 | or retried_offset == cur_offset_and_size): |
513 | |
514 | === modified file 'bzrlib/transport/http/response.py' |
515 | --- bzrlib/transport/http/response.py 2009-03-23 14:59:43 +0000 |
516 | +++ bzrlib/transport/http/response.py 2011-08-02 01:26:10 +0000 |
517 | @@ -1,4 +1,4 @@ |
518 | -# Copyright (C) 2006, 2007 Canonical Ltd |
519 | +# Copyright (C) 2006-2011 Canonical Ltd |
520 | # |
521 | # This program is free software; you can redistribute it and/or modify |
522 | # it under the terms of the GNU General Public License as published by |
523 | @@ -109,6 +109,13 @@ |
524 | # To be on the safe side we allow it before any boundary line |
525 | boundary_line = self._file.readline() |
526 | |
527 | + if boundary_line == '': |
528 | + # A timeout in the proxy server caused the response to end early. |
529 | + # See launchpad bug 198646. |
530 | + raise errors.HttpBoundaryMissing( |
531 | + self._path, |
532 | + self._boundary) |
533 | + |
534 | if boundary_line != '--' + self._boundary + '\r\n': |
535 | # rfc822.unquote() incorrectly unquotes strings enclosed in <> |
536 | # IIS 6 and 7 incorrectly wrap boundary strings in <> |
537 | |
538 | === modified file 'bzrlib/transport/local.py' |
539 | --- bzrlib/transport/local.py 2011-04-07 10:36:24 +0000 |
540 | +++ bzrlib/transport/local.py 2011-08-02 01:26:10 +0000 |
541 | @@ -327,10 +327,9 @@ |
542 | |
543 | def open_write_stream(self, relpath, mode=None): |
544 | """See Transport.open_write_stream.""" |
545 | - # initialise the file |
546 | - self.put_bytes_non_atomic(relpath, "", mode=mode) |
547 | abspath = self._abspath(relpath) |
548 | handle = osutils.open_file(abspath, 'wb') |
549 | + handle.truncate() |
550 | if mode is not None: |
551 | self._check_mode_and_size(abspath, handle.fileno(), mode) |
552 | transport._file_streams[self.abspath(relpath)] = handle |
553 | |
554 | === modified file 'doc/developers/testing.txt' |
555 | --- doc/developers/testing.txt 2011-05-30 07:36:53 +0000 |
556 | +++ doc/developers/testing.txt 2011-08-02 01:26:10 +0000 |
557 | @@ -1018,6 +1018,23 @@ |
558 | |
559 | self.overrideAttr(osutils, '_cached_user_encoding', 'latin-1') |
560 | |
561 | +This should be used with discretion; sometimes it's better to make the |
562 | +underlying code more testable so that you don't need to rely on monkey |
563 | +patching. |
564 | + |
565 | + |
566 | +Observing calls to a function |
567 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
568 | + |
569 | +Sometimes it's useful to observe how a function is called, typically when |
570 | +calling it has side effects but the side effects are not easy to observe |
571 | +from a test case. For instance the function may be expensive and we want |
572 | +to assert it is not called too many times, or it has effects on the |
573 | +machine that are safe to run during a test but not easy to measure. In |
574 | +these cases, you can use `recordCalls` which will monkey-patch in a |
575 | +wrapper that records when the function is called. |
576 | + |
577 | + |
578 | Temporarily changing environment variables |
579 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
580 | |
581 | |
582 | === modified file 'doc/en/release-notes/bzr-2.3.txt' |
583 | --- doc/en/release-notes/bzr-2.3.txt 2011-07-15 08:25:00 +0000 |
584 | +++ doc/en/release-notes/bzr-2.3.txt 2011-08-02 01:26:10 +0000 |
585 | @@ -32,6 +32,9 @@ |
586 | .. Fixes for situations where bzr would previously crash or give incorrect |
587 | or undesirable results. |
588 | |
589 | +* Cope cleanly with buggy HTTP proxies that close the socket in the middle |
590 | + of a multipart response. (Martin Pool, #198646). |
591 | + |
592 | Documentation |
593 | ************* |
594 | |
595 | |
596 | === modified file 'doc/en/release-notes/bzr-2.4.txt' |
597 | --- doc/en/release-notes/bzr-2.4.txt 2011-07-25 02:51:30 +0000 |
598 | +++ doc/en/release-notes/bzr-2.4.txt 2011-08-02 01:26:10 +0000 |
599 | @@ -32,6 +32,31 @@ |
600 | .. Fixes for situations where bzr would previously crash or give incorrect |
601 | or undesirable results. |
602 | |
603 | +* Accessing a packaging branch on Launchpad (eg, ``lp:ubuntu/bzr``) now |
604 | + checks to see if the most recent published source package version for |
605 | + that project is present in the branch tags. This should help developers |
606 | + trust whether the packaging branch is up-to-date and can be used for new |
607 | + changes. The level of verbosity is controlled by the config item |
608 | + ``launchpad.packaging_verbosity``. It can be set to one of |
609 | + |
610 | + off |
611 | + disable all checks |
612 | + |
613 | + |
614 | + minimal |
615 | + only display if the branch is out-of-date |
616 | + |
617 | + short |
618 | + also display single-line up-to-date and missing, |
619 | + |
620 | + |
621 | + all |
622 | + (default) display multi-line content for all states |
623 | + |
624 | + |
625 | + (John Arbash Meinel, #609187, #812928) |
626 | + |
627 | + |
628 | * The fix for bug #513709 caused us to open a new connection when |
629 | switching a lightweight checkout that was pointing at a bound branch. |
630 | This isn't necessary because we know the master URL without opening it, |
631 | @@ -114,6 +139,13 @@ |
632 | * ``Branch.open`` is now about 3x faster (about 2ms instead of 6.5ms). |
633 | (Andrew Bennetts). |
634 | |
635 | +* Pack, dirstate, and index files are synced to persistent storage if |
636 | + possible when writing finishes, to reduce the risk of problems caused by |
637 | + a machine crash or similar problem. This can be turned off through the |
638 | + ``dirstate.fdatasync`` and ``repository.fdatasync`` options, which can |
639 | + be set in ``locations.conf`` or ``bazaar.conf``. (Martin Pool, |
640 | + #343427) |
641 | + |
642 | Bug Fixes |
643 | ********* |
644 |
sent to pqm by email