Merge lp:~garyvdm/bzr/Bug427773 into lp:bzr/2.0

Proposed by Alexander Belchenko
Status: Rejected
Rejected by: Gary van der Merwe
Proposed branch: lp:~garyvdm/bzr/Bug427773
Merge into: lp:bzr/2.0
Diff against target: None lines
To merge this branch: bzr merge lp:~garyvdm/bzr/Bug427773
Reviewer Review Type Date Requested Status
bzr-core Pending
Review via email: mp+11645@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Gary van der Merwe (garyvdm) wrote :

I've specially prepared a cherry-picked patch for the 2.0 branch. https://code.launchpad.net/~garyvdm/bzr/427773-2.0-unlock-when-limbo/+merge/11631

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'Makefile'
--- Makefile 2009-08-03 20:38:39 +0000
+++ Makefile 2009-08-27 00:53:27 +0000
@@ -1,4 +1,4 @@
1# Copyright (C) 2005, 2006, 2007, 2008 Canonical Ltd1# Copyright (C) 2005, 2006, 2007, 2008, 2009 Canonical Ltd
2#2#
3# This program is free software; you can redistribute it and/or modify3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by4# it under the terms of the GNU General Public License as published by
@@ -40,8 +40,6 @@
4040
41check-nodocs: extensions41check-nodocs: extensions
42 $(PYTHON) -Werror -O ./bzr selftest -1v $(tests)42 $(PYTHON) -Werror -O ./bzr selftest -1v $(tests)
43 @echo "Running all tests with no locale."
44 LC_CTYPE= LANG=C LC_ALL= ./bzr selftest -1v $(tests) 2>&1 | sed -e 's/^/[ascii] /'
4543
46# Run Python style checker (apt-get install pyflakes)44# Run Python style checker (apt-get install pyflakes)
47#45#
4846
=== modified file 'NEWS'
--- NEWS 2009-08-25 05:32:07 +0000
+++ NEWS 2009-09-11 14:23:16 +0000
@@ -6,6 +6,309 @@
6.. contents:: List of Releases6.. contents:: List of Releases
7 :depth: 17 :depth: 1
88
9In Development
10##############
11
12Compatibility Breaks
13********************
14
15New Features
16************
17
18* Give more control on BZR_PLUGIN_PATH by providing a way to refer to or
19 disable the user, site and core plugin directories.
20 (Vincent Ladeuil, #412930, #316192, #145612)
21
22Bug Fixes
23*********
24
25* Bazaar's native protocol code now correctly handles EINTR, which most
26 noticeably occurs if you break in to the debugger while connected to a
27 bzr+ssh server. You can now can continue from the debugger (by typing
28 'c') and the process continues. However, note that pressing C-\ in the
29 shell may still kill the SSH process, which is bug 162509, so you must
30 sent a signal to the bzr process specifically, for example by typing
31 ``kill -QUIT PID`` in another shell. (Martin Pool, #341535)
32
33* ``bzr check`` in pack-0.92, 1.6 and 1.9 format repositories will no
34 longer report incorrect errors about ``Missing inventory ('TREE_ROOT', ...)``
35 (Robert Collins, #416732)
36
37* ``bzr info -v`` on a 2a format still claimed that it was a "Development
38 format" (John Arbash Meinel, #424392)
39
40* ``bzr merge`` and ``bzr remove-tree`` now requires --force if pending
41 merges are present in the working tree.
42 (Vincent Ladeuil, #426344)
43
44* Clearer message when Bazaar runs out of memory, instead of a ``MemoryError``
45 traceback. (Martin Pool, #109115)
46
47* Conversion to 2a will create a single pack for all the new revisions (as
48 long as it ran without interruption). This improves both ``bzr upgrade``
49 and ``bzr pull`` or ``bzr merge`` from local branches in older formats.
50 The autopack logic that occurs every 100 revisions during local
51 conversions was not returning that pack's identifier, which resulted in
52 the partial packs created during the conversion not being consolidated
53 at the end of the conversion process. (Robert Collins, #423818)
54
55* Don't restrict the command name used to run the test suite.
56 (Vincent Ladeuil, #419950)
57
58* Fetches from 2a to 2a are now again requested in 'groupcompress' order.
59 Groups that are seen as 'underutilized' will be repacked on-the-fly.
60 This means that when the source is fully packed, there is minimal
61 overhead during the fetch, but if the source is poorly packed the result
62 is a fairly well packed repository (not as good as 'bzr pack' but
63 good-enough.) (Robert Collins, John Arbash Meinel, #402652)
64
65* Network streams now decode adjacent records of the same type into a
66 single stream, reducing layering churn. (Robert Collins)
67
68* Prevent some kinds of incomplete data from being committed to a 2a
69 repository, such as revisions without inventories or inventories without
70 chk_bytes root records.
71 (Andrew Bennetts, #423506)
72
73* Make sure that we unlock the tree if we fail to create a TreeTransform
74 object when doing a merge, and there is limbo, or pending-deletions
75 directory. (Gary van der Merwe, #427773)
76
77Improvements
78************
79
80Documentation
81*************
82
83* Help on hooks no longer says 'Not deprecated' for hooks that are
84 currently supported. (Ian Clatworthy, #422415)
85
86API Changes
87***********
88
89* ``bzrlib.tests`` now uses ``stopTestRun`` for its ``TestResult``
90 subclasses - the same as python's unittest module. (Robert Collins)
91
92Internals
93*********
94
95* ``BTreeLeafParser.extract_key`` has been tweaked slightly to reduce
96 mallocs while parsing the index (approx 3=>1 mallocs per key read).
97 This results in a 10% speedup while reading an index.
98 (John Arbash Meinel)
99
100* The ``bzrlib.lsprof`` module has a new class ``BzrProfiler`` which makes
101 profiling in some situations like callbacks and generators easier.
102 (Robert Collins)
103
104Testing
105*******
106
107* Passing ``--lsprof-tests -v`` to bzr selftest will cause lsprof output to
108 be output for every test. Note that this is very verbose! (Robert Collins)
109
110* Test parameterisation now does a shallow copy, not a deep copy of the test
111 to be parameterised. This is not expected to break external use of test
112 parameterisation, and is substantially faster. (Robert Collins)
113
114
115bzr 2.0rc2 (not released yet)
116#############################
117
118New Features
119************
120
121* Added post_commit hook for mutable trees. This allows the keywords
122 plugin to expand keywords on files changed by the commit.
123 (Ian Clatworthy, #408841)
124
125Bug Fixes
126*********
127
128* Bazaar's native protocol code now correctly handles EINTR, which most
129 noticeably occurs if you break in to the debugger while connected to a
130 bzr+ssh server. You can now can continue from the debugger (by typing
131 'c') and the process continues. However, note that pressing C-\ in the
132 shell may still kill the SSH process, which is bug 162509, so you must
133 sent a signal to the bzr process specifically, for example by typing
134 ``kill -QUIT PID`` in another shell. (Martin Pool, #341535)
135
136* ``bzr check`` in pack-0.92, 1.6 and 1.9 format repositories will no
137 longer report incorrect errors about ``Missing inventory ('TREE_ROOT', ...)``
138 (Robert Collins, #416732)
139
140* ``bzr info -v`` on a 2a format still claimed that it was a "Development
141 format" (John Arbash Meinel, #424392)
142
143* ``bzr log stacked-branch`` shows the full log including
144 revisions that are in the fallback repository. (Regressed in 2.0rc1).
145 (John Arbash Meinel, #419241)
146
147* Clearer message when Bazaar runs out of memory, instead of a ``MemoryError``
148 traceback. (Martin Pool, #109115)
149
150* Conversion to 2a will create a single pack for all the new revisions (as
151 long as it ran without interruption). This improves both ``bzr upgrade``
152 and ``bzr pull`` or ``bzr merge`` from local branches in older formats.
153 The autopack logic that occurs every 100 revisions during local
154 conversions was not returning that pack's identifier, which resulted in
155 the partial packs created during the conversion not being consolidated
156 at the end of the conversion process. (Robert Collins, #423818)
157
158* Fetches from 2a to 2a are now again requested in 'groupcompress' order.
159 Groups that are seen as 'underutilized' will be repacked on-the-fly.
160 This means that when the source is fully packed, there is minimal
161 overhead during the fetch, but if the source is poorly packed the result
162 is a fairly well packed repository (not as good as 'bzr pack' but
163 good-enough.) (Robert Collins, John Arbash Meinel, #402652)
164
165* Fix a potential segmentation fault when doing 'log' of a branch that had
166 ghosts in its mainline. (Evaluating None as a tuple is bad.)
167 (John Arbash Meinel, #419241)
168
169* ``groupcompress`` sort order is now more stable, rather than relying on
170 ``topo_sort`` ordering. The implementation is now
171 ``KnownGraph.gc_sort``. (John Arbash Meinel)
172
173* Local data conversion will generate correct deltas. This is a critical
174 bugfix vs 2.0rc1, and all 2.0rc1 users should upgrade to 2.0rc2 before
175 converting repositories. (Robert Collins, #422849)
176
177* Network streams now decode adjacent records of the same type into a
178 single stream, reducing layering churn. (Robert Collins)
179
180* Prevent some kinds of incomplete data from being committed to a 2a
181 repository, such as revisions without inventories, a missing chk_bytes
182 record for an inventory, or a missing text referenced by an inventory.
183 (Andrew Bennetts, #423506, #406687)
184
185Documentation
186*************
187
188* Fix assertion error about "_remember_remote_is_before" when pushing to
189 older smart servers.
190 (Andrew Bennetts, #418931)
191
192* Help on hooks no longer says 'Not deprecated' for hooks that are
193 currently supported. (Ian Clatworthy, #422415)
194
195* The main table of contents now provides links to the new Migration Docs
196 and Plugins Guide. (Ian Clatworthy)
197
198
199bzr 2.0rc1
200##########
201
202
203:Codename: no worries
204:2.0rc1: 2009-08-26
205
206This release of Bazaar makes 2a 'brisbane-core' format the
207default. Most of the work in this release now focuses on bug
208fixes and stabilization, covering both 2a and previous formats.
209
210The Bazaar team decided that 2.0 will be a long-term supported
211release, with bugfix-only releases based on it continuing for at
212least six months or until the following stable release (we said
213that previously, but that's worth repeating).
214
215Compatibility Breaks
216********************
217
218* The default format for bzr is now ``2a``. This format brings many
219 significant performance and size improvements. bzr can pull from
220 any existing repository into a ``2a`` one, but can only transfer
221 into ``rich-root`` repositories from ``2a``. The Upgrade guide
222 has more information about this change. (Robert Collins)
223
224* On Windows auto-detection of Putty's plink.exe is disabled.
225 Default SSH client for Windows is paramiko. User still can force
226 usage of plink if explicitly set environment variable BZR_SSH=plink.
227 (#414743, Alexander Belchenko)
228
229New Features
230************
231
232* ``bzr branch --switch`` can now switch the checkout in the current directory
233 to the newly created branch. (Lukáš Lalinský)
234
235Bug Fixes
236*********
237
238* Further tweaks to handling of ``bzr add`` messages about ignored files.
239 (Jason Spashett, #76616)
240
241* Fetches were being requested in 'groupcompress' order, but weren't
242 recombining the groups. Thus they would 'fragment' to get the correct
243 order, but not 'recombine' to actually benefit from it. Until we get
244 recombining to work, switching to 'unordered' fetches avoids the
245 fragmentation. (John Arbash Meinel, #402645)
246
247* Fix a pycurl related test failure on karmic by recognizing an error
248 raised by newer versions of pycurl.
249 (Vincent Ladeuil, #306264)
250
251* Fix a test failure on karmic by making a locale test more robust.
252 (Vincent Ladeuil, #413514)
253
254* Fix IndexError printing CannotBindAddress errors.
255 (Martin Pool, #286871)
256
257* Fix "Revision ... not present" errors when upgrading stacked branches,
258 or when doing fetches from a stacked source to a stacked target.
259 (Andrew Bennetts, #399140)
260
261* ``bzr branch`` of 2a repositories over HTTP is much faster. bzr now
262 batches together small fetches from 2a repositories, rather than
263 fetching only a few hundred bytes at a time.
264 (Andrew Bennetts, #402657)
265
266Improvements
267************
268
269* A better description of the platform is shown in crash tracebacks, ``bzr
270 --version`` and ``bzr selftest``.
271 (Martin Pool, #409137)
272
273* bzr can now (again) capture crash data through the apport library,
274 so that a single human-readable file can be attached to bug reports.
275 This can be disabled by using ``-Dno_apport`` on the command line, or by
276 putting ``no_apport`` into the ``debug_flags`` section of
277 ``bazaar.conf``.
278 (Martin Pool, Robert Collins, #389328)
279
280* ``bzr push`` locally on windows will no longer give a locking error with
281 dirstate based formats. (Robert Collins)
282
283* ``bzr shelve`` and ``bzr unshelve`` now work on windows.
284 (Robert Collins, #305006)
285
286* Commit of specific files no longer prevents using the the iter_changes
287 codepath. On 2a repositories, commit of specific files should now be as
288 fast, or slightly faster, than a full commit. (Robert Collins)
289
290* The internal core code that handles specific file operations like
291 ``bzr st FILENAME`` or ``bzr commit FILENAME`` has been changed to
292 include the parent directories if they have altered, and when a
293 directory stops being a directory its children are always included. This
294 fixes a number of causes for ``InconsistentDelta`` errors, and permits
295 faster commit of specific paths. (Robert Collins, #347649)
296
297Documentation
298*************
299
300* New developer documentation for content filtering.
301 (Martin Pool)
302
303API Changes
304***********
305
306* ``bzrlib.shelf_ui`` has had the ``from_args`` convenience methods of its
307 classes changed to manage lock lifetime of the trees they open in a way
308 consistent with reader-exclusive locks. (Robert Collins, #305006)
309
310Testing
311*******
9312
10bzr 1.18.1 NOT RELEASED YET313bzr 1.18.1 NOT RELEASED YET
11###########################314###########################
@@ -17,17 +320,144 @@
17 conversion will commit too many copies a file.320 conversion will commit too many copies a file.
18 (Martin Pool, #415508)321 (Martin Pool, #415508)
19322
323Improvements
324************
325
326* ``bzr push`` locally on windows will no longer give a locking error with
327 dirstate based formats. (Robert Collins)
328
329* ``bzr shelve`` and ``bzr unshelve`` now work on windows.
330 (Robert Collins, #305006)
331
20API Changes332API Changes
21***********333***********
22334
335* ``bzrlib.shelf_ui`` has had the ``from_args`` convenience methods of its
336 classes changed to manage lock lifetime of the trees they open in a way
337 consistent with reader-exclusive locks. (Robert Collins, #305006)
338
23* ``Tree.path_content_summary`` may return a size of None, when called on339* ``Tree.path_content_summary`` may return a size of None, when called on
24 a tree with content filtering where the size of the canonical form340 a tree with content filtering where the size of the canonical form
25 cannot be cheaply determined. (Martin Pool)341 cannot be cheaply determined. (Martin Pool)
26342
343* When manually creating transport servers in test cases, a new helper
344 ``TestCase.start_server`` that registers a cleanup and starts the server
345 should be used. (Robert Collins)
27346
28bzr 1.18347bzr 1.18
29########348########
30349
350Compatibility Breaks
351********************
352
353* Committing directly to a stacked branch from a lightweight checkout will
354 no longer work. In previous versions this would appear to work but would
355 generate repositories with insufficient data to create deltas, leading
356 to later errors when branching or reading from the repository.
357 (Robert Collins, bug #375013)
358
359New Features
360************
361
362Bug Fixes
363*********
364
365* Fetching from 2a branches from a version-2 bzr protocol would fail to
366 copy the internal inventory pages from the CHK store. This cannot happen
367 in normal use as all 2a compatible clients and servers support the
368 version-3 protocol, but it does cause test suite failures when testing
369 downlevel protocol behaviour. (Robert Collins)
370
371* Fix a test failure on karmic by making a locale test more robust.
372 (Vincent Ladeuil, #413514)
373
374* Fixed "Pack ... already exists" error when running ``bzr pack`` on a
375 fully packed 2a repository. (Andrew Bennetts, #382463)
376
377* Further tweaks to handling of ``bzr add`` messages about ignored files.
378 (Jason Spashett, #76616)
379
380* Properly handle fetching into a stacked branch while converting the
381 data, especially when there are also ghosts. The code was filling in
382 parent inventories incorrectly, and also not handling when one of the
383 parents was a ghost. (John Arbash Meinel, #402778, #412198)
384
385* ``RemoteStreamSource.get_stream_for_missing_keys`` will fetch CHK
386 inventory pages when appropriate (by falling back to the vfs stream
387 source). (Andrew Bennetts, #406686)
388
389* StreamSource generates rich roots from non-rich root sources correctly
390 now. (Andrew Bennetts, #368921)
391
392* When deciding whether a repository was compatible for upgrading or
393 fetching, we previously incorrectly checked the default repository
394 format for the bzrdir format, rather than the format that was actually
395 present on disk. (Martin Pool, #408824)
396
397Improvements
398************
399
400* A better description of the platform is shown in crash tracebacks, ``bzr
401 --version`` and ``bzr selftest``.
402 (Martin Pool, #409137)
403
404* Cross-format fetches (such as between 1.9-rich-root and 2a) via the
405 smart server are more efficient now. They send inventory deltas rather
406 than full inventories. The smart server has two new requests,
407 ``Repository.get_stream_1.19`` and ``Repository.insert_stream_1.19`` to
408 support this. (Andrew Bennetts, #374738, #385826)
409
410* Extracting the full ancestry and computing the ``merge_sort`` is now
411 significantly faster. This effects things like ``bzr log -n0``. (For
412 example, ``bzr log -r -10..-1 -n0 bzr.dev`` is 2.5s down to 1.0s.
413 (John Arbash Meinel)
414
415Documentation
416*************
417
418API Changes
419***********
420
421Internals
422*********
423
424* ``-Dstrict_locks`` can now be used to check that read and write locks
425 are treated properly w.r.t. exclusivity. (We don't try to take an OS
426 read lock on a file that we already have an OS write lock on.) This is
427 now set by default for all tests, if you have a test which cannot be
428 fixed, you can use ``self.thisFailsStrictLockCheck()`` as a
429 compatibility knob. (John Arbash Meinel)
430
431* InterDifferingSerializer is now only used locally. Other fetches that
432 would have used InterDifferingSerializer now use the more network
433 friendly StreamSource, which now automatically does the same
434 transformations as InterDifferingSerializer. (Andrew Bennetts)
435
436* ``KnownGraph`` now has a ``.topo_sort`` and ``.merge_sort`` member which
437 are implemented in pyrex and significantly faster. This is exposed along
438 with ``CombinedGraphIndex.find_ancestry()`` as
439 ``VersionedFiles.get_known_graph_ancestry(keys)``.
440 (John Arbash Meinel)
441
442* RemoteBranch.open now honours ignore_fallbacks correctly on bzr-v2
443 protocols. (Robert Collins)
444
445* The index code now has some specialized routines to extract the full
446 ancestry of a key in a more efficient manner.
447 ``CombinedGraphIndex.find_ancestry()``. (Time to get ancestry for
448 bzr.dev drops from 1.5s down to 300ms. For OOo from 33s => 10.5s) (John
449 Arbash Meinel)
450
451Testing
452*******
453
454* Install the test ssl certificate and key so that installed bzr
455 can run the https tests. (Denys Duchier, #392401)
456
457
458bzr 1.18rc1
459###########
460
31:Codename: little traveller461:Codename: little traveller
32:1.18: 2009-08-20462:1.18: 2009-08-20
33:1.18rc1: 2009-08-10463:1.18rc1: 2009-08-10
@@ -113,6 +543,9 @@
113 running send and similar commands on 2a formats.543 running send and similar commands on 2a formats.
114 (Martin Pool, #408201)544 (Martin Pool, #408201)
115 545
546* Fix crash in some invocations of ``bzr status`` in format 2a.
547 (Martin Pool, #403523)
548
116* Fixed export to existing directory: if directory is empty then export 549* Fixed export to existing directory: if directory is empty then export
117 will succeed, otherwise it fails with error.550 will succeed, otherwise it fails with error.
118 (Alexander Belchenko, #406174)551 (Alexander Belchenko, #406174)
@@ -133,7 +566,9 @@
133* Requests for unknown methods no longer cause the smart server to log566* Requests for unknown methods no longer cause the smart server to log
134 lots of backtraces about ``UnknownSmartMethod``, ``do_chunk`` or567 lots of backtraces about ``UnknownSmartMethod``, ``do_chunk`` or
135 ``do_end``. (Andrew Bennetts, #338561)568 ``do_end``. (Andrew Bennetts, #338561)
136 569
570* Shelve will not shelve the initial add of the tree root. (Aaron Bentley)
571
137* Streaming from bzr servers where there is a chain of stacked branches572* Streaming from bzr servers where there is a chain of stacked branches
138 (A stacked on B stacked on C) will now work. (Robert Collins, #406597)573 (A stacked on B stacked on C) will now work. (Robert Collins, #406597)
139574
@@ -245,6 +680,17 @@
245 ``countTestsCases``. (Robert Collins)680 ``countTestsCases``. (Robert Collins)
246681
247682
683bzr 1.17.1 (unreleased)
684#######################
685
686Bug Fixes
687*********
688
689* The optional ``_knit_load_data_pyx`` C extension was never being
690 imported. This caused significant slowdowns when reading data from
691 knit format repositories. (Andrew Bennetts, #405653)
692
693
248bzr 1.17 "So late it's brunch" 2009-07-20694bzr 1.17 "So late it's brunch" 2009-07-20
249#########################################695#########################################
250:Codename: so-late-its-brunch696:Codename: so-late-its-brunch
@@ -743,6 +1189,9 @@
743Testing1189Testing
744*******1190*******
7451191
1192* ``make check`` no longer repeats the test run in ``LANG=C``.
1193 (Martin Pool, #386180)
1194
746* The number of cores is now correctly detected on OSX. (John Szakmeister)1195* The number of cores is now correctly detected on OSX. (John Szakmeister)
7471196
748* The number of cores is also detected on Solaris and win32. (Vincent Ladeuil)1197* The number of cores is also detected on Solaris and win32. (Vincent Ladeuil)
@@ -4723,7 +5172,7 @@
4723 checkouts. (Aaron Bentley, #182040)5172 checkouts. (Aaron Bentley, #182040)
47245173
4725* Stop polluting /tmp when running selftest.5174* Stop polluting /tmp when running selftest.
4726 (Vincent Ladeuil, #123623)5175 (Vincent Ladeuil, #123363)
47275176
4728* Switch from NFKC => NFC for normalization checks. NFC allows a few5177* Switch from NFKC => NFC for normalization checks. NFC allows a few
4729 more characters which should be considered valid.5178 more characters which should be considered valid.
47305179
=== modified file 'bzr'
--- bzr 2009-07-30 23:54:26 +0000
+++ bzr 2009-08-28 05:11:10 +0000
@@ -23,7 +23,7 @@
23import warnings23import warnings
2424
25# update this on each release25# update this on each release
26_script_version = (1, 18, 0)26_script_version = (2, 1, 0)
2727
28if __doc__ is None:28if __doc__ is None:
29 print "bzr does not support python -OO."29 print "bzr does not support python -OO."
3030
=== modified file 'bzrlib/__init__.py'
--- bzrlib/__init__.py 2009-08-20 08:38:09 +0000
+++ bzrlib/__init__.py 2009-08-30 21:34:42 +0000
@@ -50,7 +50,7 @@
50# Python version 2.0 is (2, 0, 0, 'final', 0)." Additionally we use a50# Python version 2.0 is (2, 0, 0, 'final', 0)." Additionally we use a
51# releaselevel of 'dev' for unreleased under-development code.51# releaselevel of 'dev' for unreleased under-development code.
5252
53version_info = (1, 18, 0, 'final', 0)53version_info = (2, 1, 0, 'dev', 0)
5454
55# API compatibility version: bzrlib is currently API compatible with 1.15.55# API compatibility version: bzrlib is currently API compatible with 1.15.
56api_minimum_version = (1, 17, 0)56api_minimum_version = (1, 17, 0)
@@ -70,6 +70,8 @@
70 1.2dev70 1.2dev
71 >>> print _format_version_tuple((1, 1, 1, 'candidate', 2))71 >>> print _format_version_tuple((1, 1, 1, 'candidate', 2))
72 1.1.1rc272 1.1.1rc2
73 >>> print bzrlib._format_version_tuple((2, 1, 0, 'beta', 1))
74 2.1b1
73 >>> print _format_version_tuple((1, 4, 0))75 >>> print _format_version_tuple((1, 4, 0))
74 1.476 1.4
75 >>> print _format_version_tuple((1, 4))77 >>> print _format_version_tuple((1, 4))
7678
=== modified file 'bzrlib/_btree_serializer_pyx.pyx'
--- bzrlib/_btree_serializer_pyx.pyx 2009-06-22 12:52:39 +0000
+++ bzrlib/_btree_serializer_pyx.pyx 2009-09-04 21:16:14 +0000
@@ -1,4 +1,4 @@
1# Copyright (C) 2008 Canonical Ltd1# Copyright (C) 2008, 2009 Canonical Ltd
2#2#
3# This program is free software; you can redistribute it and/or modify3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by4# it under the terms of the GNU General Public License as published by
@@ -41,8 +41,11 @@
41 int PyString_AsStringAndSize_ptr(PyObject *, char **buf, Py_ssize_t *len)41 int PyString_AsStringAndSize_ptr(PyObject *, char **buf, Py_ssize_t *len)
42 void PyString_InternInPlace(PyObject **)42 void PyString_InternInPlace(PyObject **)
43 int PyTuple_CheckExact(object t)43 int PyTuple_CheckExact(object t)
44 object PyTuple_New(Py_ssize_t n_entries)
45 void PyTuple_SET_ITEM(object, Py_ssize_t offset, object) # steals the ref
44 Py_ssize_t PyTuple_GET_SIZE(object t)46 Py_ssize_t PyTuple_GET_SIZE(object t)
45 PyObject *PyTuple_GET_ITEM_ptr_object "PyTuple_GET_ITEM" (object tpl, int index)47 PyObject *PyTuple_GET_ITEM_ptr_object "PyTuple_GET_ITEM" (object tpl, int index)
48 void Py_INCREF(object)
46 void Py_DECREF_ptr "Py_DECREF" (PyObject *)49 void Py_DECREF_ptr "Py_DECREF" (PyObject *)
4750
48cdef extern from "string.h":51cdef extern from "string.h":
@@ -140,14 +143,12 @@
140 cdef char *temp_ptr143 cdef char *temp_ptr
141 cdef int loop_counter144 cdef int loop_counter
142 # keys are tuples145 # keys are tuples
143 loop_counter = 0146 key = PyTuple_New(self.key_length)
144 key_segments = []147 for loop_counter from 0 <= loop_counter < self.key_length:
145 while loop_counter < self.key_length:
146 loop_counter = loop_counter + 1
147 # grab a key segment148 # grab a key segment
148 temp_ptr = <char*>memchr(self._start, c'\0', last - self._start)149 temp_ptr = <char*>memchr(self._start, c'\0', last - self._start)
149 if temp_ptr == NULL:150 if temp_ptr == NULL:
150 if loop_counter == self.key_length:151 if loop_counter + 1 == self.key_length:
151 # capture to last152 # capture to last
152 temp_ptr = last153 temp_ptr = last
153 else:154 else:
@@ -164,8 +165,9 @@
164 temp_ptr - self._start)165 temp_ptr - self._start)
165 # advance our pointer166 # advance our pointer
166 self._start = temp_ptr + 1167 self._start = temp_ptr + 1
167 PyList_Append(key_segments, key_element)168 Py_INCREF(key_element)
168 return tuple(key_segments)169 PyTuple_SET_ITEM(key, loop_counter, key_element)
170 return key
169171
170 cdef int process_line(self) except -1:172 cdef int process_line(self) except -1:
171 """Process a line in the bytes."""173 """Process a line in the bytes."""
172174
=== modified file 'bzrlib/_dirstate_helpers_pyx.pyx'
--- bzrlib/_dirstate_helpers_pyx.pyx 2009-07-27 05:44:19 +0000
+++ bzrlib/_dirstate_helpers_pyx.pyx 2009-08-28 05:00:33 +0000
@@ -28,7 +28,7 @@
2828
29from bzrlib import cache_utf8, errors, osutils29from bzrlib import cache_utf8, errors, osutils
30from bzrlib.dirstate import DirState30from bzrlib.dirstate import DirState
31from bzrlib.osutils import pathjoin, splitpath31from bzrlib.osutils import parent_directories, pathjoin, splitpath
3232
3333
34# This is the Windows equivalent of ENOTDIR34# This is the Windows equivalent of ENOTDIR
@@ -963,15 +963,21 @@
963963
964cdef class ProcessEntryC:964cdef class ProcessEntryC:
965965
966 cdef int doing_consistency_expansion
966 cdef object old_dirname_to_file_id # dict967 cdef object old_dirname_to_file_id # dict
967 cdef object new_dirname_to_file_id # dict968 cdef object new_dirname_to_file_id # dict
968 cdef object last_source_parent969 cdef object last_source_parent
969 cdef object last_target_parent970 cdef object last_target_parent
970 cdef object include_unchanged971 cdef int include_unchanged
972 cdef int partial
971 cdef object use_filesystem_for_exec973 cdef object use_filesystem_for_exec
972 cdef object utf8_decode974 cdef object utf8_decode
973 cdef readonly object searched_specific_files975 cdef readonly object searched_specific_files
976 cdef readonly object searched_exact_paths
974 cdef object search_specific_files977 cdef object search_specific_files
978 # The parents up to the root of the paths we are searching.
979 # After all normal paths are returned, these specific items are returned.
980 cdef object search_specific_file_parents
975 cdef object state981 cdef object state
976 # Current iteration variables:982 # Current iteration variables:
977 cdef object current_root983 cdef object current_root
@@ -989,31 +995,48 @@
989 cdef object current_block_list995 cdef object current_block_list
990 cdef object current_dir_info996 cdef object current_dir_info
991 cdef object current_dir_list997 cdef object current_dir_list
998 cdef object _pending_consistent_entries # list
992 cdef int path_index999 cdef int path_index
993 cdef object root_dir_info1000 cdef object root_dir_info
994 cdef object bisect_left1001 cdef object bisect_left
995 cdef object pathjoin1002 cdef object pathjoin
996 cdef object fstat1003 cdef object fstat
1004 # A set of the ids we've output when doing partial output.
1005 cdef object seen_ids
997 cdef object sha_file1006 cdef object sha_file
9981007
999 def __init__(self, include_unchanged, use_filesystem_for_exec,1008 def __init__(self, include_unchanged, use_filesystem_for_exec,
1000 search_specific_files, state, source_index, target_index,1009 search_specific_files, state, source_index, target_index,
1001 want_unversioned, tree):1010 want_unversioned, tree):
1011 self.doing_consistency_expansion = 0
1002 self.old_dirname_to_file_id = {}1012 self.old_dirname_to_file_id = {}
1003 self.new_dirname_to_file_id = {}1013 self.new_dirname_to_file_id = {}
1014 # Are we doing a partial iter_changes?
1015 self.partial = set(['']).__ne__(search_specific_files)
1004 # Using a list so that we can access the values and change them in1016 # Using a list so that we can access the values and change them in
1005 # nested scope. Each one is [path, file_id, entry]1017 # nested scope. Each one is [path, file_id, entry]
1006 self.last_source_parent = [None, None]1018 self.last_source_parent = [None, None]
1007 self.last_target_parent = [None, None]1019 self.last_target_parent = [None, None]
1008 self.include_unchanged = include_unchanged1020 if include_unchanged is None:
1021 self.include_unchanged = False
1022 else:
1023 self.include_unchanged = int(include_unchanged)
1009 self.use_filesystem_for_exec = use_filesystem_for_exec1024 self.use_filesystem_for_exec = use_filesystem_for_exec
1010 self.utf8_decode = cache_utf8._utf8_decode1025 self.utf8_decode = cache_utf8._utf8_decode
1011 # for all search_indexs in each path at or under each element of1026 # for all search_indexs in each path at or under each element of
1012 # search_specific_files, if the detail is relocated: add the id, and add the1027 # search_specific_files, if the detail is relocated: add the id, and
1013 # relocated path as one to search if its not searched already. If the1028 # add the relocated path as one to search if its not searched already.
1014 # detail is not relocated, add the id.1029 # If the detail is not relocated, add the id.
1015 self.searched_specific_files = set()1030 self.searched_specific_files = set()
1031 # When we search exact paths without expanding downwards, we record
1032 # that here.
1033 self.searched_exact_paths = set()
1016 self.search_specific_files = search_specific_files1034 self.search_specific_files = search_specific_files
1035 # The parents up to the root of the paths we are searching.
1036 # After all normal paths are returned, these specific items are returned.
1037 self.search_specific_file_parents = set()
1038 # The ids we've sent out in the delta.
1039 self.seen_ids = set()
1017 self.state = state1040 self.state = state
1018 self.current_root = None1041 self.current_root = None
1019 self.current_root_unicode = None1042 self.current_root_unicode = None
@@ -1035,26 +1058,30 @@
1035 self.current_block_pos = -11058 self.current_block_pos = -1
1036 self.current_dir_info = None1059 self.current_dir_info = None
1037 self.current_dir_list = None1060 self.current_dir_list = None
1061 self._pending_consistent_entries = []
1038 self.path_index = 01062 self.path_index = 0
1039 self.root_dir_info = None1063 self.root_dir_info = None
1040 self.bisect_left = bisect.bisect_left1064 self.bisect_left = bisect.bisect_left
1041 self.pathjoin = osutils.pathjoin1065 self.pathjoin = osutils.pathjoin
1042 self.fstat = os.fstat1066 self.fstat = os.fstat
1043 self.sha_file = osutils.sha_file1067 self.sha_file = osutils.sha_file
1068 if target_index != 0:
1069 # A lot of code in here depends on target_index == 0
1070 raise errors.BzrError('unsupported target index')
10441071
1045 cdef _process_entry(self, entry, path_info):1072 cdef _process_entry(self, entry, path_info):
1046 """Compare an entry and real disk to generate delta information.1073 """Compare an entry and real disk to generate delta information.
10471074
1048 :param path_info: top_relpath, basename, kind, lstat, abspath for1075 :param path_info: top_relpath, basename, kind, lstat, abspath for
1049 the path of entry. If None, then the path is considered absent.1076 the path of entry. If None, then the path is considered absent in
1050 (Perhaps we should pass in a concrete entry for this ?)1077 the target (Perhaps we should pass in a concrete entry for this ?)
1051 Basename is returned as a utf8 string because we expect this1078 Basename is returned as a utf8 string because we expect this
1052 tuple will be ignored, and don't want to take the time to1079 tuple will be ignored, and don't want to take the time to
1053 decode.1080 decode.
1054 :return: (iter_changes_result, changed). If the entry has not been1081 :return: (iter_changes_result, changed). If the entry has not been
1055 handled then changed is None. Otherwise it is False if no content1082 handled then changed is None. Otherwise it is False if no content
1056 or metadata changes have occured, and None if any content or1083 or metadata changes have occured, and True if any content or
1057 metadata change has occured. If self.include_unchanged is True then1084 metadata change has occurred. If self.include_unchanged is True then
1058 if changed is not None, iter_changes_result will always be a result1085 if changed is not None, iter_changes_result will always be a result
1059 tuple. Otherwise, iter_changes_result is None unless changed is1086 tuple. Otherwise, iter_changes_result is None unless changed is
1060 True.1087 True.
@@ -1099,9 +1126,12 @@
1099 else:1126 else:
1100 # add the source to the search path to find any children it1127 # add the source to the search path to find any children it
1101 # has. TODO ? : only add if it is a container ?1128 # has. TODO ? : only add if it is a container ?
1102 if not osutils.is_inside_any(self.searched_specific_files,1129 if (not self.doing_consistency_expansion and
1103 source_details[1]):1130 not osutils.is_inside_any(self.searched_specific_files,
1131 source_details[1])):
1104 self.search_specific_files.add(source_details[1])1132 self.search_specific_files.add(source_details[1])
1133 # expanding from a user requested path, parent expansion
1134 # for delta consistency happens later.
1105 # generate the old path; this is needed for stating later1135 # generate the old path; this is needed for stating later
1106 # as well.1136 # as well.
1107 old_path = source_details[1]1137 old_path = source_details[1]
@@ -1180,7 +1210,8 @@
1180 file_id = entry[0][2]1210 file_id = entry[0][2]
1181 self.old_dirname_to_file_id[old_path] = file_id1211 self.old_dirname_to_file_id[old_path] = file_id
1182 # parent id is the entry for the path in the target tree1212 # parent id is the entry for the path in the target tree
1183 if old_dirname == self.last_source_parent[0]:1213 if old_basename and old_dirname == self.last_source_parent[0]:
1214 # use a cached hit for non-root source entries.
1184 source_parent_id = self.last_source_parent[1]1215 source_parent_id = self.last_source_parent[1]
1185 else:1216 else:
1186 try:1217 try:
@@ -1196,7 +1227,8 @@
1196 self.last_source_parent[0] = old_dirname1227 self.last_source_parent[0] = old_dirname
1197 self.last_source_parent[1] = source_parent_id1228 self.last_source_parent[1] = source_parent_id
1198 new_dirname = entry[0][0]1229 new_dirname = entry[0][0]
1199 if new_dirname == self.last_target_parent[0]:1230 if entry[0][1] and new_dirname == self.last_target_parent[0]:
1231 # use a cached hit for non-root target entries.
1200 target_parent_id = self.last_target_parent[1]1232 target_parent_id = self.last_target_parent[1]
1201 else:1233 else:
1202 try:1234 try:
@@ -1313,8 +1345,13 @@
1313 # a renamed parent. TODO: handle this efficiently. Its not1345 # a renamed parent. TODO: handle this efficiently. Its not
1314 # common case to rename dirs though, so a correct but slow1346 # common case to rename dirs though, so a correct but slow
1315 # implementation will do.1347 # implementation will do.
1316 if not osutils.is_inside_any(self.searched_specific_files, target_details[1]):1348 if (not self.doing_consistency_expansion and
1349 not osutils.is_inside_any(self.searched_specific_files,
1350 target_details[1])):
1317 self.search_specific_files.add(target_details[1])1351 self.search_specific_files.add(target_details[1])
1352 # We don't expand the specific files parents list here as
1353 # the path is absent in target and won't create a delta with
1354 # missing parent.
1318 elif ((source_minikind == c'r' or source_minikind == c'a') and1355 elif ((source_minikind == c'r' or source_minikind == c'a') and
1319 (target_minikind == c'r' or target_minikind == c'a')):1356 (target_minikind == c'r' or target_minikind == c'a')):
1320 # neither of the selected trees contain this path,1357 # neither of the selected trees contain this path,
@@ -1334,6 +1371,25 @@
1334 def iter_changes(self):1371 def iter_changes(self):
1335 return self1372 return self
13361373
1374 cdef void _gather_result_for_consistency(self, result):
1375 """Check a result we will yield to make sure we are consistent later.
1376
1377 This gathers result's parents into a set to output later.
1378
1379 :param result: A result tuple.
1380 """
1381 if not self.partial or not result[0]:
1382 return
1383 self.seen_ids.add(result[0])
1384 new_path = result[1][1]
1385 if new_path:
1386 # Not the root and not a delete: queue up the parents of the path.
1387 self.search_specific_file_parents.update(
1388 osutils.parent_directories(new_path.encode('utf8')))
1389 # Add the root directory which parent_directories does not
1390 # provide.
1391 self.search_specific_file_parents.add('')
1392
1337 cdef void _update_current_block(self):1393 cdef void _update_current_block(self):
1338 if (self.block_index < len(self.state._dirblocks) and1394 if (self.block_index < len(self.state._dirblocks) and
1339 osutils.is_inside(self.current_root, self.state._dirblocks[self.block_index][0])):1395 osutils.is_inside(self.current_root, self.state._dirblocks[self.block_index][0])):
@@ -1406,8 +1462,11 @@
1406 entry = self.root_entries[self.root_entries_pos]1462 entry = self.root_entries[self.root_entries_pos]
1407 self.root_entries_pos = self.root_entries_pos + 11463 self.root_entries_pos = self.root_entries_pos + 1
1408 result, changed = self._process_entry(entry, self.root_dir_info)1464 result, changed = self._process_entry(entry, self.root_dir_info)
1409 if changed is not None and changed or self.include_unchanged:1465 if changed is not None:
1410 return result1466 if changed:
1467 self._gather_result_for_consistency(result)
1468 if changed or self.include_unchanged:
1469 return result
1411 # Have we finished the prior root, or never started one ?1470 # Have we finished the prior root, or never started one ?
1412 if self.current_root is None:1471 if self.current_root is None:
1413 # TODO: the pending list should be lexically sorted? the1472 # TODO: the pending list should be lexically sorted? the
@@ -1416,12 +1475,12 @@
1416 self.current_root = self.search_specific_files.pop()1475 self.current_root = self.search_specific_files.pop()
1417 except KeyError:1476 except KeyError:
1418 raise StopIteration()1477 raise StopIteration()
1419 self.current_root_unicode = self.current_root.decode('utf8')
1420 self.searched_specific_files.add(self.current_root)1478 self.searched_specific_files.add(self.current_root)
1421 # process the entries for this containing directory: the rest will be1479 # process the entries for this containing directory: the rest will be
1422 # found by their parents recursively.1480 # found by their parents recursively.
1423 self.root_entries = self.state._entries_for_path(self.current_root)1481 self.root_entries = self.state._entries_for_path(self.current_root)
1424 self.root_entries_len = len(self.root_entries)1482 self.root_entries_len = len(self.root_entries)
1483 self.current_root_unicode = self.current_root.decode('utf8')
1425 self.root_abspath = self.tree.abspath(self.current_root_unicode)1484 self.root_abspath = self.tree.abspath(self.current_root_unicode)
1426 try:1485 try:
1427 root_stat = os.lstat(self.root_abspath)1486 root_stat = os.lstat(self.root_abspath)
@@ -1458,6 +1517,8 @@
1458 result, changed = self._process_entry(entry, self.root_dir_info)1517 result, changed = self._process_entry(entry, self.root_dir_info)
1459 if changed is not None:1518 if changed is not None:
1460 path_handled = -11519 path_handled = -1
1520 if changed:
1521 self._gather_result_for_consistency(result)
1461 if changed or self.include_unchanged:1522 if changed or self.include_unchanged:
1462 return result1523 return result
1463 # handle unversioned specified paths:1524 # handle unversioned specified paths:
@@ -1476,7 +1537,8 @@
1476 )1537 )
1477 # If we reach here, the outer flow continues, which enters into the1538 # If we reach here, the outer flow continues, which enters into the
1478 # per-root setup logic.1539 # per-root setup logic.
1479 if self.current_dir_info is None and self.current_block is None:1540 if (self.current_dir_info is None and self.current_block is None and not
1541 self.doing_consistency_expansion):
1480 # setup iteration of this root:1542 # setup iteration of this root:
1481 self.current_dir_list = None1543 self.current_dir_list = None
1482 if self.root_dir_info and self.root_dir_info[2] == 'tree-reference':1544 if self.root_dir_info and self.root_dir_info[2] == 'tree-reference':
@@ -1606,6 +1668,8 @@
1606 # advance the entry only, after processing.1668 # advance the entry only, after processing.
1607 result, changed = self._process_entry(current_entry, None)1669 result, changed = self._process_entry(current_entry, None)
1608 if changed is not None:1670 if changed is not None:
1671 if changed:
1672 self._gather_result_for_consistency(result)
1609 if changed or self.include_unchanged:1673 if changed or self.include_unchanged:
1610 return result1674 return result
1611 self.block_index = self.block_index + 11675 self.block_index = self.block_index + 1
@@ -1618,6 +1682,15 @@
1618 # More supplied paths to process1682 # More supplied paths to process
1619 self.current_root = None1683 self.current_root = None
1620 return self._iter_next()1684 return self._iter_next()
1685 # Start expanding more conservatively, adding paths the user may not
1686 # have intended but required for consistent deltas.
1687 self.doing_consistency_expansion = 1
1688 if not self._pending_consistent_entries:
1689 self._pending_consistent_entries = self._next_consistent_entries()
1690 while self._pending_consistent_entries:
1691 result, changed = self._pending_consistent_entries.pop()
1692 if changed is not None:
1693 return result
1621 raise StopIteration()1694 raise StopIteration()
16221695
1623 cdef object _maybe_tree_ref(self, current_path_info):1696 cdef object _maybe_tree_ref(self, current_path_info):
@@ -1705,6 +1778,8 @@
1705 current_path_info)1778 current_path_info)
1706 if changed is not None:1779 if changed is not None:
1707 path_handled = -11780 path_handled = -1
1781 if not changed and not self.include_unchanged:
1782 changed = None
1708 # >- loop control starts here:1783 # >- loop control starts here:
1709 # >- entry1784 # >- entry
1710 if advance_entry and current_entry is not None:1785 if advance_entry and current_entry is not None:
@@ -1726,7 +1801,7 @@
1726 except UnicodeDecodeError:1801 except UnicodeDecodeError:
1727 raise errors.BadFilenameEncoding(1802 raise errors.BadFilenameEncoding(
1728 current_path_info[0], osutils._fs_enc)1803 current_path_info[0], osutils._fs_enc)
1729 if result is not None:1804 if changed is not None:
1730 raise AssertionError(1805 raise AssertionError(
1731 "result is not None: %r" % result)1806 "result is not None: %r" % result)
1732 result = (None,1807 result = (None,
@@ -1737,6 +1812,7 @@
1737 (None, self.utf8_decode(current_path_info[1])[0]),1812 (None, self.utf8_decode(current_path_info[1])[0]),
1738 (None, current_path_info[2]),1813 (None, current_path_info[2]),
1739 (None, new_executable))1814 (None, new_executable))
1815 changed = True
1740 # dont descend into this unversioned path if it is1816 # dont descend into this unversioned path if it is
1741 # a dir1817 # a dir
1742 if current_path_info[2] in ('directory'):1818 if current_path_info[2] in ('directory'):
@@ -1755,9 +1831,12 @@
1755 current_path_info)1831 current_path_info)
1756 else:1832 else:
1757 current_path_info = None1833 current_path_info = None
1758 if result is not None:1834 if changed is not None:
1759 # Found a result on this pass, yield it1835 # Found a result on this pass, yield it
1760 return result1836 if changed:
1837 self._gather_result_for_consistency(result)
1838 if changed or self.include_unchanged:
1839 return result
1761 if self.current_block is not None:1840 if self.current_block is not None:
1762 self.block_index = self.block_index + 11841 self.block_index = self.block_index + 1
1763 self._update_current_block()1842 self._update_current_block()
@@ -1769,3 +1848,123 @@
1769 self.current_dir_list = self.current_dir_info[1]1848 self.current_dir_list = self.current_dir_info[1]
1770 except StopIteration:1849 except StopIteration:
1771 self.current_dir_info = None1850 self.current_dir_info = None
1851
1852 cdef object _next_consistent_entries(self):
1853 """Grabs the next specific file parent case to consider.
1854
1855 :return: A list of the results, each of which is as for _process_entry.
1856 """
1857 results = []
1858 while self.search_specific_file_parents:
1859 # Process the parent directories for the paths we were iterating.
1860 # Even in extremely large trees this should be modest, so currently
1861 # no attempt is made to optimise.
1862 path_utf8 = self.search_specific_file_parents.pop()
1863 if path_utf8 in self.searched_exact_paths:
1864 # We've examined this path.
1865 continue
1866 if osutils.is_inside_any(self.searched_specific_files, path_utf8):
1867 # We've examined this path.
1868 continue
1869 path_entries = self.state._entries_for_path(path_utf8)
1870 # We need either one or two entries. If the path in
1871 # self.target_index has moved (so the entry in source_index is in
1872 # 'ar') then we need to also look for the entry for this path in
1873 # self.source_index, to output the appropriate delete-or-rename.
1874 selected_entries = []
1875 found_item = False
1876 for candidate_entry in path_entries:
1877 # Find entries present in target at this path:
1878 if candidate_entry[1][self.target_index][0] not in 'ar':
1879 found_item = True
1880 selected_entries.append(candidate_entry)
1881 # Find entries present in source at this path:
1882 elif (self.source_index is not None and
1883 candidate_entry[1][self.source_index][0] not in 'ar'):
1884 found_item = True
1885 if candidate_entry[1][self.target_index][0] == 'a':
1886 # Deleted, emit it here.
1887 selected_entries.append(candidate_entry)
1888 else:
1889 # renamed, emit it when we process the directory it
1890 # ended up at.
1891 self.search_specific_file_parents.add(
1892 candidate_entry[1][self.target_index][1])
1893 if not found_item:
1894 raise AssertionError(
1895 "Missing entry for specific path parent %r, %r" % (
1896 path_utf8, path_entries))
1897 path_info = self._path_info(path_utf8, path_utf8.decode('utf8'))
1898 for entry in selected_entries:
1899 if entry[0][2] in self.seen_ids:
1900 continue
1901 result, changed = self._process_entry(entry, path_info)
1902 if changed is None:
1903 raise AssertionError(
1904 "Got entry<->path mismatch for specific path "
1905 "%r entry %r path_info %r " % (
1906 path_utf8, entry, path_info))
1907 # Only include changes - we're outside the users requested
1908 # expansion.
1909 if changed:
1910 self._gather_result_for_consistency(result)
1911 if (result[6][0] == 'directory' and
1912 result[6][1] != 'directory'):
1913 # This stopped being a directory, the old children have
1914 # to be included.
1915 if entry[1][self.source_index][0] == 'r':
1916 # renamed, take the source path
1917 entry_path_utf8 = entry[1][self.source_index][1]
1918 else:
1919 entry_path_utf8 = path_utf8
1920 initial_key = (entry_path_utf8, '', '')
1921 block_index, _ = self.state._find_block_index_from_key(
1922 initial_key)
1923 if block_index == 0:
1924 # The children of the root are in block index 1.
1925 block_index = block_index + 1
1926 current_block = None
1927 if block_index < len(self.state._dirblocks):
1928 current_block = self.state._dirblocks[block_index]
1929 if not osutils.is_inside(
1930 entry_path_utf8, current_block[0]):
1931 # No entries for this directory at all.
1932 current_block = None
1933 if current_block is not None:
1934 for entry in current_block[1]:
1935 if entry[1][self.source_index][0] in 'ar':
1936 # Not in the source tree, so doesn't have to be
1937 # included.
1938 continue
1939 # Path of the entry itself.
1940 self.search_specific_file_parents.add(
1941 self.pathjoin(*entry[0][:2]))
1942 if changed or self.include_unchanged:
1943 results.append((result, changed))
1944 self.searched_exact_paths.add(path_utf8)
1945 return results
1946
1947 cdef object _path_info(self, utf8_path, unicode_path):
1948 """Generate path_info for unicode_path.
1949
1950 :return: None if unicode_path does not exist, or a path_info tuple.
1951 """
1952 abspath = self.tree.abspath(unicode_path)
1953 try:
1954 stat = os.lstat(abspath)
1955 except OSError, e:
1956 if e.errno == errno.ENOENT:
1957 # the path does not exist.
1958 return None
1959 else:
1960 raise
1961 utf8_basename = utf8_path.rsplit('/', 1)[-1]
1962 dir_info = (utf8_path, utf8_basename,
1963 osutils.file_kind_from_stat_mode(stat.st_mode), stat,
1964 abspath)
1965 if dir_info[2] == 'directory':
1966 if self.tree._directory_is_tree_reference(
1967 unicode_path):
1968 self.root_dir_info = self.root_dir_info[:2] + \
1969 ('tree-reference',) + self.root_dir_info[3:]
1970 return dir_info
17721971
=== modified file 'bzrlib/_known_graph_py.py'
--- bzrlib/_known_graph_py.py 2009-07-08 20:58:10 +0000
+++ bzrlib/_known_graph_py.py 2009-09-07 14:19:05 +0000
@@ -18,6 +18,7 @@
18"""18"""
1919
20from bzrlib import (20from bzrlib import (
21 errors,
21 revision,22 revision,
22 )23 )
2324
@@ -40,6 +41,18 @@
40 self.parent_keys, self.child_keys)41 self.parent_keys, self.child_keys)
4142
4243
44class _MergeSortNode(object):
45 """Information about a specific node in the merge graph."""
46
47 __slots__ = ('key', 'merge_depth', 'revno', 'end_of_merge')
48
49 def __init__(self, key, merge_depth, revno, end_of_merge):
50 self.key = key
51 self.merge_depth = merge_depth
52 self.revno = revno
53 self.end_of_merge = end_of_merge
54
55
43class KnownGraph(object):56class KnownGraph(object):
44 """This is a class which assumes we already know the full graph."""57 """This is a class which assumes we already know the full graph."""
4558
@@ -84,6 +97,10 @@
84 return [node for node in self._nodes.itervalues()97 return [node for node in self._nodes.itervalues()
85 if not node.parent_keys]98 if not node.parent_keys]
8699
100 def _find_tips(self):
101 return [node for node in self._nodes.itervalues()
102 if not node.child_keys]
103
87 def _find_gdfo(self):104 def _find_gdfo(self):
88 nodes = self._nodes105 nodes = self._nodes
89 known_parent_gdfos = {}106 known_parent_gdfos = {}
@@ -171,3 +188,119 @@
171 self._known_heads[heads_key] = heads188 self._known_heads[heads_key] = heads
172 return heads189 return heads
173190
191 def topo_sort(self):
192 """Return the nodes in topological order.
193
194 All parents must occur before all children.
195 """
196 for node in self._nodes.itervalues():
197 if node.gdfo is None:
198 raise errors.GraphCycleError(self._nodes)
199 pending = self._find_tails()
200 pending_pop = pending.pop
201 pending_append = pending.append
202
203 topo_order = []
204 topo_order_append = topo_order.append
205
206 num_seen_parents = dict.fromkeys(self._nodes, 0)
207 while pending:
208 node = pending_pop()
209 if node.parent_keys is not None:
210 # We don't include ghost parents
211 topo_order_append(node.key)
212 for child_key in node.child_keys:
213 child_node = self._nodes[child_key]
214 seen_parents = num_seen_parents[child_key] + 1
215 if seen_parents == len(child_node.parent_keys):
216 # All parents have been processed, enqueue this child
217 pending_append(child_node)
218 # This has been queued up, stop tracking it
219 del num_seen_parents[child_key]
220 else:
221 num_seen_parents[child_key] = seen_parents
222 # We started from the parents, so we don't need to do anymore work
223 return topo_order
224
225 def gc_sort(self):
226 """Return a reverse topological ordering which is 'stable'.
227
228 There are a few constraints:
229 1) Reverse topological (all children before all parents)
230 2) Grouped by prefix
231 3) 'stable' sorting, so that we get the same result, independent of
232 machine, or extra data.
233 To do this, we use the same basic algorithm as topo_sort, but when we
234 aren't sure what node to access next, we sort them lexicographically.
235 """
236 tips = self._find_tips()
237 # Split the tips based on prefix
238 prefix_tips = {}
239 for node in tips:
240 if node.key.__class__ is str or len(node.key) == 1:
241 prefix = ''
242 else:
243 prefix = node.key[0]
244 prefix_tips.setdefault(prefix, []).append(node)
245
246 num_seen_children = dict.fromkeys(self._nodes, 0)
247
248 result = []
249 for prefix in sorted(prefix_tips):
250 pending = sorted(prefix_tips[prefix], key=lambda n:n.key,
251 reverse=True)
252 while pending:
253 node = pending.pop()
254 if node.parent_keys is None:
255 # Ghost node, skip it
256 continue
257 result.append(node.key)
258 for parent_key in sorted(node.parent_keys, reverse=True):
259 parent_node = self._nodes[parent_key]
260 seen_children = num_seen_children[parent_key] + 1
261 if seen_children == len(parent_node.child_keys):
262 # All children have been processed, enqueue this parent
263 pending.append(parent_node)
264 # This has been queued up, stop tracking it
265 del num_seen_children[parent_key]
266 else:
267 num_seen_children[parent_key] = seen_children
268 return result
269
270 def merge_sort(self, tip_key):
271 """Compute the merge sorted graph output."""
272 from bzrlib import tsort
273 as_parent_map = dict((node.key, node.parent_keys)
274 for node in self._nodes.itervalues()
275 if node.parent_keys is not None)
276 # We intentionally always generate revnos and never force the
277 # mainline_revisions
278 # Strip the sequence_number that merge_sort generates
279 return [_MergeSortNode(key, merge_depth, revno, end_of_merge)
280 for _, key, merge_depth, revno, end_of_merge
281 in tsort.merge_sort(as_parent_map, tip_key,
282 mainline_revisions=None,
283 generate_revno=True)]
284
285 def get_parent_keys(self, key):
286 """Get the parents for a key
287
288 Returns a list containg the parents keys. If the key is a ghost,
289 None is returned. A KeyError will be raised if the key is not in
290 the graph.
291
292 :param keys: Key to check (eg revision_id)
293 :return: A list of parents
294 """
295 return self._nodes[key].parent_keys
296
297 def get_child_keys(self, key):
298 """Get the children for a key
299
300 Returns a list containg the children keys. A KeyError will be raised
301 if the key is not in the graph.
302
303 :param keys: Key to check (eg revision_id)
304 :return: A list of children
305 """
306 return self._nodes[key].child_keys
174307
=== modified file 'bzrlib/_known_graph_pyx.pyx'
--- bzrlib/_known_graph_pyx.pyx 2009-07-14 16:10:32 +0000
+++ bzrlib/_known_graph_pyx.pyx 2009-09-07 14:19:05 +0000
@@ -25,11 +25,18 @@
25 ctypedef struct PyObject:25 ctypedef struct PyObject:
26 pass26 pass
2727
28 int PyString_CheckExact(object)
29
30 int PyObject_RichCompareBool(object, object, int)
31 int Py_LT
32
33 int PyTuple_CheckExact(object)
28 object PyTuple_New(Py_ssize_t n)34 object PyTuple_New(Py_ssize_t n)
29 Py_ssize_t PyTuple_GET_SIZE(object t)35 Py_ssize_t PyTuple_GET_SIZE(object t)
30 PyObject * PyTuple_GET_ITEM(object t, Py_ssize_t o)36 PyObject * PyTuple_GET_ITEM(object t, Py_ssize_t o)
31 void PyTuple_SET_ITEM(object t, Py_ssize_t o, object v)37 void PyTuple_SET_ITEM(object t, Py_ssize_t o, object v)
3238
39 int PyList_CheckExact(object)
33 Py_ssize_t PyList_GET_SIZE(object l)40 Py_ssize_t PyList_GET_SIZE(object l)
34 PyObject * PyList_GET_ITEM(object l, Py_ssize_t o)41 PyObject * PyList_GET_ITEM(object l, Py_ssize_t o)
35 int PyList_SetItem(object l, Py_ssize_t o, object l) except -142 int PyList_SetItem(object l, Py_ssize_t o, object l) except -1
@@ -44,8 +51,9 @@
4451
45 void Py_INCREF(object)52 void Py_INCREF(object)
4653
54import gc
4755
48from bzrlib import revision56from bzrlib import errors, revision
4957
50cdef object NULL_REVISION58cdef object NULL_REVISION
51NULL_REVISION = revision.NULL_REVISION59NULL_REVISION = revision.NULL_REVISION
@@ -59,10 +67,9 @@
59 cdef object children67 cdef object children
60 cdef public long gdfo68 cdef public long gdfo
61 cdef int seen69 cdef int seen
70 cdef object extra
6271
63 def __init__(self, key):72 def __init__(self, key):
64 cdef int i
65
66 self.key = key73 self.key = key
67 self.parents = None74 self.parents = None
6875
@@ -70,6 +77,7 @@
70 # Greatest distance from origin77 # Greatest distance from origin
71 self.gdfo = -178 self.gdfo = -1
72 self.seen = 079 self.seen = 0
80 self.extra = None
7381
74 property child_keys:82 property child_keys:
75 def __get__(self):83 def __get__(self):
@@ -80,6 +88,18 @@
80 PyList_Append(keys, child.key)88 PyList_Append(keys, child.key)
81 return keys89 return keys
8290
91 property parent_keys:
92 def __get__(self):
93 if self.parents is None:
94 return None
95
96 cdef _KnownGraphNode parent
97
98 keys = []
99 for parent in self.parents:
100 PyList_Append(keys, parent.key)
101 return keys
102
83 cdef clear_references(self):103 cdef clear_references(self):
84 self.parents = None104 self.parents = None
85 self.children = None105 self.children = None
@@ -107,17 +127,66 @@
107 return <_KnownGraphNode>temp_node127 return <_KnownGraphNode>temp_node
108128
109129
110cdef _KnownGraphNode _get_parent(parents, Py_ssize_t pos):130cdef _KnownGraphNode _get_tuple_node(tpl, Py_ssize_t pos):
111 cdef PyObject *temp_node131 cdef PyObject *temp_node
112 cdef _KnownGraphNode node
113132
114 temp_node = PyTuple_GET_ITEM(parents, pos)133 temp_node = PyTuple_GET_ITEM(tpl, pos)
115 return <_KnownGraphNode>temp_node134 return <_KnownGraphNode>temp_node
116135
117136
118# TODO: slab allocate all _KnownGraphNode objects.137def get_key(node):
119# We already know how many we are going to need, except for a couple of138 cdef _KnownGraphNode real_node
120# ghosts that could be allocated on demand.139 real_node = node
140 return real_node.key
141
142
143cdef object _sort_list_nodes(object lst_or_tpl, int reverse):
144 """Sort a list of _KnownGraphNode objects.
145
146 If lst_or_tpl is a list, it is allowed to mutate in place. It may also
147 just return the input list if everything is already sorted.
148 """
149 cdef _KnownGraphNode node1, node2
150 cdef int do_swap, is_tuple
151 cdef Py_ssize_t length
152
153 is_tuple = PyTuple_CheckExact(lst_or_tpl)
154 if not (is_tuple or PyList_CheckExact(lst_or_tpl)):
155 raise TypeError('lst_or_tpl must be a list or tuple.')
156 length = len(lst_or_tpl)
157 if length == 0 or length == 1:
158 return lst_or_tpl
159 if length == 2:
160 if is_tuple:
161 node1 = _get_tuple_node(lst_or_tpl, 0)
162 node2 = _get_tuple_node(lst_or_tpl, 1)
163 else:
164 node1 = _get_list_node(lst_or_tpl, 0)
165 node2 = _get_list_node(lst_or_tpl, 1)
166 if reverse:
167 do_swap = PyObject_RichCompareBool(node1.key, node2.key, Py_LT)
168 else:
169 do_swap = PyObject_RichCompareBool(node2.key, node1.key, Py_LT)
170 if not do_swap:
171 return lst_or_tpl
172 if is_tuple:
173 return (node2, node1)
174 else:
175 # Swap 'in-place', since lists are mutable
176 Py_INCREF(node1)
177 PyList_SetItem(lst_or_tpl, 1, node1)
178 Py_INCREF(node2)
179 PyList_SetItem(lst_or_tpl, 0, node2)
180 return lst_or_tpl
181 # For all other sizes, we just use 'sorted()'
182 if is_tuple:
183 # Note that sorted() is just list(iterable).sort()
184 lst_or_tpl = list(lst_or_tpl)
185 lst_or_tpl.sort(key=get_key, reverse=reverse)
186 return lst_or_tpl
187
188
189cdef class _MergeSorter
121190
122cdef class KnownGraph:191cdef class KnownGraph:
123 """This is a class which assumes we already know the full graph."""192 """This is a class which assumes we already know the full graph."""
@@ -136,6 +205,9 @@
136 # Maps {sorted(revision_id, revision_id): heads}205 # Maps {sorted(revision_id, revision_id): heads}
137 self._known_heads = {}206 self._known_heads = {}
138 self.do_cache = int(do_cache)207 self.do_cache = int(do_cache)
208 # TODO: consider disabling gc since we are allocating a lot of nodes
209 # that won't be collectable anyway. real world testing has not
210 # shown a specific impact, yet.
139 self._initialize_nodes(parent_map)211 self._initialize_nodes(parent_map)
140 self._find_gdfo()212 self._find_gdfo()
141213
@@ -183,11 +255,16 @@
183 parent_keys = <object>temp_parent_keys255 parent_keys = <object>temp_parent_keys
184 num_parent_keys = len(parent_keys)256 num_parent_keys = len(parent_keys)
185 node = self._get_or_create_node(key)257 node = self._get_or_create_node(key)
186 # We know how many parents, so we could pre allocate an exact sized258 # We know how many parents, so we pre allocate the tuple
187 # tuple here
188 parent_nodes = PyTuple_New(num_parent_keys)259 parent_nodes = PyTuple_New(num_parent_keys)
189 # We use iter here, because parent_keys maybe be a list or tuple
190 for pos2 from 0 <= pos2 < num_parent_keys:260 for pos2 from 0 <= pos2 < num_parent_keys:
261 # Note: it costs us 10ms out of 40ms to lookup all of these
262 # parents, it doesn't seem to be an allocation overhead,
263 # but rather a lookup overhead. There doesn't seem to be
264 # a way around it, and that is one reason why
265 # KnownGraphNode maintains a direct pointer to the parent
266 # node.
267 # We use [] because parent_keys may be a tuple or list
191 parent_node = self._get_or_create_node(parent_keys[pos2])268 parent_node = self._get_or_create_node(parent_keys[pos2])
192 # PyTuple_SET_ITEM will steal a reference, so INCREF first269 # PyTuple_SET_ITEM will steal a reference, so INCREF first
193 Py_INCREF(parent_node)270 Py_INCREF(parent_node)
@@ -209,6 +286,19 @@
209 PyList_Append(tails, node)286 PyList_Append(tails, node)
210 return tails287 return tails
211288
289 def _find_tips(self):
290 cdef PyObject *temp_node
291 cdef _KnownGraphNode node
292 cdef Py_ssize_t pos
293
294 tips = []
295 pos = 0
296 while PyDict_Next(self._nodes, &pos, NULL, &temp_node):
297 node = <_KnownGraphNode>temp_node
298 if PyList_GET_SIZE(node.children) == 0:
299 PyList_Append(tips, node)
300 return tips
301
212 def _find_gdfo(self):302 def _find_gdfo(self):
213 cdef _KnownGraphNode node303 cdef _KnownGraphNode node
214 cdef _KnownGraphNode child304 cdef _KnownGraphNode child
@@ -315,7 +405,7 @@
315 continue405 continue
316 if node.parents is not None and PyTuple_GET_SIZE(node.parents) > 0:406 if node.parents is not None and PyTuple_GET_SIZE(node.parents) > 0:
317 for pos from 0 <= pos < PyTuple_GET_SIZE(node.parents):407 for pos from 0 <= pos < PyTuple_GET_SIZE(node.parents):
318 parent_node = _get_parent(node.parents, pos)408 parent_node = _get_tuple_node(node.parents, pos)
319 last_item = last_item + 1409 last_item = last_item + 1
320 if last_item < PyList_GET_SIZE(pending):410 if last_item < PyList_GET_SIZE(pending):
321 Py_INCREF(parent_node) # SetItem steals a ref411 Py_INCREF(parent_node) # SetItem steals a ref
@@ -335,3 +425,454 @@
335 if self.do_cache:425 if self.do_cache:
336 PyDict_SetItem(self._known_heads, heads_key, heads)426 PyDict_SetItem(self._known_heads, heads_key, heads)
337 return heads427 return heads
428
429 def topo_sort(self):
430 """Return the nodes in topological order.
431
432 All parents must occur before all children.
433 """
434 # This is, for the most part, the same iteration order that we used for
435 # _find_gdfo, consider finding a way to remove the duplication
436 # In general, we find the 'tails' (nodes with no parents), and then
437 # walk to the children. For children that have all of their parents
438 # yielded, we queue up the child to be yielded as well.
439 cdef _KnownGraphNode node
440 cdef _KnownGraphNode child
441 cdef PyObject *temp
442 cdef Py_ssize_t pos
443 cdef int replace
444 cdef Py_ssize_t last_item
445
446 pending = self._find_tails()
447 if PyList_GET_SIZE(pending) == 0 and len(self._nodes) > 0:
448 raise errors.GraphCycleError(self._nodes)
449
450 topo_order = []
451
452 last_item = PyList_GET_SIZE(pending) - 1
453 while last_item >= 0:
454 # Avoid pop followed by push, instead, peek, and replace
455 # timing shows this is 930ms => 770ms for OOo
456 node = _get_list_node(pending, last_item)
457 last_item = last_item - 1
458 if node.parents is not None:
459 # We don't include ghost parents
460 PyList_Append(topo_order, node.key)
461 for pos from 0 <= pos < PyList_GET_SIZE(node.children):
462 child = _get_list_node(node.children, pos)
463 if child.gdfo == -1:
464 # We know we have a graph cycle because a node has a parent
465 # which we couldn't find
466 raise errors.GraphCycleError(self._nodes)
467 child.seen = child.seen + 1
468 if child.seen == PyTuple_GET_SIZE(child.parents):
469 # All parents of this child have been yielded, queue this
470 # one to be yielded as well
471 last_item = last_item + 1
472 if last_item < PyList_GET_SIZE(pending):
473 Py_INCREF(child) # SetItem steals a ref
474 PyList_SetItem(pending, last_item, child)
475 else:
476 PyList_Append(pending, child)
477 # We have queued this node, we don't need to track it
478 # anymore
479 child.seen = 0
480 # We started from the parents, so we don't need to do anymore work
481 return topo_order
482
483 def gc_sort(self):
484 """Return a reverse topological ordering which is 'stable'.
485
486 There are a few constraints:
487 1) Reverse topological (all children before all parents)
488 2) Grouped by prefix
489 3) 'stable' sorting, so that we get the same result, independent of
490 machine, or extra data.
491 To do this, we use the same basic algorithm as topo_sort, but when we
492 aren't sure what node to access next, we sort them lexicographically.
493 """
494 cdef PyObject *temp
495 cdef Py_ssize_t pos, last_item
496 cdef _KnownGraphNode node, node2, parent_node
497
498 tips = self._find_tips()
499 # Split the tips based on prefix
500 prefix_tips = {}
501 for pos from 0 <= pos < PyList_GET_SIZE(tips):
502 node = _get_list_node(tips, pos)
503 if PyString_CheckExact(node.key) or len(node.key) == 1:
504 prefix = ''
505 else:
506 prefix = node.key[0]
507 temp = PyDict_GetItem(prefix_tips, prefix)
508 if temp == NULL:
509 prefix_tips[prefix] = [node]
510 else:
511 tip_nodes = <object>temp
512 PyList_Append(tip_nodes, node)
513
514 result = []
515 for prefix in sorted(prefix_tips):
516 temp = PyDict_GetItem(prefix_tips, prefix)
517 assert temp != NULL
518 tip_nodes = <object>temp
519 pending = _sort_list_nodes(tip_nodes, 1)
520 last_item = PyList_GET_SIZE(pending) - 1
521 while last_item >= 0:
522 node = _get_list_node(pending, last_item)
523 last_item = last_item - 1
524 if node.parents is None:
525 # Ghost
526 continue
527 PyList_Append(result, node.key)
528 # Sorting the parent keys isn't strictly necessary for stable
529 # sorting of a given graph. But it does help minimize the
530 # differences between graphs
531 # For bzr.dev ancestry:
532 # 4.73ms no sort
533 # 7.73ms RichCompareBool sort
534 parents = _sort_list_nodes(node.parents, 1)
535 for pos from 0 <= pos < len(parents):
536 if PyTuple_CheckExact(parents):
537 parent_node = _get_tuple_node(parents, pos)
538 else:
539 parent_node = _get_list_node(parents, pos)
540 # TODO: GraphCycle detection
541 parent_node.seen = parent_node.seen + 1
542 if (parent_node.seen
543 == PyList_GET_SIZE(parent_node.children)):
544 # All children have been processed, queue up this
545 # parent
546 last_item = last_item + 1
547 if last_item < PyList_GET_SIZE(pending):
548 Py_INCREF(parent_node) # SetItem steals a ref
549 PyList_SetItem(pending, last_item, parent_node)
550 else:
551 PyList_Append(pending, parent_node)
552 parent_node.seen = 0
553 return result
554
555 def merge_sort(self, tip_key):
556 """Compute the merge sorted graph output."""
557 cdef _MergeSorter sorter
558
559 # TODO: consider disabling gc since we are allocating a lot of nodes
560 # that won't be collectable anyway. real world testing has not
561 # shown a specific impact, yet.
562 sorter = _MergeSorter(self, tip_key)
563 return sorter.topo_order()
564
565 def get_parent_keys(self, key):
566 """Get the parents for a key
567
568 Returns a list containg the parents keys. If the key is a ghost,
569 None is returned. A KeyError will be raised if the key is not in
570 the graph.
571
572 :param keys: Key to check (eg revision_id)
573 :return: A list of parents
574 """
575 return self._nodes[key].parent_keys
576
577 def get_child_keys(self, key):
578 """Get the children for a key
579
580 Returns a list containg the children keys. A KeyError will be raised
581 if the key is not in the graph.
582
583 :param keys: Key to check (eg revision_id)
584 :return: A list of children
585 """
586 return self._nodes[key].child_keys
587
588
589cdef class _MergeSortNode:
590 """Tracks information about a node during the merge_sort operation."""
591
592 # Public api
593 cdef public object key
594 cdef public long merge_depth
595 cdef public object end_of_merge # True/False Is this the end of the current merge
596
597 # Private api, used while computing the information
598 cdef _KnownGraphNode left_parent
599 cdef _KnownGraphNode left_pending_parent
600 cdef object pending_parents # list of _KnownGraphNode for non-left parents
601 cdef long _revno_first
602 cdef long _revno_second
603 cdef long _revno_last
604 # TODO: turn these into flag/bit fields rather than individual members
605 cdef int is_first_child # Is this the first child?
606 cdef int seen_by_child # A child node has seen this parent
607 cdef int completed # Fully Processed
608
609 def __init__(self, key):
610 self.key = key
611 self.merge_depth = -1
612 self.left_parent = None
613 self.left_pending_parent = None
614 self.pending_parents = None
615 self._revno_first = -1
616 self._revno_second = -1
617 self._revno_last = -1
618 self.is_first_child = 0
619 self.seen_by_child = 0
620 self.completed = 0
621
622 def __repr__(self):
623 return '%s(%s depth:%s rev:%s,%s,%s first:%s seen:%s)' % (
624 self.__class__.__name__, self.key,
625 self.merge_depth,
626 self._revno_first, self._revno_second, self._revno_last,
627 self.is_first_child, self.seen_by_child)
628
629 cdef int has_pending_parents(self):
630 if self.left_pending_parent is not None or self.pending_parents:
631 return 1
632 return 0
633
634 cdef object _revno(self):
635 if self._revno_first == -1:
636 if self._revno_second != -1:
637 raise RuntimeError('Something wrong with: %s' % (self,))
638 return (self._revno_last,)
639 else:
640 return (self._revno_first, self._revno_second, self._revno_last)
641
642 property revno:
643 def __get__(self):
644 return self._revno()
645
646
647cdef class _MergeSorter:
648 """This class does the work of computing the merge_sort ordering.
649
650 We have some small advantages, in that we get all the extra information
651 that KnownGraph knows, like knowing the child lists, etc.
652 """
653
654 # Current performance numbers for merge_sort(bzr_dev_parent_map):
655 # 302ms tsort.merge_sort()
656 # 91ms graph.KnownGraph().merge_sort()
657 # 40ms kg.merge_sort()
658
659 cdef KnownGraph graph
660 cdef object _depth_first_stack # list
661 cdef Py_ssize_t _last_stack_item # offset to last item on stack
662 # cdef object _ms_nodes # dict of key => _MergeSortNode
663 cdef object _revno_to_branch_count # {revno => num child branches}
664 cdef object _scheduled_nodes # List of nodes ready to be yielded
665
666 def __init__(self, known_graph, tip_key):
667 cdef _KnownGraphNode node
668
669 self.graph = known_graph
670 # self._ms_nodes = {}
671 self._revno_to_branch_count = {}
672 self._depth_first_stack = []
673 self._last_stack_item = -1
674 self._scheduled_nodes = []
675 if (tip_key is not None and tip_key != NULL_REVISION
676 and tip_key != (NULL_REVISION,)):
677 node = self.graph._nodes[tip_key]
678 self._push_node(node, 0)
679
680 cdef _MergeSortNode _get_ms_node(self, _KnownGraphNode node):
681 cdef PyObject *temp_node
682 cdef _MergeSortNode ms_node
683
684 if node.extra is None:
685 ms_node = _MergeSortNode(node.key)
686 node.extra = ms_node
687 else:
688 ms_node = <_MergeSortNode>node.extra
689 return ms_node
690
691 cdef _push_node(self, _KnownGraphNode node, long merge_depth):
692 cdef _KnownGraphNode parent_node
693 cdef _MergeSortNode ms_node, ms_parent_node
694 cdef Py_ssize_t pos
695
696 ms_node = self._get_ms_node(node)
697 ms_node.merge_depth = merge_depth
698 if node.parents is None:
699 raise RuntimeError('ghost nodes should not be pushed'
700 ' onto the stack: %s' % (node,))
701 if PyTuple_GET_SIZE(node.parents) > 0:
702 parent_node = _get_tuple_node(node.parents, 0)
703 ms_node.left_parent = parent_node
704 if parent_node.parents is None: # left-hand ghost
705 ms_node.left_pending_parent = None
706 ms_node.left_parent = None
707 else:
708 ms_node.left_pending_parent = parent_node
709 if PyTuple_GET_SIZE(node.parents) > 1:
710 ms_node.pending_parents = []
711 for pos from 1 <= pos < PyTuple_GET_SIZE(node.parents):
712 parent_node = _get_tuple_node(node.parents, pos)
713 if parent_node.parents is None: # ghost
714 continue
715 PyList_Append(ms_node.pending_parents, parent_node)
716
717 ms_node.is_first_child = 1
718 if ms_node.left_parent is not None:
719 ms_parent_node = self._get_ms_node(ms_node.left_parent)
720 if ms_parent_node.seen_by_child:
721 ms_node.is_first_child = 0
722 ms_parent_node.seen_by_child = 1
723 self._last_stack_item = self._last_stack_item + 1
724 if self._last_stack_item < PyList_GET_SIZE(self._depth_first_stack):
725 Py_INCREF(node) # SetItem steals a ref
726 PyList_SetItem(self._depth_first_stack, self._last_stack_item,
727 node)
728 else:
729 PyList_Append(self._depth_first_stack, node)
730
731 cdef _pop_node(self):
732 cdef PyObject *temp
733 cdef _MergeSortNode ms_node, ms_parent_node, ms_prev_node
734 cdef _KnownGraphNode node, parent_node, prev_node
735
736 node = _get_list_node(self._depth_first_stack, self._last_stack_item)
737 ms_node = <_MergeSortNode>node.extra
738 self._last_stack_item = self._last_stack_item - 1
739 if ms_node.left_parent is not None:
740 # Assign the revision number from the left-hand parent
741 ms_parent_node = <_MergeSortNode>ms_node.left_parent.extra
742 if ms_node.is_first_child:
743 # First child just increments the final digit
744 ms_node._revno_first = ms_parent_node._revno_first
745 ms_node._revno_second = ms_parent_node._revno_second
746 ms_node._revno_last = ms_parent_node._revno_last + 1
747 else:
748 # Not the first child, make a new branch
749 # (mainline_revno, branch_count, 1)
750 if ms_parent_node._revno_first == -1:
751 # Mainline ancestor, the increment is on the last digit
752 base_revno = ms_parent_node._revno_last
753 else:
754 base_revno = ms_parent_node._revno_first
755 temp = PyDict_GetItem(self._revno_to_branch_count,
756 base_revno)
757 if temp == NULL:
758 branch_count = 1
759 else:
760 branch_count = (<object>temp) + 1
761 PyDict_SetItem(self._revno_to_branch_count, base_revno,
762 branch_count)
763 ms_node._revno_first = base_revno
764 ms_node._revno_second = branch_count
765 ms_node._revno_last = 1
766 else:
767 temp = PyDict_GetItem(self._revno_to_branch_count, 0)
768 if temp == NULL:
769 # The first root node doesn't have a 3-digit revno
770 root_count = 0
771 ms_node._revno_first = -1
772 ms_node._revno_second = -1
773 ms_node._revno_last = 1
774 else:
775 root_count = (<object>temp) + 1
776 ms_node._revno_first = 0
777 ms_node._revno_second = root_count
778 ms_node._revno_last = 1
779 PyDict_SetItem(self._revno_to_branch_count, 0, root_count)
780 ms_node.completed = 1
781 if PyList_GET_SIZE(self._scheduled_nodes) == 0:
782 # The first scheduled node is always the end of merge
783 ms_node.end_of_merge = True
784 else:
785 prev_node = _get_list_node(self._scheduled_nodes,
786 PyList_GET_SIZE(self._scheduled_nodes) - 1)
787 ms_prev_node = <_MergeSortNode>prev_node.extra
788 if ms_prev_node.merge_depth < ms_node.merge_depth:
789 # The previously pushed node is to our left, so this is the end
790 # of this right-hand chain
791 ms_node.end_of_merge = True
792 elif (ms_prev_node.merge_depth == ms_node.merge_depth
793 and prev_node not in node.parents):
794 # The next node is not a direct parent of this node
795 ms_node.end_of_merge = True
796 else:
797 ms_node.end_of_merge = False
798 PyList_Append(self._scheduled_nodes, node)
799
800 cdef _schedule_stack(self):
801 cdef _KnownGraphNode last_node, next_node
802 cdef _MergeSortNode ms_node, ms_last_node, ms_next_node
803 cdef long next_merge_depth
804 ordered = []
805 while self._last_stack_item >= 0:
806 # Peek at the last item on the stack
807 last_node = _get_list_node(self._depth_first_stack,
808 self._last_stack_item)
809 if last_node.gdfo == -1:
810 # if _find_gdfo skipped a node, that means there is a graph
811 # cycle, error out now
812 raise errors.GraphCycleError(self.graph._nodes)
813 ms_last_node = <_MergeSortNode>last_node.extra
814 if not ms_last_node.has_pending_parents():
815 # Processed all parents, pop this node
816 self._pop_node()
817 continue
818 while ms_last_node.has_pending_parents():
819 if ms_last_node.left_pending_parent is not None:
820 # recurse depth first into the primary parent
821 next_node = ms_last_node.left_pending_parent
822 ms_last_node.left_pending_parent = None
823 else:
824 # place any merges in right-to-left order for scheduling
825 # which gives us left-to-right order after we reverse
826 # the scheduled queue.
827 # Note: This has the effect of allocating common-new
828 # revisions to the right-most subtree rather than the
829 # left most, which will display nicely (you get
830 # smaller trees at the top of the combined merge).
831 next_node = ms_last_node.pending_parents.pop()
832 ms_next_node = self._get_ms_node(next_node)
833 if ms_next_node.completed:
834 # this parent was completed by a child on the
835 # call stack. skip it.
836 continue
837 # otherwise transfer it from the source graph into the
838 # top of the current depth first search stack.
839
840 if next_node is ms_last_node.left_parent:
841 next_merge_depth = ms_last_node.merge_depth
842 else:
843 next_merge_depth = ms_last_node.merge_depth + 1
844 self._push_node(next_node, next_merge_depth)
845 # and do not continue processing parents until this 'call'
846 # has recursed.
847 break
848
849 cdef topo_order(self):
850 cdef _MergeSortNode ms_node
851 cdef _KnownGraphNode node
852 cdef Py_ssize_t pos
853 cdef PyObject *temp_key, *temp_node
854
855 # Note: allocating a _MergeSortNode and deallocating it for all nodes
856 # costs approx 8.52ms (21%) of the total runtime
857 # We might consider moving the attributes into the base
858 # KnownGraph object.
859 self._schedule_stack()
860
861 # We've set up the basic schedule, now we can continue processing the
862 # output.
863 # Note: This final loop costs us 40.0ms => 28.8ms (11ms, 25%) on
864 # bzr.dev, to convert the internal Object representation into a
865 # Tuple representation...
866 # 2ms is walking the data and computing revno tuples
867 # 7ms is computing the return tuple
868 # 4ms is PyList_Append()
869 ordered = []
870 # output the result in reverse order, and separate the generated info
871 for pos from PyList_GET_SIZE(self._scheduled_nodes) > pos >= 0:
872 node = _get_list_node(self._scheduled_nodes, pos)
873 ms_node = <_MergeSortNode>node.extra
874 PyList_Append(ordered, ms_node)
875 node.extra = None
876 # Clear out the scheduled nodes now that we're done
877 self._scheduled_nodes = []
878 return ordered
338879
=== modified file 'bzrlib/annotate.py'
--- bzrlib/annotate.py 2009-07-08 17:09:03 +0000
+++ bzrlib/annotate.py 2009-08-17 18:52:01 +0000
@@ -188,6 +188,10 @@
188 # or something.188 # or something.
189 last_revision = current_rev.revision_id189 last_revision = current_rev.revision_id
190 # XXX: Partially Cloned from branch, uses the old_get_graph, eep.190 # XXX: Partially Cloned from branch, uses the old_get_graph, eep.
191 # XXX: The main difficulty is that we need to inject a single new node
192 # (current_rev) into the graph before it gets numbered, etc.
193 # Once KnownGraph gets an 'add_node()' function, we can use
194 # VF.get_known_graph_ancestry().
191 graph = repository.get_graph()195 graph = repository.get_graph()
192 revision_graph = dict(((key, value) for key, value in196 revision_graph = dict(((key, value) for key, value in
193 graph.iter_ancestry(current_rev.parent_ids) if value is not None))197 graph.iter_ancestry(current_rev.parent_ids) if value is not None))
194198
=== modified file 'bzrlib/branch.py'
--- bzrlib/branch.py 2009-08-04 04:36:34 +0000
+++ bzrlib/branch.py 2009-08-19 18:04:49 +0000
@@ -446,15 +446,11 @@
446 # start_revision_id.446 # start_revision_id.
447 if self._merge_sorted_revisions_cache is None:447 if self._merge_sorted_revisions_cache is None:
448 last_revision = self.last_revision()448 last_revision = self.last_revision()
449 graph = self.repository.get_graph()449 last_key = (last_revision,)
450 parent_map = dict(((key, value) for key, value in450 known_graph = self.repository.revisions.get_known_graph_ancestry(
451 graph.iter_ancestry([last_revision]) if value is not None))451 [last_key])
452 revision_graph = repository._strip_NULL_ghosts(parent_map)452 self._merge_sorted_revisions_cache = known_graph.merge_sort(
453 revs = tsort.merge_sort(revision_graph, last_revision, None,453 last_key)
454 generate_revno=True)
455 # Drop the sequence # before caching
456 self._merge_sorted_revisions_cache = [r[1:] for r in revs]
457
458 filtered = self._filter_merge_sorted_revisions(454 filtered = self._filter_merge_sorted_revisions(
459 self._merge_sorted_revisions_cache, start_revision_id,455 self._merge_sorted_revisions_cache, start_revision_id,
460 stop_revision_id, stop_rule)456 stop_revision_id, stop_rule)
@@ -470,27 +466,34 @@
470 """Iterate over an inclusive range of sorted revisions."""466 """Iterate over an inclusive range of sorted revisions."""
471 rev_iter = iter(merge_sorted_revisions)467 rev_iter = iter(merge_sorted_revisions)
472 if start_revision_id is not None:468 if start_revision_id is not None:
473 for rev_id, depth, revno, end_of_merge in rev_iter:469 for node in rev_iter:
470 rev_id = node.key[-1]
474 if rev_id != start_revision_id:471 if rev_id != start_revision_id:
475 continue472 continue
476 else:473 else:
477 # The decision to include the start or not474 # The decision to include the start or not
478 # depends on the stop_rule if a stop is provided475 # depends on the stop_rule if a stop is provided
479 rev_iter = chain(476 # so pop this node back into the iterator
480 iter([(rev_id, depth, revno, end_of_merge)]),477 rev_iter = chain(iter([node]), rev_iter)
481 rev_iter)
482 break478 break
483 if stop_revision_id is None:479 if stop_revision_id is None:
484 for rev_id, depth, revno, end_of_merge in rev_iter:480 # Yield everything
485 yield rev_id, depth, revno, end_of_merge481 for node in rev_iter:
482 rev_id = node.key[-1]
483 yield (rev_id, node.merge_depth, node.revno,
484 node.end_of_merge)
486 elif stop_rule == 'exclude':485 elif stop_rule == 'exclude':
487 for rev_id, depth, revno, end_of_merge in rev_iter:486 for node in rev_iter:
487 rev_id = node.key[-1]
488 if rev_id == stop_revision_id:488 if rev_id == stop_revision_id:
489 return489 return
490 yield rev_id, depth, revno, end_of_merge490 yield (rev_id, node.merge_depth, node.revno,
491 node.end_of_merge)
491 elif stop_rule == 'include':492 elif stop_rule == 'include':
492 for rev_id, depth, revno, end_of_merge in rev_iter:493 for node in rev_iter:
493 yield rev_id, depth, revno, end_of_merge494 rev_id = node.key[-1]
495 yield (rev_id, node.merge_depth, node.revno,
496 node.end_of_merge)
494 if rev_id == stop_revision_id:497 if rev_id == stop_revision_id:
495 return498 return
496 elif stop_rule == 'with-merges':499 elif stop_rule == 'with-merges':
@@ -499,10 +502,12 @@
499 left_parent = stop_rev.parent_ids[0]502 left_parent = stop_rev.parent_ids[0]
500 else:503 else:
501 left_parent = _mod_revision.NULL_REVISION504 left_parent = _mod_revision.NULL_REVISION
502 for rev_id, depth, revno, end_of_merge in rev_iter:505 for node in rev_iter:
506 rev_id = node.key[-1]
503 if rev_id == left_parent:507 if rev_id == left_parent:
504 return508 return
505 yield rev_id, depth, revno, end_of_merge509 yield (rev_id, node.merge_depth, node.revno,
510 node.end_of_merge)
506 else:511 else:
507 raise ValueError('invalid stop_rule %r' % stop_rule)512 raise ValueError('invalid stop_rule %r' % stop_rule)
508513
@@ -1147,6 +1152,9 @@
1147 revision_id: if not None, the revision history in the new branch will1152 revision_id: if not None, the revision history in the new branch will
1148 be truncated to end with revision_id.1153 be truncated to end with revision_id.
1149 """1154 """
1155 if (repository_policy is not None and
1156 repository_policy.requires_stacking()):
1157 to_bzrdir._format.require_stacking(_skip_repo=True)
1150 result = to_bzrdir.create_branch()1158 result = to_bzrdir.create_branch()
1151 result.lock_write()1159 result.lock_write()
1152 try:1160 try:
@@ -2064,7 +2072,7 @@
2064BranchFormat.register_format(__format6)2072BranchFormat.register_format(__format6)
2065BranchFormat.register_format(__format7)2073BranchFormat.register_format(__format7)
2066BranchFormat.register_format(__format8)2074BranchFormat.register_format(__format8)
2067BranchFormat.set_default_format(__format6)2075BranchFormat.set_default_format(__format7)
2068_legacy_formats = [BzrBranchFormat4(),2076_legacy_formats = [BzrBranchFormat4(),
2069 ]2077 ]
2070network_format_registry.register(2078network_format_registry.register(
20712079
=== modified file 'bzrlib/btree_index.py'
--- bzrlib/btree_index.py 2009-07-01 10:51:47 +0000
+++ bzrlib/btree_index.py 2009-08-17 22:11:06 +0000
@@ -586,13 +586,19 @@
586class _LeafNode(object):586class _LeafNode(object):
587 """A leaf node for a serialised B+Tree index."""587 """A leaf node for a serialised B+Tree index."""
588588
589 __slots__ = ('keys',)589 __slots__ = ('keys', 'min_key', 'max_key')
590590
591 def __init__(self, bytes, key_length, ref_list_length):591 def __init__(self, bytes, key_length, ref_list_length):
592 """Parse bytes to create a leaf node object."""592 """Parse bytes to create a leaf node object."""
593 # splitlines mangles the \r delimiters.. don't use it.593 # splitlines mangles the \r delimiters.. don't use it.
594 self.keys = dict(_btree_serializer._parse_leaf_lines(bytes,594 key_list = _btree_serializer._parse_leaf_lines(bytes,
595 key_length, ref_list_length))595 key_length, ref_list_length)
596 if key_list:
597 self.min_key = key_list[0][0]
598 self.max_key = key_list[-1][0]
599 else:
600 self.min_key = self.max_key = None
601 self.keys = dict(key_list)
596602
597603
598class _InternalNode(object):604class _InternalNode(object):
@@ -1039,6 +1045,39 @@
1039 output.append(cur_out)1045 output.append(cur_out)
1040 return output1046 return output
10411047
1048 def _walk_through_internal_nodes(self, keys):
1049 """Take the given set of keys, and find the corresponding LeafNodes.
1050
1051 :param keys: An unsorted iterable of keys to search for
1052 :return: (nodes, index_and_keys)
1053 nodes is a dict mapping {index: LeafNode}
1054 keys_at_index is a list of tuples of [(index, [keys for Leaf])]
1055 """
1056 # 6 seconds spent in miss_torture using the sorted() line.
1057 # Even with out of order disk IO it seems faster not to sort it when
1058 # large queries are being made.
1059 keys_at_index = [(0, sorted(keys))]
1060
1061 for row_pos, next_row_start in enumerate(self._row_offsets[1:-1]):
1062 node_indexes = [idx for idx, s_keys in keys_at_index]
1063 nodes = self._get_internal_nodes(node_indexes)
1064
1065 next_nodes_and_keys = []
1066 for node_index, sub_keys in keys_at_index:
1067 node = nodes[node_index]
1068 positions = self._multi_bisect_right(sub_keys, node.keys)
1069 node_offset = next_row_start + node.offset
1070 next_nodes_and_keys.extend([(node_offset + pos, s_keys)
1071 for pos, s_keys in positions])
1072 keys_at_index = next_nodes_and_keys
1073 # We should now be at the _LeafNodes
1074 node_indexes = [idx for idx, s_keys in keys_at_index]
1075
1076 # TODO: We may *not* want to always read all the nodes in one
1077 # big go. Consider setting a max size on this.
1078 nodes = self._get_leaf_nodes(node_indexes)
1079 return nodes, keys_at_index
1080
1042 def iter_entries(self, keys):1081 def iter_entries(self, keys):
1043 """Iterate over keys within the index.1082 """Iterate over keys within the index.
10441083
@@ -1082,32 +1121,7 @@
1082 needed_keys = keys1121 needed_keys = keys
1083 if not needed_keys:1122 if not needed_keys:
1084 return1123 return
1085 # 6 seconds spent in miss_torture using the sorted() line.1124 nodes, nodes_and_keys = self._walk_through_internal_nodes(needed_keys)
1086 # Even with out of order disk IO it seems faster not to sort it when
1087 # large queries are being made.
1088 needed_keys = sorted(needed_keys)
1089
1090 nodes_and_keys = [(0, needed_keys)]
1091
1092 for row_pos, next_row_start in enumerate(self._row_offsets[1:-1]):
1093 node_indexes = [idx for idx, s_keys in nodes_and_keys]
1094 nodes = self._get_internal_nodes(node_indexes)
1095
1096 next_nodes_and_keys = []
1097 for node_index, sub_keys in nodes_and_keys:
1098 node = nodes[node_index]
1099 positions = self._multi_bisect_right(sub_keys, node.keys)
1100 node_offset = next_row_start + node.offset
1101 next_nodes_and_keys.extend([(node_offset + pos, s_keys)
1102 for pos, s_keys in positions])
1103 nodes_and_keys = next_nodes_and_keys
1104 # We should now be at the _LeafNodes
1105 node_indexes = [idx for idx, s_keys in nodes_and_keys]
1106
1107 # TODO: We may *not* want to always read all the nodes in one
1108 # big go. Consider setting a max size on this.
1109
1110 nodes = self._get_leaf_nodes(node_indexes)
1111 for node_index, sub_keys in nodes_and_keys:1125 for node_index, sub_keys in nodes_and_keys:
1112 if not sub_keys:1126 if not sub_keys:
1113 continue1127 continue
@@ -1120,6 +1134,133 @@
1120 else:1134 else:
1121 yield (self, next_sub_key, value)1135 yield (self, next_sub_key, value)
11221136
1137 def _find_ancestors(self, keys, ref_list_num, parent_map, missing_keys):
1138 """Find the parent_map information for the set of keys.
1139
1140 This populates the parent_map dict and missing_keys set based on the
1141 queried keys. It also can fill out an arbitrary number of parents that
1142 it finds while searching for the supplied keys.
1143
1144 It is unlikely that you want to call this directly. See
1145 "CombinedGraphIndex.find_ancestry()" for a more appropriate API.
1146
1147 :param keys: A keys whose ancestry we want to return
1148 Every key will either end up in 'parent_map' or 'missing_keys'.
1149 :param ref_list_num: This index in the ref_lists is the parents we
1150 care about.
1151 :param parent_map: {key: parent_keys} for keys that are present in this
1152 index. This may contain more entries than were in 'keys', that are
1153 reachable ancestors of the keys requested.
1154 :param missing_keys: keys which are known to be missing in this index.
1155 This may include parents that were not directly requested, but we
1156 were able to determine that they are not present in this index.
1157 :return: search_keys parents that were found but not queried to know
1158 if they are missing or present. Callers can re-query this index for
1159 those keys, and they will be placed into parent_map or missing_keys
1160 """
1161 if not self.key_count():
1162 # We use key_count() to trigger reading the root node and
1163 # determining info about this BTreeGraphIndex
1164 # If we don't have any keys, then everything is missing
1165 missing_keys.update(keys)
1166 return set()
1167 if ref_list_num >= self.node_ref_lists:
1168 raise ValueError('No ref list %d, index has %d ref lists'
1169 % (ref_list_num, self.node_ref_lists))
1170
1171 # The main trick we are trying to accomplish is that when we find a
1172 # key listing its parents, we expect that the parent key is also likely
1173 # to sit on the same page. Allowing us to expand parents quickly
1174 # without suffering the full stack of bisecting, etc.
1175 nodes, nodes_and_keys = self._walk_through_internal_nodes(keys)
1176
1177 # These are parent keys which could not be immediately resolved on the
1178 # page where the child was present. Note that we may already be
1179 # searching for that key, and it may actually be present [or known
1180 # missing] on one of the other pages we are reading.
1181 # TODO:
1182 # We could try searching for them in the immediate previous or next
1183 # page. If they occur "later" we could put them in a pending lookup
1184 # set, and then for each node we read thereafter we could check to
1185 # see if they are present.
1186 # However, we don't know the impact of keeping this list of things
1187 # that I'm going to search for every node I come across from here on
1188 # out.
1189 # It doesn't handle the case when the parent key is missing on a
1190 # page that we *don't* read. So we already have to handle being
1191 # re-entrant for that.
1192 # Since most keys contain a date string, they are more likely to be
1193 # found earlier in the file than later, but we would know that right
1194 # away (key < min_key), and wouldn't keep searching it on every other
1195 # page that we read.
1196 # Mostly, it is an idea, one which should be benchmarked.
1197 parents_not_on_page = set()
1198
1199 for node_index, sub_keys in nodes_and_keys:
1200 if not sub_keys:
1201 continue
1202 # sub_keys is all of the keys we are looking for that should exist
1203 # on this page, if they aren't here, then they won't be found
1204 node = nodes[node_index]
1205 node_keys = node.keys
1206 parents_to_check = set()
1207 for next_sub_key in sub_keys:
1208 if next_sub_key not in node_keys:
1209 # This one is just not present in the index at all
1210 missing_keys.add(next_sub_key)
1211 else:
1212 value, refs = node_keys[next_sub_key]
1213 parent_keys = refs[ref_list_num]
1214 parent_map[next_sub_key] = parent_keys
1215 parents_to_check.update(parent_keys)
1216 # Don't look for things we've already found
1217 parents_to_check = parents_to_check.difference(parent_map)
1218 # this can be used to test the benefit of having the check loop
1219 # inlined.
1220 # parents_not_on_page.update(parents_to_check)
1221 # continue
1222 while parents_to_check:
1223 next_parents_to_check = set()
1224 for key in parents_to_check:
1225 if key in node_keys:
1226 value, refs = node_keys[key]
1227 parent_keys = refs[ref_list_num]
1228 parent_map[key] = parent_keys
1229 next_parents_to_check.update(parent_keys)
1230 else:
1231 # This parent either is genuinely missing, or should be
1232 # found on another page. Perf test whether it is better
1233 # to check if this node should fit on this page or not.
1234 # in the 'everything-in-one-pack' scenario, this *not*
1235 # doing the check is 237ms vs 243ms.
1236 # So slightly better, but I assume the standard 'lots
1237 # of packs' is going to show a reasonable improvement
1238 # from the check, because it avoids 'going around
1239 # again' for everything that is in another index
1240 # parents_not_on_page.add(key)
1241 # Missing for some reason
1242 if key < node.min_key:
1243 # in the case of bzr.dev, 3.4k/5.3k misses are
1244 # 'earlier' misses (65%)
1245 parents_not_on_page.add(key)
1246 elif key > node.max_key:
1247 # This parent key would be present on a different
1248 # LeafNode
1249 parents_not_on_page.add(key)
1250 else:
1251 # assert key != node.min_key and key != node.max_key
1252 # If it was going to be present, it would be on
1253 # *this* page, so mark it missing.
1254 missing_keys.add(key)
1255 parents_to_check = next_parents_to_check.difference(parent_map)
1256 # Might want to do another .difference() from missing_keys
1257 # parents_not_on_page could have been found on a different page, or be
1258 # known to be missing. So cull out everything that has already been
1259 # found.
1260 search_keys = parents_not_on_page.difference(
1261 parent_map).difference(missing_keys)
1262 return search_keys
1263
1123 def iter_entries_prefix(self, keys):1264 def iter_entries_prefix(self, keys):
1124 """Iterate over keys within the index using prefix matching.1265 """Iterate over keys within the index using prefix matching.
11251266
11261267
=== modified file 'bzrlib/builtins.py'
--- bzrlib/builtins.py 2009-07-27 06:22:57 +0000
+++ bzrlib/builtins.py 2009-09-08 16:45:11 +0000
@@ -120,6 +120,15 @@
120120
121121
122def _get_one_revision_tree(command_name, revisions, branch=None, tree=None):122def _get_one_revision_tree(command_name, revisions, branch=None, tree=None):
123 """Get a revision tree. Not suitable for commands that change the tree.
124
125 Specifically, the basis tree in dirstate trees is coupled to the dirstate
126 and doing a commit/uncommit/pull will at best fail due to changing the
127 basis revision data.
128
129 If tree is passed in, it should be already locked, for lifetime management
130 of the trees internal cached state.
131 """
123 if branch is None:132 if branch is None:
124 branch = tree.branch133 branch = tree.branch
125 if revisions is None:134 if revisions is None:
@@ -452,8 +461,8 @@
452 raise errors.BzrCommandError("You cannot remove the working tree"461 raise errors.BzrCommandError("You cannot remove the working tree"
453 " of a remote path")462 " of a remote path")
454 if not force:463 if not force:
455 # XXX: What about pending merges ? -- vila 20090629464 if (working.has_changes(working.basis_tree())
456 if working.has_changes(working.basis_tree()):465 or len(working.get_parent_ids()) > 1):
457 raise errors.UncommittedChanges(working)466 raise errors.UncommittedChanges(working)
458467
459 working_path = working.bzrdir.root_transport.base468 working_path = working.bzrdir.root_transport.base
@@ -603,6 +612,9 @@
603 branches that will be merged later (without showing the two different612 branches that will be merged later (without showing the two different
604 adds as a conflict). It is also useful when merging another project613 adds as a conflict). It is also useful when merging another project
605 into a subdirectory of this one.614 into a subdirectory of this one.
615
616 Any files matching patterns in the ignore list will not be added
617 unless they are explicitly mentioned.
606 """618 """
607 takes_args = ['file*']619 takes_args = ['file*']
608 takes_options = [620 takes_options = [
@@ -616,7 +628,7 @@
616 help='Lookup file ids from this tree.'),628 help='Lookup file ids from this tree.'),
617 ]629 ]
618 encoding_type = 'replace'630 encoding_type = 'replace'
619 _see_also = ['remove']631 _see_also = ['remove', 'ignore']
620632
621 def run(self, file_list, no_recurse=False, dry_run=False, verbose=False,633 def run(self, file_list, no_recurse=False, dry_run=False, verbose=False,
622 file_ids_from=None):634 file_ids_from=None):
@@ -654,14 +666,6 @@
654 for path in ignored[glob]:666 for path in ignored[glob]:
655 self.outf.write("ignored %s matching \"%s\"\n"667 self.outf.write("ignored %s matching \"%s\"\n"
656 % (path, glob))668 % (path, glob))
657 else:
658 match_len = 0
659 for glob, paths in ignored.items():
660 match_len += len(paths)
661 self.outf.write("ignored %d file(s).\n" % match_len)
662 self.outf.write("If you wish to add ignored files, "
663 "please add them explicitly by name. "
664 "(\"bzr ignored\" gives a list)\n")
665669
666670
667class cmd_mkdir(Command):671class cmd_mkdir(Command):
@@ -1172,6 +1176,9 @@
1172 help='Hard-link working tree files where possible.'),1176 help='Hard-link working tree files where possible.'),
1173 Option('no-tree',1177 Option('no-tree',
1174 help="Create a branch without a working-tree."),1178 help="Create a branch without a working-tree."),
1179 Option('switch',
1180 help="Switch the checkout in the current directory "
1181 "to the new branch."),
1175 Option('stacked',1182 Option('stacked',
1176 help='Create a stacked branch referring to the source branch. '1183 help='Create a stacked branch referring to the source branch. '
1177 'The new branch will depend on the availability of the source '1184 'The new branch will depend on the availability of the source '
@@ -1188,9 +1195,9 @@
11881195
1189 def run(self, from_location, to_location=None, revision=None,1196 def run(self, from_location, to_location=None, revision=None,
1190 hardlink=False, stacked=False, standalone=False, no_tree=False,1197 hardlink=False, stacked=False, standalone=False, no_tree=False,
1191 use_existing_dir=False):1198 use_existing_dir=False, switch=False):
1199 from bzrlib import switch as _mod_switch
1192 from bzrlib.tag import _merge_tags_if_possible1200 from bzrlib.tag import _merge_tags_if_possible
1193
1194 accelerator_tree, br_from = bzrdir.BzrDir.open_tree_or_branch(1201 accelerator_tree, br_from = bzrdir.BzrDir.open_tree_or_branch(
1195 from_location)1202 from_location)
1196 if (accelerator_tree is not None and1203 if (accelerator_tree is not None and
@@ -1250,6 +1257,12 @@
1250 except (errors.NotStacked, errors.UnstackableBranchFormat,1257 except (errors.NotStacked, errors.UnstackableBranchFormat,
1251 errors.UnstackableRepositoryFormat), e:1258 errors.UnstackableRepositoryFormat), e:
1252 note('Branched %d revision(s).' % branch.revno())1259 note('Branched %d revision(s).' % branch.revno())
1260 if switch:
1261 # Switch to the new branch
1262 wt, _ = WorkingTree.open_containing('.')
1263 _mod_switch.switch(wt.bzrdir, branch)
1264 note('Switched to branch: %s',
1265 urlutils.unescape_for_display(branch.base, 'utf-8'))
1253 finally:1266 finally:
1254 br_from.unlock()1267 br_from.unlock()
12551268
@@ -3025,6 +3038,10 @@
3025 raise errors.BzrCommandError("empty commit message specified")3038 raise errors.BzrCommandError("empty commit message specified")
3026 return my_message3039 return my_message
30273040
3041 # The API permits a commit with a filter of [] to mean 'select nothing'
3042 # but the command line should not do that.
3043 if not selected_list:
3044 selected_list = None
3028 try:3045 try:
3029 tree.commit(message_callback=get_message,3046 tree.commit(message_callback=get_message,
3030 specific_files=selected_list,3047 specific_files=selected_list,
@@ -3365,6 +3382,8 @@
3365 Option('lsprof-timed',3382 Option('lsprof-timed',
3366 help='Generate lsprof output for benchmarked'3383 help='Generate lsprof output for benchmarked'
3367 ' sections of code.'),3384 ' sections of code.'),
3385 Option('lsprof-tests',
3386 help='Generate lsprof output for each test.'),
3368 Option('cache-dir', type=str,3387 Option('cache-dir', type=str,
3369 help='Cache intermediate benchmark output in this '3388 help='Cache intermediate benchmark output in this '
3370 'directory.'),3389 'directory.'),
@@ -3411,7 +3430,7 @@
3411 first=False, list_only=False,3430 first=False, list_only=False,
3412 randomize=None, exclude=None, strict=False,3431 randomize=None, exclude=None, strict=False,
3413 load_list=None, debugflag=None, starting_with=None, subunit=False,3432 load_list=None, debugflag=None, starting_with=None, subunit=False,
3414 parallel=None):3433 parallel=None, lsprof_tests=False):
3415 from bzrlib.tests import selftest3434 from bzrlib.tests import selftest
3416 import bzrlib.benchmarks as benchmarks3435 import bzrlib.benchmarks as benchmarks
3417 from bzrlib.benchmarks import tree_creator3436 from bzrlib.benchmarks import tree_creator
@@ -3451,6 +3470,7 @@
3451 "transport": transport,3470 "transport": transport,
3452 "test_suite_factory": test_suite_factory,3471 "test_suite_factory": test_suite_factory,
3453 "lsprof_timed": lsprof_timed,3472 "lsprof_timed": lsprof_timed,
3473 "lsprof_tests": lsprof_tests,
3454 "bench_history": benchfile,3474 "bench_history": benchfile,
3455 "matching_tests_first": first,3475 "matching_tests_first": first,
3456 "list_only": list_only,3476 "list_only": list_only,
@@ -3633,13 +3653,14 @@
3633 verified = 'inapplicable'3653 verified = 'inapplicable'
3634 tree = WorkingTree.open_containing(directory)[0]3654 tree = WorkingTree.open_containing(directory)[0]
36353655
3636 # die as quickly as possible if there are uncommitted changes
3637 try:3656 try:
3638 basis_tree = tree.revision_tree(tree.last_revision())3657 basis_tree = tree.revision_tree(tree.last_revision())
3639 except errors.NoSuchRevision:3658 except errors.NoSuchRevision:
3640 basis_tree = tree.basis_tree()3659 basis_tree = tree.basis_tree()
3660
3661 # die as quickly as possible if there are uncommitted changes
3641 if not force:3662 if not force:
3642 if tree.has_changes(basis_tree):3663 if tree.has_changes(basis_tree) or len(tree.get_parent_ids()) > 1:
3643 raise errors.UncommittedChanges(tree)3664 raise errors.UncommittedChanges(tree)
36443665
3645 view_info = _get_view_info_for_change_reporter(tree)3666 view_info = _get_view_info_for_change_reporter(tree)
@@ -5627,8 +5648,12 @@
5627 if writer is None:5648 if writer is None:
5628 writer = bzrlib.option.diff_writer_registry.get()5649 writer = bzrlib.option.diff_writer_registry.get()
5629 try:5650 try:
5630 Shelver.from_args(writer(sys.stdout), revision, all, file_list,5651 shelver = Shelver.from_args(writer(sys.stdout), revision, all,
5631 message, destroy=destroy).run()5652 file_list, message, destroy=destroy)
5653 try:
5654 shelver.run()
5655 finally:
5656 shelver.work_tree.unlock()
5632 except errors.UserAbort:5657 except errors.UserAbort:
5633 return 05658 return 0
56345659
@@ -5673,7 +5698,11 @@
56735698
5674 def run(self, shelf_id=None, action='apply'):5699 def run(self, shelf_id=None, action='apply'):
5675 from bzrlib.shelf_ui import Unshelver5700 from bzrlib.shelf_ui import Unshelver
5676 Unshelver.from_args(shelf_id, action).run()5701 unshelver = Unshelver.from_args(shelf_id, action)
5702 try:
5703 unshelver.run()
5704 finally:
5705 unshelver.tree.unlock()
56775706
56785707
5679class cmd_clean_tree(Command):5708class cmd_clean_tree(Command):
56805709
=== modified file 'bzrlib/bzrdir.py'
--- bzrlib/bzrdir.py 2009-08-04 14:48:59 +0000
+++ bzrlib/bzrdir.py 2009-08-21 02:10:06 +0000
@@ -129,9 +129,16 @@
129 return True129 return True
130130
131 def check_conversion_target(self, target_format):131 def check_conversion_target(self, target_format):
132 """Check that a bzrdir as a whole can be converted to a new format."""
133 # The only current restriction is that the repository content can be
134 # fetched compatibly with the target.
132 target_repo_format = target_format.repository_format135 target_repo_format = target_format.repository_format
133 source_repo_format = self._format.repository_format136 try:
134 source_repo_format.check_conversion_target(target_repo_format)137 self.open_repository()._format.check_conversion_target(
138 target_repo_format)
139 except errors.NoRepositoryPresent:
140 # No repo, no problem.
141 pass
135142
136 @staticmethod143 @staticmethod
137 def _check_supported(format, allow_unsupported,144 def _check_supported(format, allow_unsupported,
@@ -3039,7 +3046,8 @@
3039 new is _mod_branch.BzrBranchFormat8):3046 new is _mod_branch.BzrBranchFormat8):
3040 branch_converter = _mod_branch.Converter7to8()3047 branch_converter = _mod_branch.Converter7to8()
3041 else:3048 else:
3042 raise errors.BadConversionTarget("No converter", new)3049 raise errors.BadConversionTarget("No converter", new,
3050 branch._format)
3043 branch_converter.convert(branch)3051 branch_converter.convert(branch)
3044 branch = self.bzrdir.open_branch()3052 branch = self.bzrdir.open_branch()
3045 old = branch._format.__class__3053 old = branch._format.__class__
@@ -3548,6 +3556,10 @@
3548 if self._require_stacking:3556 if self._require_stacking:
3549 raise3557 raise
35503558
3559 def requires_stacking(self):
3560 """Return True if this policy requires stacking."""
3561 return self._stack_on is not None and self._require_stacking
3562
3551 def _get_full_stack_on(self):3563 def _get_full_stack_on(self):
3552 """Get a fully-qualified URL for the stack_on location."""3564 """Get a fully-qualified URL for the stack_on location."""
3553 if self._stack_on is None:3565 if self._stack_on is None:
@@ -3860,11 +3872,11 @@
3860# The following format should be an alias for the rich root equivalent 3872# The following format should be an alias for the rich root equivalent
3861# of the default format3873# of the default format
3862format_registry.register_metadir('default-rich-root',3874format_registry.register_metadir('default-rich-root',
3863 'bzrlib.repofmt.pack_repo.RepositoryFormatKnitPack4',3875 'bzrlib.repofmt.groupcompress_repo.RepositoryFormat2a',
3864 help='Default format, rich root variant. (needed for bzr-svn and bzr-git).',3876 branch_format='bzrlib.branch.BzrBranchFormat7',
3865 branch_format='bzrlib.branch.BzrBranchFormat6',3877 tree_format='bzrlib.workingtree.WorkingTreeFormat6',
3866 tree_format='bzrlib.workingtree.WorkingTreeFormat4',
3867 alias=True,3878 alias=True,
3868 )3879 help='Same as 2a.')
3880
3869# The current format that is made on 'bzr init'.3881# The current format that is made on 'bzr init'.
3870format_registry.set_default('pack-0.92')3882format_registry.set_default('2a')
38713883
=== modified file 'bzrlib/commands.py'
--- bzrlib/commands.py 2009-06-19 09:06:56 +0000
+++ bzrlib/commands.py 2009-09-09 17:51:19 +0000
@@ -1028,13 +1028,13 @@
1028 ret = apply_coveraged(opt_coverage_dir, run, *run_argv)1028 ret = apply_coveraged(opt_coverage_dir, run, *run_argv)
1029 else:1029 else:
1030 ret = run(*run_argv)1030 ret = run(*run_argv)
1031 if 'memory' in debug.debug_flags:
1032 trace.debug_memory('Process status after command:', short=False)
1033 return ret or 01031 return ret or 0
1034 finally:1032 finally:
1035 # reset, in case we may do other commands later within the same1033 # reset, in case we may do other commands later within the same
1036 # process. Commands that want to execute sub-commands must propagate1034 # process. Commands that want to execute sub-commands must propagate
1037 # --verbose in their own way.1035 # --verbose in their own way.
1036 if 'memory' in debug.debug_flags:
1037 trace.debug_memory('Process status after command:', short=False)
1038 option._verbosity_level = saved_verbosity_level1038 option._verbosity_level = saved_verbosity_level
10391039
10401040
10411041
=== modified file 'bzrlib/commit.py'
--- bzrlib/commit.py 2009-08-25 05:09:42 +0000
+++ bzrlib/commit.py 2009-08-28 05:00:33 +0000
@@ -209,7 +209,8 @@
209 :param timestamp: if not None, seconds-since-epoch for a209 :param timestamp: if not None, seconds-since-epoch for a
210 postdated/predated commit.210 postdated/predated commit.
211211
212 :param specific_files: If true, commit only those files.212 :param specific_files: If not None, commit only those files. An empty
213 list means 'commit no files'.
213214
214 :param rev_id: If set, use this as the new revision id.215 :param rev_id: If set, use this as the new revision id.
215 Useful for test or import commands that need to tightly216 Useful for test or import commands that need to tightly
@@ -264,6 +265,8 @@
264 self.master_locked = False265 self.master_locked = False
265 self.recursive = recursive266 self.recursive = recursive
266 self.rev_id = None267 self.rev_id = None
268 # self.specific_files is None to indicate no filter, or any iterable to
269 # indicate a filter - [] means no files at all, as per iter_changes.
267 if specific_files is not None:270 if specific_files is not None:
268 self.specific_files = sorted(271 self.specific_files = sorted(
269 minimum_path_selection(specific_files))272 minimum_path_selection(specific_files))
@@ -285,7 +288,6 @@
285 # the command line parameters, and the repository has fast delta288 # the command line parameters, and the repository has fast delta
286 # generation. See bug 347649.289 # generation. See bug 347649.
287 self.use_record_iter_changes = (290 self.use_record_iter_changes = (
288 not self.specific_files and
289 not self.exclude and 291 not self.exclude and
290 not self.branch.repository._format.supports_tree_reference and292 not self.branch.repository._format.supports_tree_reference and
291 (self.branch.repository._format.fast_deltas or293 (self.branch.repository._format.fast_deltas or
@@ -333,7 +335,7 @@
333 self._gather_parents()335 self._gather_parents()
334 # After a merge, a selected file commit is not supported.336 # After a merge, a selected file commit is not supported.
335 # See 'bzr help merge' for an explanation as to why.337 # See 'bzr help merge' for an explanation as to why.
336 if len(self.parents) > 1 and self.specific_files:338 if len(self.parents) > 1 and self.specific_files is not None:
337 raise errors.CannotCommitSelectedFileMerge(self.specific_files)339 raise errors.CannotCommitSelectedFileMerge(self.specific_files)
338 # Excludes are a form of selected file commit.340 # Excludes are a form of selected file commit.
339 if len(self.parents) > 1 and self.exclude:341 if len(self.parents) > 1 and self.exclude:
@@ -619,12 +621,13 @@
619 """Update the commit builder with the data about what has changed.621 """Update the commit builder with the data about what has changed.
620 """622 """
621 exclude = self.exclude623 exclude = self.exclude
622 specific_files = self.specific_files or []624 specific_files = self.specific_files
623 mutter("Selecting files for commit with filter %s", specific_files)625 mutter("Selecting files for commit with filter %s", specific_files)
624626
625 self._check_strict()627 self._check_strict()
626 if self.use_record_iter_changes:628 if self.use_record_iter_changes:
627 iter_changes = self.work_tree.iter_changes(self.basis_tree)629 iter_changes = self.work_tree.iter_changes(self.basis_tree,
630 specific_files=specific_files)
628 iter_changes = self._filter_iter_changes(iter_changes)631 iter_changes = self._filter_iter_changes(iter_changes)
629 for file_id, path, fs_hash in self.builder.record_iter_changes(632 for file_id, path, fs_hash in self.builder.record_iter_changes(
630 self.work_tree, self.basis_revid, iter_changes):633 self.work_tree, self.basis_revid, iter_changes):
631634
=== modified file 'bzrlib/config.py'
--- bzrlib/config.py 2009-07-02 08:59:16 +0000
+++ bzrlib/config.py 2009-08-20 04:53:23 +0000
@@ -821,6 +821,29 @@
821 return osutils.pathjoin(config_dir(), 'ignore')821 return osutils.pathjoin(config_dir(), 'ignore')
822822
823823
824def crash_dir():
825 """Return the directory name to store crash files.
826
827 This doesn't implicitly create it.
828
829 On Windows it's in the config directory; elsewhere in the XDG cache directory.
830 """
831 if sys.platform == 'win32':
832 return osutils.pathjoin(config_dir(), 'Crash')
833 else:
834 return osutils.pathjoin(xdg_cache_dir(), 'crash')
835
836
837def xdg_cache_dir():
838 # See http://standards.freedesktop.org/basedir-spec/latest/ar01s03.html
839 # Possibly this should be different on Windows?
840 e = os.environ.get('XDG_CACHE_DIR', None)
841 if e:
842 return e
843 else:
844 return os.path.expanduser('~/.cache')
845
846
824def _auto_user_id():847def _auto_user_id():
825 """Calculate automatic user identification.848 """Calculate automatic user identification.
826849
827850
=== added file 'bzrlib/crash.py'
--- bzrlib/crash.py 1970-01-01 00:00:00 +0000
+++ bzrlib/crash.py 2009-08-20 05:47:53 +0000
@@ -0,0 +1,163 @@
1# Copyright (C) 2009 Canonical Ltd
2#
3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation; either version 2 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program; if not, write to the Free Software
15# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
17
18"""Handling and reporting crashes.
19"""
20
21# for interactive testing, try the 'bzr assert-fail' command
22# or see http://code.launchpad.net/~mbp/bzr/bzr-fail
23
24import os
25import platform
26import pprint
27import sys
28import time
29from StringIO import StringIO
30
31import bzrlib
32from bzrlib import (
33 config,
34 debug,
35 osutils,
36 plugin,
37 trace,
38 )
39
40
41def report_bug(exc_info, stderr):
42 if 'no_apport' not in debug.debug_flags:
43 try:
44 report_bug_to_apport(exc_info, stderr)
45 return
46 except ImportError, e:
47 trace.mutter("couldn't find apport bug-reporting library: %s" % e)
48 pass
49 except Exception, e:
50 # this should only happen if apport is installed but it didn't
51 # work, eg because of an io error writing the crash file
52 sys.stderr.write("bzr: failed to report crash using apport:\n "
53 " %r\n" % e)
54 pass
55 report_bug_legacy(exc_info, stderr)
56
57
58def report_bug_legacy(exc_info, err_file):
59 """Report a bug by just printing a message to the user."""
60 trace.print_exception(exc_info, err_file)
61 err_file.write('\n')
62 err_file.write('bzr %s on python %s (%s)\n' % \
63 (bzrlib.__version__,
64 bzrlib._format_version_tuple(sys.version_info),
65 platform.platform(aliased=1)))
66 err_file.write('arguments: %r\n' % sys.argv)
67 err_file.write(
68 'encoding: %r, fsenc: %r, lang: %r\n' % (
69 osutils.get_user_encoding(), sys.getfilesystemencoding(),
70 os.environ.get('LANG')))
71 err_file.write("plugins:\n")
72 err_file.write(_format_plugin_list())
73 err_file.write(
74 "\n\n"
75 "*** Bazaar has encountered an internal error. This probably indicates a\n"
76 " bug in Bazaar. You can help us fix it by filing a bug report at\n"
77 " https://bugs.launchpad.net/bzr/+filebug\n"
78 " including this traceback and a description of the problem.\n"
79 )
80
81
82def report_bug_to_apport(exc_info, stderr):
83 """Report a bug to apport for optional automatic filing.
84 """
85 # this is based on apport_package_hook.py, but omitting some of the
86 # Ubuntu-specific policy about what to report and when
87
88 # if this fails its caught at a higher level; we don't want to open the
89 # crash file unless apport can be loaded.
90 import apport
91
92 crash_file = _open_crash_file()
93 try:
94 _write_apport_report_to_file(exc_info, crash_file)
95 finally:
96 crash_file.close()
97
98 stderr.write("bzr: ERROR: %s.%s: %s\n"
99 "\n"
100 "*** Bazaar has encountered an internal error. This probably indicates a\n"
101 " bug in Bazaar. You can help us fix it by filing a bug report at\n"
102 " https://bugs.launchpad.net/bzr/+filebug\n"
103 " attaching the crash file\n"
104 " %s\n"
105 " and including a description of the problem.\n"
106 "\n"
107 " The crash file is plain text and you can inspect or edit it to remove\n"
108 " private information.\n"
109 % (exc_info[0].__module__, exc_info[0].__name__, exc_info[1],
110 crash_file.name))
111
112
113def _write_apport_report_to_file(exc_info, crash_file):
114 import traceback
115 from apport.report import Report
116
117 exc_type, exc_object, exc_tb = exc_info
118
119 pr = Report()
120 # add_proc_info gives you the memory map of the process: this seems rarely
121 # useful for Bazaar and it does make the report harder to scan, though it
122 # does tell you what binary modules are loaded.
123 # pr.add_proc_info()
124 pr.add_user_info()
125 pr['CommandLine'] = pprint.pformat(sys.argv)
126 pr['BzrVersion'] = bzrlib.__version__
127 pr['PythonVersion'] = bzrlib._format_version_tuple(sys.version_info)
128 pr['Platform'] = platform.platform(aliased=1)
129 pr['UserEncoding'] = osutils.get_user_encoding()
130 pr['FileSystemEncoding'] = sys.getfilesystemencoding()
131 pr['Locale'] = os.environ.get('LANG')
132 pr['BzrPlugins'] = _format_plugin_list()
133 pr['PythonLoadedModules'] = _format_module_list()
134 pr['BzrDebugFlags'] = pprint.pformat(debug.debug_flags)
135
136 tb_file = StringIO()
137 traceback.print_exception(exc_type, exc_object, exc_tb, file=tb_file)
138 pr['Traceback'] = tb_file.getvalue()
139
140 pr.write(crash_file)
141
142
143def _open_crash_file():
144 crash_dir = config.crash_dir()
145 # user-readable only, just in case the contents are sensitive.
146 if not osutils.isdir(crash_dir):
147 os.makedirs(crash_dir, mode=0700)
148 filename = 'bzr-%s-%s.crash' % (
149 osutils.compact_date(time.time()),
150 os.getpid(),)
151 return open(osutils.pathjoin(crash_dir, filename), 'wt')
152
153
154def _format_plugin_list():
155 plugin_lines = []
156 for name, a_plugin in sorted(plugin.plugins().items()):
157 plugin_lines.append(" %-20s %s [%s]" %
158 (name, a_plugin.path(), a_plugin.__version__))
159 return '\n'.join(plugin_lines)
160
161
162def _format_module_list():
163 return pprint.pformat(sys.modules)
0164
=== modified file 'bzrlib/dirstate.py'
--- bzrlib/dirstate.py 2009-07-27 05:44:19 +0000
+++ bzrlib/dirstate.py 2009-08-28 05:00:33 +0000
@@ -3166,15 +3166,18 @@
31663166
3167 __slots__ = ["old_dirname_to_file_id", "new_dirname_to_file_id",3167 __slots__ = ["old_dirname_to_file_id", "new_dirname_to_file_id",
3168 "last_source_parent", "last_target_parent", "include_unchanged",3168 "last_source_parent", "last_target_parent", "include_unchanged",
3169 "use_filesystem_for_exec", "utf8_decode", "searched_specific_files",3169 "partial", "use_filesystem_for_exec", "utf8_decode",
3170 "search_specific_files", "state", "source_index", "target_index",3170 "searched_specific_files", "search_specific_files",
3171 "want_unversioned", "tree"]3171 "searched_exact_paths", "search_specific_file_parents", "seen_ids",
3172 "state", "source_index", "target_index", "want_unversioned", "tree"]
31723173
3173 def __init__(self, include_unchanged, use_filesystem_for_exec,3174 def __init__(self, include_unchanged, use_filesystem_for_exec,
3174 search_specific_files, state, source_index, target_index,3175 search_specific_files, state, source_index, target_index,
3175 want_unversioned, tree):3176 want_unversioned, tree):
3176 self.old_dirname_to_file_id = {}3177 self.old_dirname_to_file_id = {}
3177 self.new_dirname_to_file_id = {}3178 self.new_dirname_to_file_id = {}
3179 # Are we doing a partial iter_changes?
3180 self.partial = search_specific_files != set([''])
3178 # Using a list so that we can access the values and change them in3181 # Using a list so that we can access the values and change them in
3179 # nested scope. Each one is [path, file_id, entry]3182 # nested scope. Each one is [path, file_id, entry]
3180 self.last_source_parent = [None, None]3183 self.last_source_parent = [None, None]
@@ -3183,14 +3186,25 @@
3183 self.use_filesystem_for_exec = use_filesystem_for_exec3186 self.use_filesystem_for_exec = use_filesystem_for_exec
3184 self.utf8_decode = cache_utf8._utf8_decode3187 self.utf8_decode = cache_utf8._utf8_decode
3185 # for all search_indexs in each path at or under each element of3188 # for all search_indexs in each path at or under each element of
3186 # search_specific_files, if the detail is relocated: add the id, and add the3189 # search_specific_files, if the detail is relocated: add the id, and
3187 # relocated path as one to search if its not searched already. If the3190 # add the relocated path as one to search if its not searched already.
3188 # detail is not relocated, add the id.3191 # If the detail is not relocated, add the id.
3189 self.searched_specific_files = set()3192 self.searched_specific_files = set()
3193 # When we search exact paths without expanding downwards, we record
3194 # that here.
3195 self.searched_exact_paths = set()
3190 self.search_specific_files = search_specific_files3196 self.search_specific_files = search_specific_files
3197 # The parents up to the root of the paths we are searching.
3198 # After all normal paths are returned, these specific items are returned.
3199 self.search_specific_file_parents = set()
3200 # The ids we've sent out in the delta.
3201 self.seen_ids = set()
3191 self.state = state3202 self.state = state
3192 self.source_index = source_index3203 self.source_index = source_index
3193 self.target_index = target_index3204 self.target_index = target_index
3205 if target_index != 0:
3206 # A lot of code in here depends on target_index == 0
3207 raise errors.BzrError('unsupported target index')
3194 self.want_unversioned = want_unversioned3208 self.want_unversioned = want_unversioned
3195 self.tree = tree3209 self.tree = tree
31963210
@@ -3198,15 +3212,15 @@
3198 """Compare an entry and real disk to generate delta information.3212 """Compare an entry and real disk to generate delta information.
31993213
3200 :param path_info: top_relpath, basename, kind, lstat, abspath for3214 :param path_info: top_relpath, basename, kind, lstat, abspath for
3201 the path of entry. If None, then the path is considered absent.3215 the path of entry. If None, then the path is considered absent in
3202 (Perhaps we should pass in a concrete entry for this ?)3216 the target (Perhaps we should pass in a concrete entry for this ?)
3203 Basename is returned as a utf8 string because we expect this3217 Basename is returned as a utf8 string because we expect this
3204 tuple will be ignored, and don't want to take the time to3218 tuple will be ignored, and don't want to take the time to
3205 decode.3219 decode.
3206 :return: (iter_changes_result, changed). If the entry has not been3220 :return: (iter_changes_result, changed). If the entry has not been
3207 handled then changed is None. Otherwise it is False if no content3221 handled then changed is None. Otherwise it is False if no content
3208 or metadata changes have occured, and None if any content or3222 or metadata changes have occurred, and True if any content or
3209 metadata change has occured. If self.include_unchanged is True then3223 metadata change has occurred. If self.include_unchanged is True then
3210 if changed is not None, iter_changes_result will always be a result3224 if changed is not None, iter_changes_result will always be a result
3211 tuple. Otherwise, iter_changes_result is None unless changed is3225 tuple. Otherwise, iter_changes_result is None unless changed is
3212 True.3226 True.
@@ -3463,6 +3477,25 @@
3463 def __iter__(self):3477 def __iter__(self):
3464 return self3478 return self
34653479
3480 def _gather_result_for_consistency(self, result):
3481 """Check a result we will yield to make sure we are consistent later.
3482
3483 This gathers result's parents into a set to output later.
3484
3485 :param result: A result tuple.
3486 """
3487 if not self.partial or not result[0]:
3488 return
3489 self.seen_ids.add(result[0])
3490 new_path = result[1][1]
3491 if new_path:
3492 # Not the root and not a delete: queue up the parents of the path.
3493 self.search_specific_file_parents.update(
3494 osutils.parent_directories(new_path.encode('utf8')))
3495 # Add the root directory which parent_directories does not
3496 # provide.
3497 self.search_specific_file_parents.add('')
3498
3466 def iter_changes(self):3499 def iter_changes(self):
3467 """Iterate over the changes."""3500 """Iterate over the changes."""
3468 utf8_decode = cache_utf8._utf8_decode3501 utf8_decode = cache_utf8._utf8_decode
@@ -3546,6 +3579,8 @@
3546 result, changed = _process_entry(entry, root_dir_info)3579 result, changed = _process_entry(entry, root_dir_info)
3547 if changed is not None:3580 if changed is not None:
3548 path_handled = True3581 path_handled = True
3582 if changed:
3583 self._gather_result_for_consistency(result)
3549 if changed or self.include_unchanged:3584 if changed or self.include_unchanged:
3550 yield result3585 yield result
3551 if self.want_unversioned and not path_handled and root_dir_info:3586 if self.want_unversioned and not path_handled and root_dir_info:
@@ -3664,6 +3699,8 @@
3664 # advance the entry only, after processing.3699 # advance the entry only, after processing.
3665 result, changed = _process_entry(current_entry, None)3700 result, changed = _process_entry(current_entry, None)
3666 if changed is not None:3701 if changed is not None:
3702 if changed:
3703 self._gather_result_for_consistency(result)
3667 if changed or self.include_unchanged:3704 if changed or self.include_unchanged:
3668 yield result3705 yield result
3669 block_index +=13706 block_index +=1
@@ -3702,6 +3739,8 @@
3702 # no path is fine: the per entry code will handle it.3739 # no path is fine: the per entry code will handle it.
3703 result, changed = _process_entry(current_entry, current_path_info)3740 result, changed = _process_entry(current_entry, current_path_info)
3704 if changed is not None:3741 if changed is not None:
3742 if changed:
3743 self._gather_result_for_consistency(result)
3705 if changed or self.include_unchanged:3744 if changed or self.include_unchanged:
3706 yield result3745 yield result
3707 elif (current_entry[0][1] != current_path_info[1]3746 elif (current_entry[0][1] != current_path_info[1]
@@ -3723,6 +3762,8 @@
3723 # advance the entry only, after processing.3762 # advance the entry only, after processing.
3724 result, changed = _process_entry(current_entry, None)3763 result, changed = _process_entry(current_entry, None)
3725 if changed is not None:3764 if changed is not None:
3765 if changed:
3766 self._gather_result_for_consistency(result)
3726 if changed or self.include_unchanged:3767 if changed or self.include_unchanged:
3727 yield result3768 yield result
3728 advance_path = False3769 advance_path = False
@@ -3730,6 +3771,8 @@
3730 result, changed = _process_entry(current_entry, current_path_info)3771 result, changed = _process_entry(current_entry, current_path_info)
3731 if changed is not None:3772 if changed is not None:
3732 path_handled = True3773 path_handled = True
3774 if changed:
3775 self._gather_result_for_consistency(result)
3733 if changed or self.include_unchanged:3776 if changed or self.include_unchanged:
3734 yield result3777 yield result
3735 if advance_entry and current_entry is not None:3778 if advance_entry and current_entry is not None:
@@ -3795,6 +3838,124 @@
3795 current_dir_info = dir_iterator.next()3838 current_dir_info = dir_iterator.next()
3796 except StopIteration:3839 except StopIteration:
3797 current_dir_info = None3840 current_dir_info = None
3841 for result in self._iter_specific_file_parents():
3842 yield result
3843
3844 def _iter_specific_file_parents(self):
3845 """Iter over the specific file parents."""
3846 while self.search_specific_file_parents:
3847 # Process the parent directories for the paths we were iterating.
3848 # Even in extremely large trees this should be modest, so currently
3849 # no attempt is made to optimise.
3850 path_utf8 = self.search_specific_file_parents.pop()
3851 if osutils.is_inside_any(self.searched_specific_files, path_utf8):
3852 # We've examined this path.
3853 continue
3854 if path_utf8 in self.searched_exact_paths:
3855 # We've examined this path.
3856 continue
3857 path_entries = self.state._entries_for_path(path_utf8)
3858 # We need either one or two entries. If the path in
3859 # self.target_index has moved (so the entry in source_index is in
3860 # 'ar') then we need to also look for the entry for this path in
3861 # self.source_index, to output the appropriate delete-or-rename.
3862 selected_entries = []
3863 found_item = False
3864 for candidate_entry in path_entries:
3865 # Find entries present in target at this path:
3866 if candidate_entry[1][self.target_index][0] not in 'ar':
3867 found_item = True
3868 selected_entries.append(candidate_entry)
3869 # Find entries present in source at this path:
3870 elif (self.source_index is not None and
3871 candidate_entry[1][self.source_index][0] not in 'ar'):
3872 found_item = True
3873 if candidate_entry[1][self.target_index][0] == 'a':
3874 # Deleted, emit it here.
3875 selected_entries.append(candidate_entry)
3876 else:
3877 # renamed, emit it when we process the directory it
3878 # ended up at.
3879 self.search_specific_file_parents.add(
3880 candidate_entry[1][self.target_index][1])
3881 if not found_item:
3882 raise AssertionError(
3883 "Missing entry for specific path parent %r, %r" % (
3884 path_utf8, path_entries))
3885 path_info = self._path_info(path_utf8, path_utf8.decode('utf8'))
3886 for entry in selected_entries:
3887 if entry[0][2] in self.seen_ids:
3888 continue
3889 result, changed = self._process_entry(entry, path_info)
3890 if changed is None:
3891 raise AssertionError(
3892 "Got entry<->path mismatch for specific path "
3893 "%r entry %r path_info %r " % (
3894 path_utf8, entry, path_info))
3895 # Only include changes - we're outside the users requested
3896 # expansion.
3897 if changed:
3898 self._gather_result_for_consistency(result)
3899 if (result[6][0] == 'directory' and
3900 result[6][1] != 'directory'):
3901 # This stopped being a directory, the old children have
3902 # to be included.
3903 if entry[1][self.source_index][0] == 'r':
3904 # renamed, take the source path
3905 entry_path_utf8 = entry[1][self.source_index][1]
3906 else:
3907 entry_path_utf8 = path_utf8
3908 initial_key = (entry_path_utf8, '', '')
3909 block_index, _ = self.state._find_block_index_from_key(
3910 initial_key)
3911 if block_index == 0:
3912 # The children of the root are in block index 1.
3913 block_index +=1
3914 current_block = None
3915 if block_index < len(self.state._dirblocks):
3916 current_block = self.state._dirblocks[block_index]
3917 if not osutils.is_inside(
3918 entry_path_utf8, current_block[0]):
3919 # No entries for this directory at all.
3920 current_block = None
3921 if current_block is not None:
3922 for entry in current_block[1]:
3923 if entry[1][self.source_index][0] in 'ar':
3924 # Not in the source tree, so doesn't have to be
3925 # included.
3926 continue
3927 # Path of the entry itself.
3928
3929 self.search_specific_file_parents.add(
3930 osutils.pathjoin(*entry[0][:2]))
3931 if changed or self.include_unchanged:
3932 yield result
3933 self.searched_exact_paths.add(path_utf8)
3934
3935 def _path_info(self, utf8_path, unicode_path):
3936 """Generate path_info for unicode_path.
3937
3938 :return: None if unicode_path does not exist, or a path_info tuple.
3939 """
3940 abspath = self.tree.abspath(unicode_path)
3941 try:
3942 stat = os.lstat(abspath)
3943 except OSError, e:
3944 if e.errno == errno.ENOENT:
3945 # the path does not exist.
3946 return None
3947 else:
3948 raise
3949 utf8_basename = utf8_path.rsplit('/', 1)[-1]
3950 dir_info = (utf8_path, utf8_basename,
3951 osutils.file_kind_from_stat_mode(stat.st_mode), stat,
3952 abspath)
3953 if dir_info[2] == 'directory':
3954 if self.tree._directory_is_tree_reference(
3955 unicode_path):
3956 self.root_dir_info = self.root_dir_info[:2] + \
3957 ('tree-reference',) + self.root_dir_info[3:]
3958 return dir_info
37983959
37993960
3800# Try to load the compiled form if possible3961# Try to load the compiled form if possible
38013962
=== modified file 'bzrlib/errors.py'
--- bzrlib/errors.py 2009-07-14 21:07:36 +0000
+++ bzrlib/errors.py 2009-08-30 21:34:42 +0000
@@ -793,6 +793,12 @@
793793
794794
795class IncompatibleRepositories(BzrError):795class IncompatibleRepositories(BzrError):
796 """Report an error that two repositories are not compatible.
797
798 Note that the source and target repositories are permitted to be strings:
799 this exception is thrown from the smart server and may refer to a
800 repository the client hasn't opened.
801 """
796802
797 _fmt = "%(target)s\n" \803 _fmt = "%(target)s\n" \
798 "is not compatible with\n" \804 "is not compatible with\n" \
@@ -2006,12 +2012,14 @@
20062012
2007class BadConversionTarget(BzrError):2013class BadConversionTarget(BzrError):
20082014
2009 _fmt = "Cannot convert to format %(format)s. %(problem)s"2015 _fmt = "Cannot convert from format %(from_format)s to format %(format)s." \
2016 " %(problem)s"
20102017
2011 def __init__(self, problem, format):2018 def __init__(self, problem, format, from_format=None):
2012 BzrError.__init__(self)2019 BzrError.__init__(self)
2013 self.problem = problem2020 self.problem = problem
2014 self.format = format2021 self.format = format
2022 self.from_format = from_format or '(unspecified)'
20152023
20162024
2017class NoDiffFound(BzrError):2025class NoDiffFound(BzrError):
@@ -2918,8 +2926,9 @@
2918 _fmt = 'Cannot bind address "%(host)s:%(port)i": %(orig_error)s.'2926 _fmt = 'Cannot bind address "%(host)s:%(port)i": %(orig_error)s.'
29192927
2920 def __init__(self, host, port, orig_error):2928 def __init__(self, host, port, orig_error):
2929 # nb: in python2.4 socket.error doesn't have a useful repr
2921 BzrError.__init__(self, host=host, port=port,2930 BzrError.__init__(self, host=host, port=port,
2922 orig_error=orig_error[1])2931 orig_error=repr(orig_error.args))
29232932
29242933
2925class UnknownRules(BzrError):2934class UnknownRules(BzrError):
29262935
=== modified file 'bzrlib/fetch.py'
--- bzrlib/fetch.py 2009-07-09 08:59:51 +0000
+++ bzrlib/fetch.py 2009-08-07 04:29:36 +0000
@@ -25,16 +25,21 @@
2525
26import operator26import operator
2727
28from bzrlib.lazy_import import lazy_import
29lazy_import(globals(), """
30from bzrlib import (
31 tsort,
32 versionedfile,
33 )
34""")
28import bzrlib35import bzrlib
29from bzrlib import (36from bzrlib import (
30 errors,37 errors,
31 symbol_versioning,38 symbol_versioning,
32 )39 )
33from bzrlib.revision import NULL_REVISION40from bzrlib.revision import NULL_REVISION
34from bzrlib.tsort import topo_sort
35from bzrlib.trace import mutter41from bzrlib.trace import mutter
36import bzrlib.ui42import bzrlib.ui
37from bzrlib.versionedfile import FulltextContentFactory
3843
3944
40class RepoFetcher(object):45class RepoFetcher(object):
@@ -213,11 +218,9 @@
213218
214 def _find_root_ids(self, revs, parent_map, graph):219 def _find_root_ids(self, revs, parent_map, graph):
215 revision_root = {}220 revision_root = {}
216 planned_versions = {}
217 for tree in self.iter_rev_trees(revs):221 for tree in self.iter_rev_trees(revs):
218 revision_id = tree.inventory.root.revision222 revision_id = tree.inventory.root.revision
219 root_id = tree.get_root_id()223 root_id = tree.get_root_id()
220 planned_versions.setdefault(root_id, []).append(revision_id)
221 revision_root[revision_id] = root_id224 revision_root[revision_id] = root_id
222 # Find out which parents we don't already know root ids for225 # Find out which parents we don't already know root ids for
223 parents = set()226 parents = set()
@@ -229,7 +232,7 @@
229 for tree in self.iter_rev_trees(parents):232 for tree in self.iter_rev_trees(parents):
230 root_id = tree.get_root_id()233 root_id = tree.get_root_id()
231 revision_root[tree.get_revision_id()] = root_id234 revision_root[tree.get_revision_id()] = root_id
232 return revision_root, planned_versions235 return revision_root
233236
234 def generate_root_texts(self, revs):237 def generate_root_texts(self, revs):
235 """Generate VersionedFiles for all root ids.238 """Generate VersionedFiles for all root ids.
@@ -238,9 +241,8 @@
238 """241 """
239 graph = self.source.get_graph()242 graph = self.source.get_graph()
240 parent_map = graph.get_parent_map(revs)243 parent_map = graph.get_parent_map(revs)
241 rev_order = topo_sort(parent_map)244 rev_order = tsort.topo_sort(parent_map)
242 rev_id_to_root_id, root_id_to_rev_ids = self._find_root_ids(245 rev_id_to_root_id = self._find_root_ids(revs, parent_map, graph)
243 revs, parent_map, graph)
244 root_id_order = [(rev_id_to_root_id[rev_id], rev_id) for rev_id in246 root_id_order = [(rev_id_to_root_id[rev_id], rev_id) for rev_id in
245 rev_order]247 rev_order]
246 # Guaranteed stable, this groups all the file id operations together248 # Guaranteed stable, this groups all the file id operations together
@@ -249,20 +251,93 @@
249 # yet, and are unlikely to in non-rich-root environments anyway.251 # yet, and are unlikely to in non-rich-root environments anyway.
250 root_id_order.sort(key=operator.itemgetter(0))252 root_id_order.sort(key=operator.itemgetter(0))
251 # Create a record stream containing the roots to create.253 # Create a record stream containing the roots to create.
252 def yield_roots():254 from bzrlib.graph import FrozenHeadsCache
253 for key in root_id_order:255 graph = FrozenHeadsCache(graph)
254 root_id, rev_id = key256 new_roots_stream = _new_root_data_stream(
255 rev_parents = parent_map[rev_id]257 root_id_order, rev_id_to_root_id, parent_map, self.source, graph)
256 # We drop revision parents with different file-ids, because258 return [('texts', new_roots_stream)]
257 # that represents a rename of the root to a different location259
258 # - its not actually a parent for us. (We could look for that260
259 # file id in the revision tree at considerably more expense,261def _new_root_data_stream(
260 # but for now this is sufficient (and reconcile will catch and262 root_keys_to_create, rev_id_to_root_id_map, parent_map, repo, graph=None):
261 # correct this anyway).263 """Generate a texts substream of synthesised root entries.
262 # When a parent revision is a ghost, we guess that its root id264
263 # was unchanged (rather than trimming it from the parent list).265 Used in fetches that do rich-root upgrades.
264 parent_keys = tuple((root_id, parent) for parent in rev_parents266
265 if parent != NULL_REVISION and267 :param root_keys_to_create: iterable of (root_id, rev_id) pairs describing
266 rev_id_to_root_id.get(parent, root_id) == root_id)268 the root entries to create.
267 yield FulltextContentFactory(key, parent_keys, None, '')269 :param rev_id_to_root_id_map: dict of known rev_id -> root_id mappings for
268 return [('texts', yield_roots())]270 calculating the parents. If a parent rev_id is not found here then it
271 will be recalculated.
272 :param parent_map: a parent map for all the revisions in
273 root_keys_to_create.
274 :param graph: a graph to use instead of repo.get_graph().
275 """
276 for root_key in root_keys_to_create:
277 root_id, rev_id = root_key
278 parent_keys = _parent_keys_for_root_version(
279 root_id, rev_id, rev_id_to_root_id_map, parent_map, repo, graph)
280 yield versionedfile.FulltextContentFactory(
281 root_key, parent_keys, None, '')
282
283
284def _parent_keys_for_root_version(
285 root_id, rev_id, rev_id_to_root_id_map, parent_map, repo, graph=None):
286 """Get the parent keys for a given root id.
287
288 A helper function for _new_root_data_stream.
289 """
290 # Include direct parents of the revision, but only if they used the same
291 # root_id and are heads.
292 rev_parents = parent_map[rev_id]
293 parent_ids = []
294 for parent_id in rev_parents:
295 if parent_id == NULL_REVISION:
296 continue
297 if parent_id not in rev_id_to_root_id_map:
298 # We probably didn't read this revision, go spend the extra effort
299 # to actually check
300 try:
301 tree = repo.revision_tree(parent_id)
302 except errors.NoSuchRevision:
303 # Ghost, fill out rev_id_to_root_id in case we encounter this
304 # again.
305 # But set parent_root_id to None since we don't really know
306 parent_root_id = None
307 else:
308 parent_root_id = tree.get_root_id()
309 rev_id_to_root_id_map[parent_id] = None
310 # XXX: why not:
311 # rev_id_to_root_id_map[parent_id] = parent_root_id
312 # memory consumption maybe?
313 else:
314 parent_root_id = rev_id_to_root_id_map[parent_id]
315 if root_id == parent_root_id:
316 # With stacking we _might_ want to refer to a non-local revision,
317 # but this code path only applies when we have the full content
318 # available, so ghosts really are ghosts, not just the edge of
319 # local data.
320 parent_ids.append(parent_id)
321 else:
322 # root_id may be in the parent anyway.
323 try:
324 tree = repo.revision_tree(parent_id)
325 except errors.NoSuchRevision:
326 # ghost, can't refer to it.
327 pass
328 else:
329 try:
330 parent_ids.append(tree.inventory[root_id].revision)
331 except errors.NoSuchId:
332 # not in the tree
333 pass
334 # Drop non-head parents
335 if graph is None:
336 graph = repo.get_graph()
337 heads = graph.heads(parent_ids)
338 selected_ids = []
339 for parent_id in parent_ids:
340 if parent_id in heads and parent_id not in selected_ids:
341 selected_ids.append(parent_id)
342 parent_keys = [(root_id, parent_id) for parent_id in selected_ids]
343 return parent_keys
269344
=== modified file 'bzrlib/graph.py'
--- bzrlib/graph.py 2009-08-04 04:36:34 +0000
+++ bzrlib/graph.py 2009-08-17 18:36:14 +0000
@@ -21,7 +21,6 @@
21 errors,21 errors,
22 revision,22 revision,
23 trace,23 trace,
24 tsort,
25 )24 )
26from bzrlib.symbol_versioning import deprecated_function, deprecated_in25from bzrlib.symbol_versioning import deprecated_function, deprecated_in
2726
@@ -926,6 +925,7 @@
926 An ancestor may sort after a descendant if the relationship is not925 An ancestor may sort after a descendant if the relationship is not
927 visible in the supplied list of revisions.926 visible in the supplied list of revisions.
928 """927 """
928 from bzrlib import tsort
929 sorter = tsort.TopoSorter(self.get_parent_map(revisions))929 sorter = tsort.TopoSorter(self.get_parent_map(revisions))
930 return sorter.iter_topo_order()930 return sorter.iter_topo_order()
931931
932932
=== modified file 'bzrlib/groupcompress.py'
--- bzrlib/groupcompress.py 2009-08-04 04:36:34 +0000
+++ bzrlib/groupcompress.py 2009-09-09 13:05:33 +0000
@@ -33,7 +33,6 @@
33 pack,33 pack,
34 trace,34 trace,
35 )35 )
36from bzrlib.graph import Graph
37from bzrlib.btree_index import BTreeBuilder36from bzrlib.btree_index import BTreeBuilder
38from bzrlib.lru_cache import LRUSizeCache37from bzrlib.lru_cache import LRUSizeCache
39from bzrlib.tsort import topo_sort38from bzrlib.tsort import topo_sort
@@ -45,12 +44,15 @@
45 VersionedFiles,44 VersionedFiles,
46 )45 )
4746
47# Minimum number of uncompressed bytes to try fetch at once when retrieving
48# groupcompress blocks.
49BATCH_SIZE = 2**16
50
48_USE_LZMA = False and (pylzma is not None)51_USE_LZMA = False and (pylzma is not None)
4952
50# osutils.sha_string('')53# osutils.sha_string('')
51_null_sha1 = 'da39a3ee5e6b4b0d3255bfef95601890afd80709'54_null_sha1 = 'da39a3ee5e6b4b0d3255bfef95601890afd80709'
5255
53
54def sort_gc_optimal(parent_map):56def sort_gc_optimal(parent_map):
55 """Sort and group the keys in parent_map into groupcompress order.57 """Sort and group the keys in parent_map into groupcompress order.
5658
@@ -62,16 +64,15 @@
62 # groupcompress ordering is approximately reverse topological,64 # groupcompress ordering is approximately reverse topological,
63 # properly grouped by file-id.65 # properly grouped by file-id.
64 per_prefix_map = {}66 per_prefix_map = {}
65 for item in parent_map.iteritems():67 for key, value in parent_map.iteritems():
66 key = item[0]
67 if isinstance(key, str) or len(key) == 1:68 if isinstance(key, str) or len(key) == 1:
68 prefix = ''69 prefix = ''
69 else:70 else:
70 prefix = key[0]71 prefix = key[0]
71 try:72 try:
72 per_prefix_map[prefix].append(item)73 per_prefix_map[prefix][key] = value
73 except KeyError:74 except KeyError:
74 per_prefix_map[prefix] = [item]75 per_prefix_map[prefix] = {key: value}
7576
76 present_keys = []77 present_keys = []
77 for prefix in sorted(per_prefix_map):78 for prefix in sorted(per_prefix_map):
@@ -456,7 +457,6 @@
456 # There are code paths that first extract as fulltext, and then457 # There are code paths that first extract as fulltext, and then
457 # extract as storage_kind (smart fetch). So we don't break the458 # extract as storage_kind (smart fetch). So we don't break the
458 # refcycle here, but instead in manager.get_record_stream()459 # refcycle here, but instead in manager.get_record_stream()
459 # self._manager = None
460 if storage_kind == 'fulltext':460 if storage_kind == 'fulltext':
461 return self._bytes461 return self._bytes
462 else:462 else:
@@ -468,6 +468,14 @@
468class _LazyGroupContentManager(object):468class _LazyGroupContentManager(object):
469 """This manages a group of _LazyGroupCompressFactory objects."""469 """This manages a group of _LazyGroupCompressFactory objects."""
470470
471 _max_cut_fraction = 0.75 # We allow a block to be trimmed to 75% of
472 # current size, and still be considered
473 # resuable
474 _full_block_size = 4*1024*1024
475 _full_mixed_block_size = 2*1024*1024
476 _full_enough_block_size = 3*1024*1024 # size at which we won't repack
477 _full_enough_mixed_block_size = 2*768*1024 # 1.5MB
478
471 def __init__(self, block):479 def __init__(self, block):
472 self._block = block480 self._block = block
473 # We need to preserve the ordering481 # We need to preserve the ordering
@@ -545,22 +553,23 @@
545 # time (self._block._content) is a little expensive.553 # time (self._block._content) is a little expensive.
546 self._block._ensure_content(self._last_byte)554 self._block._ensure_content(self._last_byte)
547555
548 def _check_rebuild_block(self):556 def _check_rebuild_action(self):
549 """Check to see if our block should be repacked."""557 """Check to see if our block should be repacked."""
550 total_bytes_used = 0558 total_bytes_used = 0
551 last_byte_used = 0559 last_byte_used = 0
552 for factory in self._factories:560 for factory in self._factories:
553 total_bytes_used += factory._end - factory._start561 total_bytes_used += factory._end - factory._start
554 last_byte_used = max(last_byte_used, factory._end)562 if last_byte_used < factory._end:
555 # If we are using most of the bytes from the block, we have nothing563 last_byte_used = factory._end
556 # else to check (currently more that 1/2)564 # If we are using more than half of the bytes from the block, we have
565 # nothing else to check
557 if total_bytes_used * 2 >= self._block._content_length:566 if total_bytes_used * 2 >= self._block._content_length:
558 return567 return None, last_byte_used, total_bytes_used
559 # Can we just strip off the trailing bytes? If we are going to be568 # We are using less than 50% of the content. Is the content we are
560 # transmitting more than 50% of the front of the content, go ahead569 # using at the beginning of the block? If so, we can just trim the
570 # tail, rather than rebuilding from scratch.
561 if total_bytes_used * 2 > last_byte_used:571 if total_bytes_used * 2 > last_byte_used:
562 self._trim_block(last_byte_used)572 return 'trim', last_byte_used, total_bytes_used
563 return
564573
565 # We are using a small amount of the data, and it isn't just packed574 # We are using a small amount of the data, and it isn't just packed
566 # nicely at the front, so rebuild the content.575 # nicely at the front, so rebuild the content.
@@ -573,7 +582,80 @@
573 # expanding many deltas into fulltexts, as well.582 # expanding many deltas into fulltexts, as well.
574 # If we build a cheap enough 'strip', then we could try a strip,583 # If we build a cheap enough 'strip', then we could try a strip,
575 # if that expands the content, we then rebuild.584 # if that expands the content, we then rebuild.
576 self._rebuild_block()585 return 'rebuild', last_byte_used, total_bytes_used
586
587 def check_is_well_utilized(self):
588 """Is the current block considered 'well utilized'?
589
590 This heuristic asks if the current block considers itself to be a fully
591 developed group, rather than just a loose collection of data.
592 """
593 if len(self._factories) == 1:
594 # A block of length 1 could be improved by combining with other
595 # groups - don't look deeper. Even larger than max size groups
596 # could compress well with adjacent versions of the same thing.
597 return False
598 action, last_byte_used, total_bytes_used = self._check_rebuild_action()
599 block_size = self._block._content_length
600 if total_bytes_used < block_size * self._max_cut_fraction:
601 # This block wants to trim itself small enough that we want to
602 # consider it under-utilized.
603 return False
604 # TODO: This code is meant to be the twin of _insert_record_stream's
605 # 'start_new_block' logic. It would probably be better to factor
606 # out that logic into a shared location, so that it stays
607 # together better
608 # We currently assume a block is properly utilized whenever it is >75%
609 # of the size of a 'full' block. In normal operation, a block is
610 # considered full when it hits 4MB of same-file content. So any block
611 # >3MB is 'full enough'.
612 # The only time this isn't true is when a given block has large-object
613 # content. (a single file >4MB, etc.)
614 # Under these circumstances, we allow a block to grow to
615 # 2 x largest_content. Which means that if a given block had a large
616 # object, it may actually be under-utilized. However, given that this
617 # is 'pack-on-the-fly' it is probably reasonable to not repack large
618 # content blobs on-the-fly. Note that because we return False for all
619 # 1-item blobs, we will repack them; we may wish to reevaluate our
620 # treatment of large object blobs in the future.
621 if block_size >= self._full_enough_block_size:
622 return True
623 # If a block is <3MB, it still may be considered 'full' if it contains
624 # mixed content. The current rule is 2MB of mixed content is considered
625 # full. So check to see if this block contains mixed content, and
626 # set the threshold appropriately.
627 common_prefix = None
628 for factory in self._factories:
629 prefix = factory.key[:-1]
630 if common_prefix is None:
631 common_prefix = prefix
632 elif prefix != common_prefix:
633 # Mixed content, check the size appropriately
634 if block_size >= self._full_enough_mixed_block_size:
635 return True
636 break
637 # The content failed both the mixed check and the single-content check
638 # so obviously it is not fully utilized
639 # TODO: there is one other constraint that isn't being checked
640 # namely, that the entries in the block are in the appropriate
641 # order. For example, you could insert the entries in exactly
642 # reverse groupcompress order, and we would think that is ok.
643 # (all the right objects are in one group, and it is fully
644 # utilized, etc.) For now, we assume that case is rare,
645 # especially since we should always fetch in 'groupcompress'
646 # order.
647 return False
648
649 def _check_rebuild_block(self):
650 action, last_byte_used, total_bytes_used = self._check_rebuild_action()
651 if action is None:
652 return
653 if action == 'trim':
654 self._trim_block(last_byte_used)
655 elif action == 'rebuild':
656 self._rebuild_block()
657 else:
658 raise ValueError('unknown rebuild action: %r' % (action,))
577659
578 def _wire_bytes(self):660 def _wire_bytes(self):
579 """Return a byte stream suitable for transmitting over the wire."""661 """Return a byte stream suitable for transmitting over the wire."""
@@ -975,23 +1057,139 @@
975 versioned_files.stream.close()1057 versioned_files.stream.close()
9761058
9771059
1060class _BatchingBlockFetcher(object):
1061 """Fetch group compress blocks in batches.
1062
1063 :ivar total_bytes: int of expected number of bytes needed to fetch the
1064 currently pending batch.
1065 """
1066
1067 def __init__(self, gcvf, locations):
1068 self.gcvf = gcvf
1069 self.locations = locations
1070 self.keys = []
1071 self.batch_memos = {}
1072 self.memos_to_get = []
1073 self.total_bytes = 0
1074 self.last_read_memo = None
1075 self.manager = None
1076
1077 def add_key(self, key):
1078 """Add another to key to fetch.
1079
1080 :return: The estimated number of bytes needed to fetch the batch so
1081 far.
1082 """
1083 self.keys.append(key)
1084 index_memo, _, _, _ = self.locations[key]
1085 read_memo = index_memo[0:3]
1086 # Three possibilities for this read_memo:
1087 # - it's already part of this batch; or
1088 # - it's not yet part of this batch, but is already cached; or
1089 # - it's not yet part of this batch and will need to be fetched.
1090 if read_memo in self.batch_memos:
1091 # This read memo is already in this batch.
1092 return self.total_bytes
1093 try:
1094 cached_block = self.gcvf._group_cache[read_memo]
1095 except KeyError:
1096 # This read memo is new to this batch, and the data isn't cached
1097 # either.
1098 self.batch_memos[read_memo] = None
1099 self.memos_to_get.append(read_memo)
1100 byte_length = read_memo[2]
1101 self.total_bytes += byte_length
1102 else:
1103 # This read memo is new to this batch, but cached.
1104 # Keep a reference to the cached block in batch_memos because it's
1105 # certain that we'll use it when this batch is processed, but
1106 # there's a risk that it would fall out of _group_cache between now
1107 # and then.
1108 self.batch_memos[read_memo] = cached_block
1109 return self.total_bytes
1110
1111 def _flush_manager(self):
1112 if self.manager is not None:
1113 for factory in self.manager.get_record_stream():
1114 yield factory
1115 self.manager = None
1116 self.last_read_memo = None
1117
1118 def yield_factories(self, full_flush=False):
1119 """Yield factories for keys added since the last yield. They will be
1120 returned in the order they were added via add_key.
1121
1122 :param full_flush: by default, some results may not be returned in case
1123 they can be part of the next batch. If full_flush is True, then
1124 all results are returned.
1125 """
1126 if self.manager is None and not self.keys:
1127 return
1128 # Fetch all memos in this batch.
1129 blocks = self.gcvf._get_blocks(self.memos_to_get)
1130 # Turn blocks into factories and yield them.
1131 memos_to_get_stack = list(self.memos_to_get)
1132 memos_to_get_stack.reverse()
1133 for key in self.keys:
1134 index_memo, _, parents, _ = self.locations[key]
1135 read_memo = index_memo[:3]
1136 if self.last_read_memo != read_memo:
1137 # We are starting a new block. If we have a
1138 # manager, we have found everything that fits for
1139 # now, so yield records
1140 for factory in self._flush_manager():
1141 yield factory
1142 # Now start a new manager.
1143 if memos_to_get_stack and memos_to_get_stack[-1] == read_memo:
1144 # The next block from _get_blocks will be the block we
1145 # need.
1146 block_read_memo, block = blocks.next()
1147 if block_read_memo != read_memo:
1148 raise AssertionError(
1149 "block_read_memo out of sync with read_memo"
1150 "(%r != %r)" % (block_read_memo, read_memo))
1151 self.batch_memos[read_memo] = block
1152 memos_to_get_stack.pop()
1153 else:
1154 block = self.batch_memos[read_memo]
1155 self.manager = _LazyGroupContentManager(block)
1156 self.last_read_memo = read_memo
1157 start, end = index_memo[3:5]
1158 self.manager.add_factory(key, parents, start, end)
1159 if full_flush:
1160 for factory in self._flush_manager():
1161 yield factory
1162 del self.keys[:]
1163 self.batch_memos.clear()
1164 del self.memos_to_get[:]
1165 self.total_bytes = 0
1166
1167
978class GroupCompressVersionedFiles(VersionedFiles):1168class GroupCompressVersionedFiles(VersionedFiles):
979 """A group-compress based VersionedFiles implementation."""1169 """A group-compress based VersionedFiles implementation."""
9801170
981 def __init__(self, index, access, delta=True):1171 def __init__(self, index, access, delta=True, _unadded_refs=None):
982 """Create a GroupCompressVersionedFiles object.1172 """Create a GroupCompressVersionedFiles object.
9831173
984 :param index: The index object storing access and graph data.1174 :param index: The index object storing access and graph data.
985 :param access: The access object storing raw data.1175 :param access: The access object storing raw data.
986 :param delta: Whether to delta compress or just entropy compress.1176 :param delta: Whether to delta compress or just entropy compress.
1177 :param _unadded_refs: private parameter, don't use.
987 """1178 """
988 self._index = index1179 self._index = index
989 self._access = access1180 self._access = access
990 self._delta = delta1181 self._delta = delta
991 self._unadded_refs = {}1182 if _unadded_refs is None:
1183 _unadded_refs = {}
1184 self._unadded_refs = _unadded_refs
992 self._group_cache = LRUSizeCache(max_size=50*1024*1024)1185 self._group_cache = LRUSizeCache(max_size=50*1024*1024)
993 self._fallback_vfs = []1186 self._fallback_vfs = []
9941187
1188 def without_fallbacks(self):
1189 """Return a clone of this object without any fallbacks configured."""
1190 return GroupCompressVersionedFiles(self._index, self._access,
1191 self._delta, _unadded_refs=dict(self._unadded_refs))
1192
995 def add_lines(self, key, parents, lines, parent_texts=None,1193 def add_lines(self, key, parents, lines, parent_texts=None,
996 left_matching_blocks=None, nostore_sha=None, random_id=False,1194 left_matching_blocks=None, nostore_sha=None, random_id=False,
997 check_content=True):1195 check_content=True):
@@ -1099,6 +1297,22 @@
1099 self._check_lines_not_unicode(lines)1297 self._check_lines_not_unicode(lines)
1100 self._check_lines_are_lines(lines)1298 self._check_lines_are_lines(lines)
11011299
1300 def get_known_graph_ancestry(self, keys):
1301 """Get a KnownGraph instance with the ancestry of keys."""
1302 # Note that this is identical to
1303 # KnitVersionedFiles.get_known_graph_ancestry, but they don't share
1304 # ancestry.
1305 parent_map, missing_keys = self._index.find_ancestry(keys)
1306 for fallback in self._fallback_vfs:
1307 if not missing_keys:
1308 break
1309 (f_parent_map, f_missing_keys) = fallback._index.find_ancestry(
1310 missing_keys)
1311 parent_map.update(f_parent_map)
1312 missing_keys = f_missing_keys
1313 kg = _mod_graph.KnownGraph(parent_map)
1314 return kg
1315
1102 def get_parent_map(self, keys):1316 def get_parent_map(self, keys):
1103 """Get a map of the graph parents of keys.1317 """Get a map of the graph parents of keys.
11041318
@@ -1131,26 +1345,42 @@
1131 missing.difference_update(set(new_result))1345 missing.difference_update(set(new_result))
1132 return result, source_results1346 return result, source_results
11331347
1134 def _get_block(self, index_memo):1348 def _get_blocks(self, read_memos):
1135 read_memo = index_memo[0:3]1349 """Get GroupCompressBlocks for the given read_memos.
1136 # get the group:1350
1137 try:1351 :returns: a series of (read_memo, block) pairs, in the order they were
1138 block = self._group_cache[read_memo]1352 originally passed.
1139 except KeyError:1353 """
1140 # read the group1354 cached = {}
1141 zdata = self._access.get_raw_records([read_memo]).next()1355 for read_memo in read_memos:
1142 # decompress - whole thing - this is not a bug, as it1356 try:
1143 # permits caching. We might want to store the partially1357 block = self._group_cache[read_memo]
1144 # decompresed group and decompress object, so that recent1358 except KeyError:
1145 # texts are not penalised by big groups.1359 pass
1146 block = GroupCompressBlock.from_bytes(zdata)1360 else:
1147 self._group_cache[read_memo] = block1361 cached[read_memo] = block
1148 # cheapo debugging:1362 not_cached = []
1149 # print len(zdata), len(plain)1363 not_cached_seen = set()
1150 # parse - requires split_lines, better to have byte offsets1364 for read_memo in read_memos:
1151 # here (but not by much - we only split the region for the1365 if read_memo in cached:
1152 # recipe, and we often want to end up with lines anyway.1366 # Don't fetch what we already have
1153 return block1367 continue
1368 if read_memo in not_cached_seen:
1369 # Don't try to fetch the same data twice
1370 continue
1371 not_cached.append(read_memo)
1372 not_cached_seen.add(read_memo)
1373 raw_records = self._access.get_raw_records(not_cached)
1374 for read_memo in read_memos:
1375 try:
1376 yield read_memo, cached[read_memo]
1377 except KeyError:
1378 # Read the block, and cache it.
1379 zdata = raw_records.next()
1380 block = GroupCompressBlock.from_bytes(zdata)
1381 self._group_cache[read_memo] = block
1382 cached[read_memo] = block
1383 yield read_memo, block
11541384
1155 def get_missing_compression_parent_keys(self):1385 def get_missing_compression_parent_keys(self):
1156 """Return the keys of missing compression parents.1386 """Return the keys of missing compression parents.
@@ -1322,55 +1552,35 @@
1322 unadded_keys, source_result)1552 unadded_keys, source_result)
1323 for key in missing:1553 for key in missing:
1324 yield AbsentContentFactory(key)1554 yield AbsentContentFactory(key)
1325 manager = None1555 # Batch up as many keys as we can until either:
1326 last_read_memo = None1556 # - we encounter an unadded ref, or
1327 # TODO: This works fairly well at batching up existing groups into a1557 # - we run out of keys, or
1328 # streamable format, and possibly allowing for taking one big1558 # - the total bytes to retrieve for this batch > BATCH_SIZE
1329 # group and splitting it when it isn't fully utilized.1559 batcher = _BatchingBlockFetcher(self, locations)
1330 # However, it doesn't allow us to find under-utilized groups and
1331 # combine them into a bigger group on the fly.
1332 # (Consider the issue with how chk_map inserts texts
1333 # one-at-a-time.) This could be done at insert_record_stream()
1334 # time, but it probably would decrease the number of
1335 # bytes-on-the-wire for fetch.
1336 for source, keys in source_keys:1560 for source, keys in source_keys:
1337 if source is self:1561 if source is self:
1338 for key in keys:1562 for key in keys:
1339 if key in self._unadded_refs:1563 if key in self._unadded_refs:
1340 if manager is not None:1564 # Flush batch, then yield unadded ref from
1341 for factory in manager.get_record_stream():1565 # self._compressor.
1342 yield factory1566 for factory in batcher.yield_factories(full_flush=True):
1343 last_read_memo = manager = None1567 yield factory
1344 bytes, sha1 = self._compressor.extract(key)1568 bytes, sha1 = self._compressor.extract(key)
1345 parents = self._unadded_refs[key]1569 parents = self._unadded_refs[key]
1346 yield FulltextContentFactory(key, parents, sha1, bytes)1570 yield FulltextContentFactory(key, parents, sha1, bytes)
1347 else:1571 continue
1348 index_memo, _, parents, (method, _) = locations[key]1572 if batcher.add_key(key) > BATCH_SIZE:
1349 read_memo = index_memo[0:3]1573 # Ok, this batch is big enough. Yield some results.
1350 if last_read_memo != read_memo:1574 for factory in batcher.yield_factories():
1351 # We are starting a new block. If we have a1575 yield factory
1352 # manager, we have found everything that fits for
1353 # now, so yield records
1354 if manager is not None:
1355 for factory in manager.get_record_stream():
1356 yield factory
1357 # Now start a new manager
1358 block = self._get_block(index_memo)
1359 manager = _LazyGroupContentManager(block)
1360 last_read_memo = read_memo
1361 start, end = index_memo[3:5]
1362 manager.add_factory(key, parents, start, end)
1363 else:1576 else:
1364 if manager is not None:1577 for factory in batcher.yield_factories(full_flush=True):
1365 for factory in manager.get_record_stream():1578 yield factory
1366 yield factory
1367 last_read_memo = manager = None
1368 for record in source.get_record_stream(keys, ordering,1579 for record in source.get_record_stream(keys, ordering,
1369 include_delta_closure):1580 include_delta_closure):
1370 yield record1581 yield record
1371 if manager is not None:1582 for factory in batcher.yield_factories(full_flush=True):
1372 for factory in manager.get_record_stream():1583 yield factory
1373 yield factory
13741584
1375 def get_sha1s(self, keys):1585 def get_sha1s(self, keys):
1376 """See VersionedFiles.get_sha1s()."""1586 """See VersionedFiles.get_sha1s()."""
@@ -1449,6 +1659,7 @@
1449 block_length = None1659 block_length = None
1450 # XXX: TODO: remove this, it is just for safety checking for now1660 # XXX: TODO: remove this, it is just for safety checking for now
1451 inserted_keys = set()1661 inserted_keys = set()
1662 reuse_this_block = reuse_blocks
1452 for record in stream:1663 for record in stream:
1453 # Raise an error when a record is missing.1664 # Raise an error when a record is missing.
1454 if record.storage_kind == 'absent':1665 if record.storage_kind == 'absent':
@@ -1462,10 +1673,20 @@
1462 if reuse_blocks:1673 if reuse_blocks:
1463 # If the reuse_blocks flag is set, check to see if we can just1674 # If the reuse_blocks flag is set, check to see if we can just
1464 # copy a groupcompress block as-is.1675 # copy a groupcompress block as-is.
1676 # We only check on the first record (groupcompress-block) not
1677 # on all of the (groupcompress-block-ref) entries.
1678 # The reuse_this_block flag is then kept for as long as
1679 if record.storage_kind == 'groupcompress-block':
1680 # Check to see if we really want to re-use this block
1681 insert_manager = record._manager
1682 reuse_this_block = insert_manager.check_is_well_utilized()
1683 else:
1684 reuse_this_block = False
1685 if reuse_this_block:
1686 # We still want to reuse this block
1465 if record.storage_kind == 'groupcompress-block':1687 if record.storage_kind == 'groupcompress-block':
1466 # Insert the raw block into the target repo1688 # Insert the raw block into the target repo
1467 insert_manager = record._manager1689 insert_manager = record._manager
1468 insert_manager._check_rebuild_block()
1469 bytes = record._manager._block.to_bytes()1690 bytes = record._manager._block.to_bytes()
1470 _, start, length = self._access.add_raw_records(1691 _, start, length = self._access.add_raw_records(
1471 [(None, len(bytes))], bytes)[0]1692 [(None, len(bytes))], bytes)[0]
@@ -1476,6 +1697,11 @@
1476 'groupcompress-block-ref'):1697 'groupcompress-block-ref'):
1477 if insert_manager is None:1698 if insert_manager is None:
1478 raise AssertionError('No insert_manager set')1699 raise AssertionError('No insert_manager set')
1700 if insert_manager is not record._manager:
1701 raise AssertionError('insert_manager does not match'
1702 ' the current record, we cannot be positive'
1703 ' that the appropriate content was inserted.'
1704 )
1479 value = "%d %d %d %d" % (block_start, block_length,1705 value = "%d %d %d %d" % (block_start, block_length,
1480 record._start, record._end)1706 record._start, record._end)
1481 nodes = [(record.key, value, (record.parents,))]1707 nodes = [(record.key, value, (record.parents,))]
@@ -1593,7 +1819,7 @@
15931819
1594 def __init__(self, graph_index, is_locked, parents=True,1820 def __init__(self, graph_index, is_locked, parents=True,
1595 add_callback=None, track_external_parent_refs=False,1821 add_callback=None, track_external_parent_refs=False,
1596 inconsistency_fatal=True):1822 inconsistency_fatal=True, track_new_keys=False):
1597 """Construct a _GCGraphIndex on a graph_index.1823 """Construct a _GCGraphIndex on a graph_index.
15981824
1599 :param graph_index: An implementation of bzrlib.index.GraphIndex.1825 :param graph_index: An implementation of bzrlib.index.GraphIndex.
@@ -1619,7 +1845,8 @@
1619 self._is_locked = is_locked1845 self._is_locked = is_locked
1620 self._inconsistency_fatal = inconsistency_fatal1846 self._inconsistency_fatal = inconsistency_fatal
1621 if track_external_parent_refs:1847 if track_external_parent_refs:
1622 self._key_dependencies = knit._KeyRefs()1848 self._key_dependencies = knit._KeyRefs(
1849 track_new_keys=track_new_keys)
1623 else:1850 else:
1624 self._key_dependencies = None1851 self._key_dependencies = None
16251852
@@ -1679,10 +1906,14 @@
1679 result.append((key, value))1906 result.append((key, value))
1680 records = result1907 records = result
1681 key_dependencies = self._key_dependencies1908 key_dependencies = self._key_dependencies
1682 if key_dependencies is not None and self._parents:1909 if key_dependencies is not None:
1683 for key, value, refs in records:1910 if self._parents:
1684 parents = refs[0]1911 for key, value, refs in records:
1685 key_dependencies.add_references(key, parents)1912 parents = refs[0]
1913 key_dependencies.add_references(key, parents)
1914 else:
1915 for key, value, refs in records:
1916 new_keys.add_key(key)
1686 self._add_callback(records)1917 self._add_callback(records)
16871918
1688 def _check_read(self):1919 def _check_read(self):
@@ -1719,6 +1950,10 @@
1719 if missing_keys:1950 if missing_keys:
1720 raise errors.RevisionNotPresent(missing_keys.pop(), self)1951 raise errors.RevisionNotPresent(missing_keys.pop(), self)
17211952
1953 def find_ancestry(self, keys):
1954 """See CombinedGraphIndex.find_ancestry"""
1955 return self._graph_index.find_ancestry(keys, 0)
1956
1722 def get_parent_map(self, keys):1957 def get_parent_map(self, keys):
1723 """Get a map of the parents of keys.1958 """Get a map of the parents of keys.
17241959
@@ -1741,7 +1976,7 @@
1741 """Return the keys of missing parents."""1976 """Return the keys of missing parents."""
1742 # Copied from _KnitGraphIndex.get_missing_parents1977 # Copied from _KnitGraphIndex.get_missing_parents
1743 # We may have false positives, so filter those out.1978 # We may have false positives, so filter those out.
1744 self._key_dependencies.add_keys(1979 self._key_dependencies.satisfy_refs_for_keys(
1745 self.get_parent_map(self._key_dependencies.get_unsatisfied_refs()))1980 self.get_parent_map(self._key_dependencies.get_unsatisfied_refs()))
1746 return frozenset(self._key_dependencies.get_unsatisfied_refs())1981 return frozenset(self._key_dependencies.get_unsatisfied_refs())
17471982
@@ -1801,17 +2036,17 @@
18012036
1802 This allows this _GCGraphIndex to keep track of any missing2037 This allows this _GCGraphIndex to keep track of any missing
1803 compression parents we may want to have filled in to make those2038 compression parents we may want to have filled in to make those
1804 indices valid.2039 indices valid. It also allows _GCGraphIndex to track any new keys.
18052040
1806 :param graph_index: A GraphIndex2041 :param graph_index: A GraphIndex
1807 """2042 """
1808 if self._key_dependencies is not None:2043 key_dependencies = self._key_dependencies
1809 # Add parent refs from graph_index (and discard parent refs that2044 if key_dependencies is None:
1810 # the graph_index has).2045 return
1811 add_refs = self._key_dependencies.add_references2046 for node in graph_index.iter_all_entries():
1812 for node in graph_index.iter_all_entries():2047 # Add parent refs from graph_index (and discard parent refs
1813 add_refs(node[1], node[3][0])2048 # that the graph_index has).
18142049 key_dependencies.add_references(node[1], node[3][0])
18152050
18162051
1817from bzrlib._groupcompress_py import (2052from bzrlib._groupcompress_py import (
18182053
=== modified file 'bzrlib/help_topics/en/configuration.txt'
--- bzrlib/help_topics/en/configuration.txt 2009-06-26 18:13:41 +0000
+++ bzrlib/help_topics/en/configuration.txt 2009-08-21 09:19:11 +0000
@@ -63,6 +63,60 @@
63~~~~~~~~~~~~~~~63~~~~~~~~~~~~~~~
6464
65The path to the plugins directory that Bazaar should use.65The path to the plugins directory that Bazaar should use.
66If not set, Bazaar will search for plugins in:
67
68* the user specific plugin directory (containing the ``user`` plugins),
69
70* the bzrlib directory (containing the ``core`` plugins),
71
72* the site specific plugin directory if applicable (containing
73 the ``site`` plugins).
74
75If ``BZR_PLUGIN_PATH`` is set in any fashion, it will change the
76the way the plugin are searched.
77
78As for the ``PATH`` variables, if multiple directories are
79specified in ``BZR_PLUGIN_PATH`` they should be separated by the
80platform specific appropriate character (':' on Unix/Linux/etc,
81';' on windows)
82
83By default if ``BZR_PLUGIN_PATH`` is set, it replaces searching
84in ``user``. However it will continue to search in ``core`` and
85``site`` unless they are explicitly removed.
86
87If you need to change the order or remove one of these
88directories, you should use special values:
89
90* ``-user``, ``-core``, ``-site`` will remove the corresponding
91 path from the default values,
92
93* ``+user``, ``+core``, ``+site`` will add the corresponding path
94 before the remaining default values (and also remove it from
95 the default values).
96
97Note that the special values 'user', 'core' and 'site' should be
98used literally, they will be substituted by the corresponding,
99platform specific, values.
100
101Examples:
102^^^^^^^^^
103
104The examples below uses ':' as the separator, windows users
105should use ';'.
106
107Overriding the default user plugin directory:
108``BZR_PLUGIN_PATH='/path/to/my/other/plugins'``
109
110Disabling the site directory while retaining the user directory:
111``BZR_PLUGIN_PATH='-site:+user'``
112
113Disabling all plugins (better achieved with --no-plugins):
114``BZR_PLUGIN_PATH='-user:-core:-site'``
115
116Overriding the default site plugin directory:
117``BZR_PLUGIN_PATH='/path/to/my/site/plugins:-site':+user``
118
119
66120
67BZRPATH121BZRPATH
68~~~~~~~122~~~~~~~
69123
=== modified file 'bzrlib/help_topics/en/debug-flags.txt'
--- bzrlib/help_topics/en/debug-flags.txt 2009-08-04 04:36:34 +0000
+++ bzrlib/help_topics/en/debug-flags.txt 2009-08-20 05:02:45 +0000
@@ -12,18 +12,26 @@
12 operations.12 operations.
13-Dfetch Trace history copying between repositories.13-Dfetch Trace history copying between repositories.
14-Dfilters Emit information for debugging content filtering.14-Dfilters Emit information for debugging content filtering.
15-Dforceinvdeltas Force use of inventory deltas during generic streaming fetch.
15-Dgraph Trace graph traversal.16-Dgraph Trace graph traversal.
16-Dhashcache Log every time a working file is read to determine its hash.17-Dhashcache Log every time a working file is read to determine its hash.
17-Dhooks Trace hook execution.18-Dhooks Trace hook execution.
18-Dhpss Trace smart protocol requests and responses.19-Dhpss Trace smart protocol requests and responses.
19-Dhpssdetail More hpss details.20-Dhpssdetail More hpss details.
20-Dhpssvfs Traceback on vfs access to Remote objects.21-Dhpssvfs Traceback on vfs access to Remote objects.
21-Dhttp Trace http connections, requests and responses22-Dhttp Trace http connections, requests and responses.
22-Dindex Trace major index operations.23-Dindex Trace major index operations.
23-Dknit Trace knit operations.24-Dknit Trace knit operations.
25-Dstrict_locks Trace when OS locks are potentially used in a non-portable
26 manner.
24-Dlock Trace when lockdir locks are taken or released.27-Dlock Trace when lockdir locks are taken or released.
25-Dprogress Trace progress bar operations.28-Dprogress Trace progress bar operations.
26-Dmerge Emit information for debugging merges.29-Dmerge Emit information for debugging merges.
30-Dno_apport Don't use apport to report crashes.
27-Dunlock Some errors during unlock are treated as warnings.31-Dunlock Some errors during unlock are treated as warnings.
28-Dpack Emit information about pack operations.32-Dpack Emit information about pack operations.
29-Dsftp Trace SFTP internals.33-Dsftp Trace SFTP internals.
34-Dstream Trace fetch streams.
35-DIDS_never Never use InterDifferingSerializer when fetching.
36-DIDS_always Always use InterDifferingSerializer to fetch if appropriate
37 for the format, even for non-local fetches.
3038
=== modified file 'bzrlib/hooks.py'
--- bzrlib/hooks.py 2009-06-10 03:31:01 +0000
+++ bzrlib/hooks.py 2009-09-01 12:29:54 +0000
@@ -219,9 +219,7 @@
219 strings.append('Introduced in: %s' % introduced_string)219 strings.append('Introduced in: %s' % introduced_string)
220 if self.deprecated:220 if self.deprecated:
221 deprecated_string = _format_version_tuple(self.deprecated)221 deprecated_string = _format_version_tuple(self.deprecated)
222 else:222 strings.append('Deprecated in: %s' % deprecated_string)
223 deprecated_string = 'Not deprecated'
224 strings.append('Deprecated in: %s' % deprecated_string)
225 strings.append('')223 strings.append('')
226 strings.extend(textwrap.wrap(self.__doc__,224 strings.extend(textwrap.wrap(self.__doc__,
227 break_long_words=False))225 break_long_words=False))
228226
=== modified file 'bzrlib/index.py'
--- bzrlib/index.py 2009-07-01 10:53:08 +0000
+++ bzrlib/index.py 2009-08-17 22:11:06 +0000
@@ -333,6 +333,22 @@
333 if combine_backing_indices is not None:333 if combine_backing_indices is not None:
334 self._combine_backing_indices = combine_backing_indices334 self._combine_backing_indices = combine_backing_indices
335335
336 def find_ancestry(self, keys, ref_list_num):
337 """See CombinedGraphIndex.find_ancestry()"""
338 pending = set(keys)
339 parent_map = {}
340 missing_keys = set()
341 while pending:
342 next_pending = set()
343 for _, key, value, ref_lists in self.iter_entries(pending):
344 parent_keys = ref_lists[ref_list_num]
345 parent_map[key] = parent_keys
346 next_pending.update([p for p in parent_keys if p not in
347 parent_map])
348 missing_keys.update(pending.difference(parent_map))
349 pending = next_pending
350 return parent_map, missing_keys
351
336352
337class GraphIndex(object):353class GraphIndex(object):
338 """An index for data with embedded graphs.354 """An index for data with embedded graphs.
@@ -702,6 +718,23 @@
702 # the last thing looked up was a terminal element718 # the last thing looked up was a terminal element
703 yield (self, ) + key_dict719 yield (self, ) + key_dict
704720
721 def _find_ancestors(self, keys, ref_list_num, parent_map, missing_keys):
722 """See BTreeIndex._find_ancestors."""
723 # The api can be implemented as a trivial overlay on top of
724 # iter_entries, it is not an efficient implementation, but it at least
725 # gets the job done.
726 found_keys = set()
727 search_keys = set()
728 for index, key, value, refs in self.iter_entries(keys):
729 parent_keys = refs[ref_list_num]
730 found_keys.add(key)
731 parent_map[key] = parent_keys
732 search_keys.update(parent_keys)
733 # Figure out what, if anything, was missing
734 missing_keys.update(set(keys).difference(found_keys))
735 search_keys = search_keys.difference(parent_map)
736 return search_keys
737
705 def key_count(self):738 def key_count(self):
706 """Return an estimate of the number of keys in this index.739 """Return an estimate of the number of keys in this index.
707740
@@ -1297,6 +1330,69 @@
1297 except errors.NoSuchFile:1330 except errors.NoSuchFile:
1298 self._reload_or_raise()1331 self._reload_or_raise()
12991332
1333 def find_ancestry(self, keys, ref_list_num):
1334 """Find the complete ancestry for the given set of keys.
1335
1336 Note that this is a whole-ancestry request, so it should be used
1337 sparingly.
1338
1339 :param keys: An iterable of keys to look for
1340 :param ref_list_num: The reference list which references the parents
1341 we care about.
1342 :return: (parent_map, missing_keys)
1343 """
1344 missing_keys = set()
1345 parent_map = {}
1346 keys_to_lookup = set(keys)
1347 generation = 0
1348 while keys_to_lookup:
1349 # keys that *all* indexes claim are missing, stop searching them
1350 generation += 1
1351 all_index_missing = None
1352 # print 'gen\tidx\tsub\tn_keys\tn_pmap\tn_miss'
1353 # print '%4d\t\t\t%4d\t%5d\t%5d' % (generation, len(keys_to_lookup),
1354 # len(parent_map),
1355 # len(missing_keys))
1356 for index_idx, index in enumerate(self._indices):
1357 # TODO: we should probably be doing something with
1358 # 'missing_keys' since we've already determined that
1359 # those revisions have not been found anywhere
1360 index_missing_keys = set()
1361 # Find all of the ancestry we can from this index
1362 # keep looking until the search_keys set is empty, which means
1363 # things we didn't find should be in index_missing_keys
1364 search_keys = keys_to_lookup
1365 sub_generation = 0
1366 # print ' \t%2d\t\t%4d\t%5d\t%5d' % (
1367 # index_idx, len(search_keys),
1368 # len(parent_map), len(index_missing_keys))
1369 while search_keys:
1370 sub_generation += 1
1371 # TODO: ref_list_num should really be a parameter, since
1372 # CombinedGraphIndex does not know what the ref lists
1373 # mean.
1374 search_keys = index._find_ancestors(search_keys,
1375 ref_list_num, parent_map, index_missing_keys)
1376 # print ' \t \t%2d\t%4d\t%5d\t%5d' % (
1377 # sub_generation, len(search_keys),
1378 # len(parent_map), len(index_missing_keys))
1379 # Now set whatever was missing to be searched in the next index
1380 keys_to_lookup = index_missing_keys
1381 if all_index_missing is None:
1382 all_index_missing = set(index_missing_keys)
1383 else:
1384 all_index_missing.intersection_update(index_missing_keys)
1385 if not keys_to_lookup:
1386 break
1387 if all_index_missing is None:
1388 # There were no indexes, so all search keys are 'missing'
1389 missing_keys.update(keys_to_lookup)
1390 keys_to_lookup = None
1391 else:
1392 missing_keys.update(all_index_missing)
1393 keys_to_lookup.difference_update(all_index_missing)
1394 return parent_map, missing_keys
1395
1300 def key_count(self):1396 def key_count(self):
1301 """Return an estimate of the number of keys in this index.1397 """Return an estimate of the number of keys in this index.
13021398
13031399
=== modified file 'bzrlib/inventory.py'
--- bzrlib/inventory.py 2009-08-05 02:30:59 +0000
+++ bzrlib/inventory.py 2009-08-30 23:51:10 +0000
@@ -437,7 +437,13 @@
437 self.text_id is not None):437 self.text_id is not None):
438 checker._report_items.append('directory {%s} has text in revision {%s}'438 checker._report_items.append('directory {%s} has text in revision {%s}'
439 % (self.file_id, rev_id))439 % (self.file_id, rev_id))
440 # Directories are stored as ''.440 # In non rich root repositories we do not expect a file graph for the
441 # root.
442 if self.name == '' and not checker.rich_roots:
443 return
444 # Directories are stored as an empty file, but the file should exist
445 # to provide a per-fileid log. The hash of every directory content is
446 # "da..." below (the sha1sum of '').
441 checker.add_pending_item(rev_id,447 checker.add_pending_item(rev_id,
442 ('texts', self.file_id, self.revision), 'text',448 ('texts', self.file_id, self.revision), 'text',
443 'da39a3ee5e6b4b0d3255bfef95601890afd80709')449 'da39a3ee5e6b4b0d3255bfef95601890afd80709')
@@ -743,6 +749,9 @@
743 """749 """
744 return self.has_id(file_id)750 return self.has_id(file_id)
745751
752 def has_filename(self, filename):
753 return bool(self.path2id(filename))
754
746 def id2path(self, file_id):755 def id2path(self, file_id):
747 """Return as a string the path to file_id.756 """Return as a string the path to file_id.
748757
@@ -751,6 +760,8 @@
751 >>> e = i.add(InventoryFile('foo-id', 'foo.c', parent_id='src-id'))760 >>> e = i.add(InventoryFile('foo-id', 'foo.c', parent_id='src-id'))
752 >>> print i.id2path('foo-id')761 >>> print i.id2path('foo-id')
753 src/foo.c762 src/foo.c
763
764 :raises NoSuchId: If file_id is not present in the inventory.
754 """765 """
755 # get all names, skipping root766 # get all names, skipping root
756 return '/'.join(reversed(767 return '/'.join(reversed(
@@ -1363,9 +1374,6 @@
1363 yield ie1374 yield ie
1364 file_id = ie.parent_id1375 file_id = ie.parent_id
13651376
1366 def has_filename(self, filename):
1367 return bool(self.path2id(filename))
1368
1369 def has_id(self, file_id):1377 def has_id(self, file_id):
1370 return (file_id in self._byid)1378 return (file_id in self._byid)
13711379
13721380
=== modified file 'bzrlib/inventory_delta.py'
--- bzrlib/inventory_delta.py 2009-04-02 05:53:12 +0000
+++ bzrlib/inventory_delta.py 2009-08-13 00:20:29 +0000
@@ -29,6 +29,25 @@
29from bzrlib import inventory29from bzrlib import inventory
30from bzrlib.revision import NULL_REVISION30from bzrlib.revision import NULL_REVISION
3131
32FORMAT_1 = 'bzr inventory delta v1 (bzr 1.14)'
33
34
35class InventoryDeltaError(errors.BzrError):
36 """An error when serializing or deserializing an inventory delta."""
37
38 # Most errors when serializing and deserializing are due to bugs, although
39 # damaged input (i.e. a bug in a different process) could cause
40 # deserialization errors too.
41 internal_error = True
42
43
44class IncompatibleInventoryDelta(errors.BzrError):
45 """The delta could not be deserialised because its contents conflict with
46 the allow_versioned_root or allow_tree_references flags of the
47 deserializer.
48 """
49 internal_error = False
50
3251
33def _directory_content(entry):52def _directory_content(entry):
34 """Serialize the content component of entry which is a directory.53 """Serialize the content component of entry which is a directory.
@@ -49,7 +68,7 @@
49 exec_bytes = ''68 exec_bytes = ''
50 size_exec_sha = (entry.text_size, exec_bytes, entry.text_sha1)69 size_exec_sha = (entry.text_size, exec_bytes, entry.text_sha1)
51 if None in size_exec_sha:70 if None in size_exec_sha:
52 raise errors.BzrError('Missing size or sha for %s' % entry.file_id)71 raise InventoryDeltaError('Missing size or sha for %s' % entry.file_id)
53 return "file\x00%d\x00%s\x00%s" % size_exec_sha72 return "file\x00%d\x00%s\x00%s" % size_exec_sha
5473
5574
@@ -60,7 +79,7 @@
60 """79 """
61 target = entry.symlink_target80 target = entry.symlink_target
62 if target is None:81 if target is None:
63 raise errors.BzrError('Missing target for %s' % entry.file_id)82 raise InventoryDeltaError('Missing target for %s' % entry.file_id)
64 return "link\x00%s" % target.encode('utf8')83 return "link\x00%s" % target.encode('utf8')
6584
6685
@@ -71,7 +90,8 @@
71 """90 """
72 tree_revision = entry.reference_revision91 tree_revision = entry.reference_revision
73 if tree_revision is None:92 if tree_revision is None:
74 raise errors.BzrError('Missing reference revision for %s' % entry.file_id)93 raise InventoryDeltaError(
94 'Missing reference revision for %s' % entry.file_id)
75 return "tree\x00%s" % tree_revision95 return "tree\x00%s" % tree_revision
7696
7797
@@ -115,11 +135,8 @@
115 return result135 return result
116136
117137
118
119class InventoryDeltaSerializer(object):138class InventoryDeltaSerializer(object):
120 """Serialize and deserialize inventory deltas."""139 """Serialize inventory deltas."""
121
122 FORMAT_1 = 'bzr inventory delta v1 (bzr 1.14)'
123140
124 def __init__(self, versioned_root, tree_references):141 def __init__(self, versioned_root, tree_references):
125 """Create an InventoryDeltaSerializer.142 """Create an InventoryDeltaSerializer.
@@ -141,6 +158,9 @@
141 def delta_to_lines(self, old_name, new_name, delta_to_new):158 def delta_to_lines(self, old_name, new_name, delta_to_new):
142 """Return a line sequence for delta_to_new.159 """Return a line sequence for delta_to_new.
143160
161 Both the versioned_root and tree_references flags must be set via
162 require_flags before calling this.
163
144 :param old_name: A UTF8 revision id for the old inventory. May be164 :param old_name: A UTF8 revision id for the old inventory. May be
145 NULL_REVISION if there is no older inventory and delta_to_new165 NULL_REVISION if there is no older inventory and delta_to_new
146 includes the entire inventory contents.166 includes the entire inventory contents.
@@ -150,15 +170,20 @@
150 takes.170 takes.
151 :return: The serialized delta as lines.171 :return: The serialized delta as lines.
152 """172 """
173 if type(old_name) is not str:
174 raise TypeError('old_name should be str, got %r' % (old_name,))
175 if type(new_name) is not str:
176 raise TypeError('new_name should be str, got %r' % (new_name,))
153 lines = ['', '', '', '', '']177 lines = ['', '', '', '', '']
154 to_line = self._delta_item_to_line178 to_line = self._delta_item_to_line
155 for delta_item in delta_to_new:179 for delta_item in delta_to_new:
156 lines.append(to_line(delta_item))180 line = to_line(delta_item, new_name)
157 if lines[-1].__class__ != str:181 if line.__class__ != str:
158 raise errors.BzrError(182 raise InventoryDeltaError(
159 'to_line generated non-str output %r' % lines[-1])183 'to_line generated non-str output %r' % lines[-1])
184 lines.append(line)
160 lines.sort()185 lines.sort()
161 lines[0] = "format: %s\n" % InventoryDeltaSerializer.FORMAT_1186 lines[0] = "format: %s\n" % FORMAT_1
162 lines[1] = "parent: %s\n" % old_name187 lines[1] = "parent: %s\n" % old_name
163 lines[2] = "version: %s\n" % new_name188 lines[2] = "version: %s\n" % new_name
164 lines[3] = "versioned_root: %s\n" % self._serialize_bool(189 lines[3] = "versioned_root: %s\n" % self._serialize_bool(
@@ -173,7 +198,7 @@
173 else:198 else:
174 return "false"199 return "false"
175200
176 def _delta_item_to_line(self, delta_item):201 def _delta_item_to_line(self, delta_item, new_version):
177 """Convert delta_item to a line."""202 """Convert delta_item to a line."""
178 oldpath, newpath, file_id, entry = delta_item203 oldpath, newpath, file_id, entry = delta_item
179 if newpath is None:204 if newpath is None:
@@ -188,6 +213,10 @@
188 oldpath_utf8 = 'None'213 oldpath_utf8 = 'None'
189 else:214 else:
190 oldpath_utf8 = '/' + oldpath.encode('utf8')215 oldpath_utf8 = '/' + oldpath.encode('utf8')
216 if newpath == '/':
217 raise AssertionError(
218 "Bad inventory delta: '/' is not a valid newpath "
219 "(should be '') in delta item %r" % (delta_item,))
191 # TODO: Test real-world utf8 cache hit rate. It may be a win.220 # TODO: Test real-world utf8 cache hit rate. It may be a win.
192 newpath_utf8 = '/' + newpath.encode('utf8')221 newpath_utf8 = '/' + newpath.encode('utf8')
193 # Serialize None as ''222 # Serialize None as ''
@@ -196,58 +225,78 @@
196 last_modified = entry.revision225 last_modified = entry.revision
197 # special cases for /226 # special cases for /
198 if newpath_utf8 == '/' and not self._versioned_root:227 if newpath_utf8 == '/' and not self._versioned_root:
199 if file_id != 'TREE_ROOT':228 # This is an entry for the root, this inventory does not
200 raise errors.BzrError(229 # support versioned roots. So this must be an unversioned
201 'file_id %s is not TREE_ROOT for /' % file_id)230 # root, i.e. last_modified == new revision. Otherwise, this
202 if last_modified is not None:231 # delta is invalid.
203 raise errors.BzrError(232 # Note: the non-rich-root repositories *can* have roots with
204 'Version present for / in %s' % file_id)233 # file-ids other than TREE_ROOT, e.g. repo formats that use the
205 last_modified = NULL_REVISION234 # xml5 serializer.
235 if last_modified != new_version:
236 raise InventoryDeltaError(
237 'Version present for / in %s (%s != %s)'
238 % (file_id, last_modified, new_version))
206 if last_modified is None:239 if last_modified is None:
207 raise errors.BzrError("no version for fileid %s" % file_id)240 raise InventoryDeltaError("no version for fileid %s" % file_id)
208 content = self._entry_to_content[entry.kind](entry)241 content = self._entry_to_content[entry.kind](entry)
209 return ("%s\x00%s\x00%s\x00%s\x00%s\x00%s\n" %242 return ("%s\x00%s\x00%s\x00%s\x00%s\x00%s\n" %
210 (oldpath_utf8, newpath_utf8, file_id, parent_id, last_modified,243 (oldpath_utf8, newpath_utf8, file_id, parent_id, last_modified,
211 content))244 content))
212245
246
247class InventoryDeltaDeserializer(object):
248 """Deserialize inventory deltas."""
249
250 def __init__(self, allow_versioned_root=True, allow_tree_references=True):
251 """Create an InventoryDeltaDeserializer.
252
253 :param versioned_root: If True, any root entry that is seen is expected
254 to be versioned, and root entries can have any fileid.
255 :param tree_references: If True support tree-reference entries.
256 """
257 self._allow_versioned_root = allow_versioned_root
258 self._allow_tree_references = allow_tree_references
259
213 def _deserialize_bool(self, value):260 def _deserialize_bool(self, value):
214 if value == "true":261 if value == "true":
215 return True262 return True
216 elif value == "false":263 elif value == "false":
217 return False264 return False
218 else:265 else:
219 raise errors.BzrError("value %r is not a bool" % (value,))266 raise InventoryDeltaError("value %r is not a bool" % (value,))
220267
221 def parse_text_bytes(self, bytes):268 def parse_text_bytes(self, bytes):
222 """Parse the text bytes of a serialized inventory delta.269 """Parse the text bytes of a serialized inventory delta.
223270
271 If versioned_root and/or tree_references flags were set via
272 require_flags, then the parsed flags must match or a BzrError will be
273 raised.
274
224 :param bytes: The bytes to parse. This can be obtained by calling275 :param bytes: The bytes to parse. This can be obtained by calling
225 delta_to_lines and then doing ''.join(delta_lines).276 delta_to_lines and then doing ''.join(delta_lines).
226 :return: (parent_id, new_id, inventory_delta)277 :return: (parent_id, new_id, versioned_root, tree_references,
278 inventory_delta)
227 """279 """
280 if bytes[-1:] != '\n':
281 last_line = bytes.rsplit('\n', 1)[-1]
282 raise InventoryDeltaError('last line not empty: %r' % (last_line,))
228 lines = bytes.split('\n')[:-1] # discard the last empty line283 lines = bytes.split('\n')[:-1] # discard the last empty line
229 if not lines or lines[0] != 'format: %s' % InventoryDeltaSerializer.FORMAT_1:284 if not lines or lines[0] != 'format: %s' % FORMAT_1:
230 raise errors.BzrError('unknown format %r' % lines[0:1])285 raise InventoryDeltaError('unknown format %r' % lines[0:1])
231 if len(lines) < 2 or not lines[1].startswith('parent: '):286 if len(lines) < 2 or not lines[1].startswith('parent: '):
232 raise errors.BzrError('missing parent: marker')287 raise InventoryDeltaError('missing parent: marker')
233 delta_parent_id = lines[1][8:]288 delta_parent_id = lines[1][8:]
234 if len(lines) < 3 or not lines[2].startswith('version: '):289 if len(lines) < 3 or not lines[2].startswith('version: '):
235 raise errors.BzrError('missing version: marker')290 raise InventoryDeltaError('missing version: marker')
236 delta_version_id = lines[2][9:]291 delta_version_id = lines[2][9:]
237 if len(lines) < 4 or not lines[3].startswith('versioned_root: '):292 if len(lines) < 4 or not lines[3].startswith('versioned_root: '):
238 raise errors.BzrError('missing versioned_root: marker')293 raise InventoryDeltaError('missing versioned_root: marker')
239 delta_versioned_root = self._deserialize_bool(lines[3][16:])294 delta_versioned_root = self._deserialize_bool(lines[3][16:])
240 if len(lines) < 5 or not lines[4].startswith('tree_references: '):295 if len(lines) < 5 or not lines[4].startswith('tree_references: '):
241 raise errors.BzrError('missing tree_references: marker')296 raise InventoryDeltaError('missing tree_references: marker')
242 delta_tree_references = self._deserialize_bool(lines[4][17:])297 delta_tree_references = self._deserialize_bool(lines[4][17:])
243 if delta_versioned_root != self._versioned_root:298 if (not self._allow_versioned_root and delta_versioned_root):
244 raise errors.BzrError(299 raise IncompatibleInventoryDelta("versioned_root not allowed")
245 "serialized versioned_root flag is wrong: %s" %
246 (delta_versioned_root,))
247 if delta_tree_references != self._tree_references:
248 raise errors.BzrError(
249 "serialized tree_references flag is wrong: %s" %
250 (delta_tree_references,))
251 result = []300 result = []
252 seen_ids = set()301 seen_ids = set()
253 line_iter = iter(lines)302 line_iter = iter(lines)
@@ -258,33 +307,58 @@
258 content) = line.split('\x00', 5)307 content) = line.split('\x00', 5)
259 parent_id = parent_id or None308 parent_id = parent_id or None
260 if file_id in seen_ids:309 if file_id in seen_ids:
261 raise errors.BzrError(310 raise InventoryDeltaError(
262 "duplicate file id in inventory delta %r" % lines)311 "duplicate file id in inventory delta %r" % lines)
263 seen_ids.add(file_id)312 seen_ids.add(file_id)
264 if newpath_utf8 == '/' and not delta_versioned_root and (313 if (newpath_utf8 == '/' and not delta_versioned_root and
265 last_modified != 'null:' or file_id != 'TREE_ROOT'):314 last_modified != delta_version_id):
266 raise errors.BzrError("Versioned root found: %r" % line)315 # Delta claims to be not have a versioned root, yet here's
267 elif last_modified[-1] == ':':316 # a root entry with a non-default version.
268 raise errors.BzrError('special revisionid found: %r' % line)317 raise InventoryDeltaError("Versioned root found: %r" % line)
269 if not delta_tree_references and content.startswith('tree\x00'):318 elif newpath_utf8 != 'None' and last_modified[-1] == ':':
270 raise errors.BzrError("Tree reference found: %r" % line)319 # Deletes have a last_modified of null:, but otherwise special
271 content_tuple = tuple(content.split('\x00'))320 # revision ids should not occur.
272 entry = _parse_entry(321 raise InventoryDeltaError('special revisionid found: %r' % line)
273 newpath_utf8, file_id, parent_id, last_modified, content_tuple)322 if content.startswith('tree\x00'):
323 if delta_tree_references is False:
324 raise InventoryDeltaError(
325 "Tree reference found (but header said "
326 "tree_references: false): %r" % line)
327 elif not self._allow_tree_references:
328 raise IncompatibleInventoryDelta(
329 "Tree reference not allowed")
274 if oldpath_utf8 == 'None':330 if oldpath_utf8 == 'None':
275 oldpath = None331 oldpath = None
332 elif oldpath_utf8[:1] != '/':
333 raise InventoryDeltaError(
334 "oldpath invalid (does not start with /): %r"
335 % (oldpath_utf8,))
276 else:336 else:
337 oldpath_utf8 = oldpath_utf8[1:]
277 oldpath = oldpath_utf8.decode('utf8')338 oldpath = oldpath_utf8.decode('utf8')
278 if newpath_utf8 == 'None':339 if newpath_utf8 == 'None':
279 newpath = None340 newpath = None
341 elif newpath_utf8[:1] != '/':
342 raise InventoryDeltaError(
343 "newpath invalid (does not start with /): %r"
344 % (newpath_utf8,))
280 else:345 else:
346 # Trim leading slash
347 newpath_utf8 = newpath_utf8[1:]
281 newpath = newpath_utf8.decode('utf8')348 newpath = newpath_utf8.decode('utf8')
349 content_tuple = tuple(content.split('\x00'))
350 if content_tuple[0] == 'deleted':
351 entry = None
352 else:
353 entry = _parse_entry(
354 newpath, file_id, parent_id, last_modified, content_tuple)
282 delta_item = (oldpath, newpath, file_id, entry)355 delta_item = (oldpath, newpath, file_id, entry)
283 result.append(delta_item)356 result.append(delta_item)
284 return delta_parent_id, delta_version_id, result357 return (delta_parent_id, delta_version_id, delta_versioned_root,
285358 delta_tree_references, result)
286359
287def _parse_entry(utf8_path, file_id, parent_id, last_modified, content):360
361def _parse_entry(path, file_id, parent_id, last_modified, content):
288 entry_factory = {362 entry_factory = {
289 'dir': _dir_to_entry,363 'dir': _dir_to_entry,
290 'file': _file_to_entry,364 'file': _file_to_entry,
@@ -292,8 +366,10 @@
292 'tree': _tree_to_entry,366 'tree': _tree_to_entry,
293 }367 }
294 kind = content[0]368 kind = content[0]
295 path = utf8_path[1:].decode('utf8')369 if path.startswith('/'):
370 raise AssertionError
296 name = basename(path)371 name = basename(path)
297 return entry_factory[content[0]](372 return entry_factory[content[0]](
298 content, name, parent_id, file_id, last_modified)373 content, name, parent_id, file_id, last_modified)
299374
375
300376
=== modified file 'bzrlib/knit.py'
--- bzrlib/knit.py 2009-08-04 04:36:34 +0000
+++ bzrlib/knit.py 2009-09-09 13:05:33 +0000
@@ -1190,6 +1190,19 @@
1190 generator = _VFContentMapGenerator(self, [key])1190 generator = _VFContentMapGenerator(self, [key])
1191 return generator._get_content(key)1191 return generator._get_content(key)
11921192
1193 def get_known_graph_ancestry(self, keys):
1194 """Get a KnownGraph instance with the ancestry of keys."""
1195 parent_map, missing_keys = self._index.find_ancestry(keys)
1196 for fallback in self._fallback_vfs:
1197 if not missing_keys:
1198 break
1199 (f_parent_map, f_missing_keys) = fallback._index.find_ancestry(
1200 missing_keys)
1201 parent_map.update(f_parent_map)
1202 missing_keys = f_missing_keys
1203 kg = _mod_graph.KnownGraph(parent_map)
1204 return kg
1205
1193 def get_parent_map(self, keys):1206 def get_parent_map(self, keys):
1194 """Get a map of the graph parents of keys.1207 """Get a map of the graph parents of keys.
11951208
@@ -1505,10 +1518,10 @@
1505 if source is parent_maps[0]:1518 if source is parent_maps[0]:
1506 # this KnitVersionedFiles1519 # this KnitVersionedFiles
1507 records = [(key, positions[key][1]) for key in keys]1520 records = [(key, positions[key][1]) for key in keys]
1508 for key, raw_data, sha1 in self._read_records_iter_raw(records):1521 for key, raw_data in self._read_records_iter_unchecked(records):
1509 (record_details, index_memo, _) = positions[key]1522 (record_details, index_memo, _) = positions[key]
1510 yield KnitContentFactory(key, global_map[key],1523 yield KnitContentFactory(key, global_map[key],
1511 record_details, sha1, raw_data, self._factory.annotated, None)1524 record_details, None, raw_data, self._factory.annotated, None)
1512 else:1525 else:
1513 vf = self._fallback_vfs[parent_maps.index(source) - 1]1526 vf = self._fallback_vfs[parent_maps.index(source) - 1]
1514 for record in vf.get_record_stream(keys, ordering,1527 for record in vf.get_record_stream(keys, ordering,
@@ -1583,6 +1596,13 @@
1583 # key = basis_parent, value = index entry to add1596 # key = basis_parent, value = index entry to add
1584 buffered_index_entries = {}1597 buffered_index_entries = {}
1585 for record in stream:1598 for record in stream:
1599 kind = record.storage_kind
1600 if kind.startswith('knit-') and kind.endswith('-gz'):
1601 # Check that the ID in the header of the raw knit bytes matches
1602 # the record metadata.
1603 raw_data = record._raw_record
1604 df, rec = self._parse_record_header(record.key, raw_data)
1605 df.close()
1586 buffered = False1606 buffered = False
1587 parents = record.parents1607 parents = record.parents
1588 if record.storage_kind in delta_types:1608 if record.storage_kind in delta_types:
@@ -2560,6 +2580,33 @@
2560 except KeyError:2580 except KeyError:
2561 raise RevisionNotPresent(key, self)2581 raise RevisionNotPresent(key, self)
25622582
2583 def find_ancestry(self, keys):
2584 """See CombinedGraphIndex.find_ancestry()"""
2585 prefixes = set(key[:-1] for key in keys)
2586 self._load_prefixes(prefixes)
2587 result = {}
2588 parent_map = {}
2589 missing_keys = set()
2590 pending_keys = list(keys)
2591 # This assumes that keys will not reference parents in a different
2592 # prefix, which is accurate so far.
2593 while pending_keys:
2594 key = pending_keys.pop()
2595 if key in parent_map:
2596 continue
2597 prefix = key[:-1]
2598 try:
2599 suffix_parents = self._kndx_cache[prefix][0][key[-1]][4]
2600 except KeyError:
2601 missing_keys.add(key)
2602 else:
2603 parent_keys = tuple([prefix + (suffix,)
2604 for suffix in suffix_parents])
2605 parent_map[key] = parent_keys
2606 pending_keys.extend([p for p in parent_keys
2607 if p not in parent_map])
2608 return parent_map, missing_keys
2609
2563 def get_parent_map(self, keys):2610 def get_parent_map(self, keys):
2564 """Get a map of the parents of keys.2611 """Get a map of the parents of keys.
25652612
@@ -2737,9 +2784,20 @@
27372784
2738class _KeyRefs(object):2785class _KeyRefs(object):
27392786
2740 def __init__(self):2787 def __init__(self, track_new_keys=False):
2741 # dict mapping 'key' to 'set of keys referring to that key'2788 # dict mapping 'key' to 'set of keys referring to that key'
2742 self.refs = {}2789 self.refs = {}
2790 if track_new_keys:
2791 # set remembering all new keys
2792 self.new_keys = set()
2793 else:
2794 self.new_keys = None
2795
2796 def clear(self):
2797 if self.refs:
2798 self.refs.clear()
2799 if self.new_keys:
2800 self.new_keys.clear()
27432801
2744 def add_references(self, key, refs):2802 def add_references(self, key, refs):
2745 # Record the new references2803 # Record the new references
@@ -2752,19 +2810,28 @@
2752 # Discard references satisfied by the new key2810 # Discard references satisfied by the new key
2753 self.add_key(key)2811 self.add_key(key)
27542812
2813 def get_new_keys(self):
2814 return self.new_keys
2815
2755 def get_unsatisfied_refs(self):2816 def get_unsatisfied_refs(self):
2756 return self.refs.iterkeys()2817 return self.refs.iterkeys()
27572818
2758 def add_key(self, key):2819 def _satisfy_refs_for_key(self, key):
2759 try:2820 try:
2760 del self.refs[key]2821 del self.refs[key]
2761 except KeyError:2822 except KeyError:
2762 # No keys depended on this key. That's ok.2823 # No keys depended on this key. That's ok.
2763 pass2824 pass
27642825
2765 def add_keys(self, keys):2826 def add_key(self, key):
2827 # satisfy refs for key, and remember that we've seen this key.
2828 self._satisfy_refs_for_key(key)
2829 if self.new_keys is not None:
2830 self.new_keys.add(key)
2831
2832 def satisfy_refs_for_keys(self, keys):
2766 for key in keys:2833 for key in keys:
2767 self.add_key(key)2834 self._satisfy_refs_for_key(key)
27682835
2769 def get_referrers(self):2836 def get_referrers(self):
2770 result = set()2837 result = set()
@@ -2932,7 +2999,7 @@
2932 # If updating this, you should also update2999 # If updating this, you should also update
2933 # groupcompress._GCGraphIndex.get_missing_parents3000 # groupcompress._GCGraphIndex.get_missing_parents
2934 # We may have false positives, so filter those out.3001 # We may have false positives, so filter those out.
2935 self._key_dependencies.add_keys(3002 self._key_dependencies.satisfy_refs_for_keys(
2936 self.get_parent_map(self._key_dependencies.get_unsatisfied_refs()))3003 self.get_parent_map(self._key_dependencies.get_unsatisfied_refs()))
2937 return frozenset(self._key_dependencies.get_unsatisfied_refs())3004 return frozenset(self._key_dependencies.get_unsatisfied_refs())
29383005
@@ -3049,6 +3116,10 @@
3049 options.append('no-eol')3116 options.append('no-eol')
3050 return options3117 return options
30513118
3119 def find_ancestry(self, keys):
3120 """See CombinedGraphIndex.find_ancestry()"""
3121 return self._graph_index.find_ancestry(keys, 0)
3122
3052 def get_parent_map(self, keys):3123 def get_parent_map(self, keys):
3053 """Get a map of the parents of keys.3124 """Get a map of the parents of keys.
30543125
30553126
=== modified file 'bzrlib/lock.py'
--- bzrlib/lock.py 2009-07-20 08:47:58 +0000
+++ bzrlib/lock.py 2009-07-31 16:51:48 +0000
@@ -190,6 +190,13 @@
190 if self.filename in _fcntl_WriteLock._open_locks:190 if self.filename in _fcntl_WriteLock._open_locks:
191 self._clear_f()191 self._clear_f()
192 raise errors.LockContention(self.filename)192 raise errors.LockContention(self.filename)
193 if self.filename in _fcntl_ReadLock._open_locks:
194 if 'strict_locks' in debug.debug_flags:
195 self._clear_f()
196 raise errors.LockContention(self.filename)
197 else:
198 trace.mutter('Write lock taken w/ an open read lock on: %s'
199 % (self.filename,))
193200
194 self._open(self.filename, 'rb+')201 self._open(self.filename, 'rb+')
195 # reserve a slot for this lock - even if the lockf call fails,202 # reserve a slot for this lock - even if the lockf call fails,
@@ -220,6 +227,14 @@
220 def __init__(self, filename):227 def __init__(self, filename):
221 super(_fcntl_ReadLock, self).__init__()228 super(_fcntl_ReadLock, self).__init__()
222 self.filename = osutils.realpath(filename)229 self.filename = osutils.realpath(filename)
230 if self.filename in _fcntl_WriteLock._open_locks:
231 if 'strict_locks' in debug.debug_flags:
232 # We raise before calling _open so we don't need to
233 # _clear_f
234 raise errors.LockContention(self.filename)
235 else:
236 trace.mutter('Read lock taken w/ an open write lock on: %s'
237 % (self.filename,))
223 _fcntl_ReadLock._open_locks.setdefault(self.filename, 0)238 _fcntl_ReadLock._open_locks.setdefault(self.filename, 0)
224 _fcntl_ReadLock._open_locks[self.filename] += 1239 _fcntl_ReadLock._open_locks[self.filename] += 1
225 self._open(filename, 'rb')240 self._open(filename, 'rb')
@@ -418,15 +433,15 @@
418 DWORD, # dwFlagsAndAttributes433 DWORD, # dwFlagsAndAttributes
419 HANDLE # hTemplateFile434 HANDLE # hTemplateFile
420 )((_function_name, ctypes.windll.kernel32))435 )((_function_name, ctypes.windll.kernel32))
421 436
422 INVALID_HANDLE_VALUE = -1437 INVALID_HANDLE_VALUE = -1
423 438
424 GENERIC_READ = 0x80000000439 GENERIC_READ = 0x80000000
425 GENERIC_WRITE = 0x40000000440 GENERIC_WRITE = 0x40000000
426 FILE_SHARE_READ = 1441 FILE_SHARE_READ = 1
427 OPEN_ALWAYS = 4442 OPEN_ALWAYS = 4
428 FILE_ATTRIBUTE_NORMAL = 128443 FILE_ATTRIBUTE_NORMAL = 128
429 444
430 ERROR_ACCESS_DENIED = 5445 ERROR_ACCESS_DENIED = 5
431 ERROR_SHARING_VIOLATION = 32446 ERROR_SHARING_VIOLATION = 32
432447
433448
=== modified file 'bzrlib/lsprof.py'
--- bzrlib/lsprof.py 2009-03-08 06:18:06 +0000
+++ bzrlib/lsprof.py 2009-08-24 21:05:09 +0000
@@ -13,45 +13,74 @@
1313
14__all__ = ['profile', 'Stats']14__all__ = ['profile', 'Stats']
1515
16_g_threadmap = {}
17
18
19def _thread_profile(f, *args, **kwds):
20 # we lose the first profile point for a new thread in order to trampoline
21 # a new Profile object into place
22 global _g_threadmap
23 thr = thread.get_ident()
24 _g_threadmap[thr] = p = Profiler()
25 # this overrides our sys.setprofile hook:
26 p.enable(subcalls=True, builtins=True)
27
28
29def profile(f, *args, **kwds):16def profile(f, *args, **kwds):
30 """Run a function profile.17 """Run a function profile.
3118
32 Exceptions are not caught: If you need stats even when exceptions are to be19 Exceptions are not caught: If you need stats even when exceptions are to be
33 raised, passing in a closure that will catch the exceptions and transform20 raised, pass in a closure that will catch the exceptions and transform them
34 them appropriately for your driver function.21 appropriately for your driver function.
3522
36 :return: The functions return value and a stats object.23 :return: The functions return value and a stats object.
37 """24 """
38 global _g_threadmap25 profiler = BzrProfiler()
39 p = Profiler()26 profiler.start()
40 p.enable(subcalls=True)
41 threading.setprofile(_thread_profile)
42 try:27 try:
43 ret = f(*args, **kwds)28 ret = f(*args, **kwds)
44 finally:29 finally:
45 p.disable()30 stats = profiler.stop()
46 for pp in _g_threadmap.values():31 return ret, stats
32
33
34class BzrProfiler(object):
35 """Bzr utility wrapper around Profiler.
36
37 For most uses the module level 'profile()' function will be suitable.
38 However profiling when a simple wrapped function isn't available may
39 be easier to accomplish using this class.
40
41 To use it, create a BzrProfiler and call start() on it. Some arbitrary
42 time later call stop() to stop profiling and retrieve the statistics
43 from the code executed in the interim.
44 """
45
46 def start(self):
47 """Start profiling.
48
49 This hooks into threading and will record all calls made until
50 stop() is called.
51 """
52 self._g_threadmap = {}
53 self.p = Profiler()
54 self.p.enable(subcalls=True)
55 threading.setprofile(self._thread_profile)
56
57 def stop(self):
58 """Stop profiling.
59
60 This unhooks from threading and cleans up the profiler, returning
61 the gathered Stats object.
62
63 :return: A bzrlib.lsprof.Stats object.
64 """
65 self.p.disable()
66 for pp in self._g_threadmap.values():
47 pp.disable()67 pp.disable()
48 threading.setprofile(None)68 threading.setprofile(None)
69 p = self.p
70 self.p = None
71 threads = {}
72 for tid, pp in self._g_threadmap.items():
73 threads[tid] = Stats(pp.getstats(), {})
74 self._g_threadmap = None
75 return Stats(p.getstats(), threads)
4976
50 threads = {}77 def _thread_profile(self, f, *args, **kwds):
51 for tid, pp in _g_threadmap.items():78 # we lose the first profile point for a new thread in order to
52 threads[tid] = Stats(pp.getstats(), {})79 # trampoline a new Profile object into place
53 _g_threadmap = {}80 thr = thread.get_ident()
54 return ret, Stats(p.getstats(), threads)81 self._g_threadmap[thr] = p = Profiler()
82 # this overrides our sys.setprofile hook:
83 p.enable(subcalls=True, builtins=True)
5584
5685
57class Stats(object):86class Stats(object):
5887
=== modified file 'bzrlib/mail_client.py'
--- bzrlib/mail_client.py 2009-06-10 03:56:49 +0000
+++ bzrlib/mail_client.py 2009-09-02 08:26:27 +0000
@@ -424,6 +424,10 @@
424424
425 _client_commands = ['emacsclient']425 _client_commands = ['emacsclient']
426426
427 def __init__(self, config):
428 super(EmacsMail, self).__init__(config)
429 self.elisp_tmp_file = None
430
427 def _prepare_send_function(self):431 def _prepare_send_function(self):
428 """Write our wrapper function into a temporary file.432 """Write our wrapper function into a temporary file.
429433
@@ -500,6 +504,7 @@
500 if attach_path is not None:504 if attach_path is not None:
501 # Do not create a file if there is no attachment505 # Do not create a file if there is no attachment
502 elisp = self._prepare_send_function()506 elisp = self._prepare_send_function()
507 self.elisp_tmp_file = elisp
503 lmmform = '(load "%s")' % elisp508 lmmform = '(load "%s")' % elisp
504 mmform = '(bzr-add-mime-att "%s")' % \509 mmform = '(bzr-add-mime-att "%s")' % \
505 self._encode_path(attach_path, 'attachment')510 self._encode_path(attach_path, 'attachment')
506511
=== modified file 'bzrlib/merge.py'
--- bzrlib/merge.py 2009-07-02 13:07:14 +0000
+++ bzrlib/merge.py 2009-09-11 13:32:55 +0000
@@ -64,8 +64,12 @@
6464
6565
66def transform_tree(from_tree, to_tree, interesting_ids=None):66def transform_tree(from_tree, to_tree, interesting_ids=None):
67 merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,67 from_tree.lock_tree_write()
68 interesting_ids=interesting_ids, this_tree=from_tree)68 try:
69 merge_inner(from_tree.branch, to_tree, from_tree, ignore_zero=True,
70 interesting_ids=interesting_ids, this_tree=from_tree)
71 finally:
72 from_tree.unlock()
6973
7074
71class Merger(object):75class Merger(object):
@@ -102,6 +106,17 @@
102 self._is_criss_cross = None106 self._is_criss_cross = None
103 self._lca_trees = None107 self._lca_trees = None
104108
109 def cache_trees_with_revision_ids(self, trees):
110 """Cache any tree in trees if it has a revision_id."""
111 for maybe_tree in trees:
112 if maybe_tree is None:
113 continue
114 try:
115 rev_id = maybe_tree.get_revision_id()
116 except AttributeError:
117 continue
118 self._cached_trees[rev_id] = maybe_tree
119
105 @property120 @property
106 def revision_graph(self):121 def revision_graph(self):
107 if self._revision_graph is None:122 if self._revision_graph is None:
@@ -598,19 +613,21 @@
598 self.this_tree.lock_tree_write()613 self.this_tree.lock_tree_write()
599 self.base_tree.lock_read()614 self.base_tree.lock_read()
600 self.other_tree.lock_read()615 self.other_tree.lock_read()
601 self.tt = TreeTransform(self.this_tree, self.pb)
602 try:616 try:
603 self.pp.next_phase()617 self.tt = TreeTransform(self.this_tree, self.pb)
604 self._compute_transform()
605 self.pp.next_phase()
606 results = self.tt.apply(no_conflicts=True)
607 self.write_modified(results)
608 try:618 try:
609 self.this_tree.add_conflicts(self.cooked_conflicts)619 self.pp.next_phase()
610 except UnsupportedOperation:620 self._compute_transform()
611 pass621 self.pp.next_phase()
622 results = self.tt.apply(no_conflicts=True)
623 self.write_modified(results)
624 try:
625 self.this_tree.add_conflicts(self.cooked_conflicts)
626 except UnsupportedOperation:
627 pass
628 finally:
629 self.tt.finalize()
612 finally:630 finally:
613 self.tt.finalize()
614 self.other_tree.unlock()631 self.other_tree.unlock()
615 self.base_tree.unlock()632 self.base_tree.unlock()
616 self.this_tree.unlock()633 self.this_tree.unlock()
@@ -1516,6 +1533,7 @@
1516 get_revision_id = getattr(base_tree, 'get_revision_id', None)1533 get_revision_id = getattr(base_tree, 'get_revision_id', None)
1517 if get_revision_id is None:1534 if get_revision_id is None:
1518 get_revision_id = base_tree.last_revision1535 get_revision_id = base_tree.last_revision
1536 merger.cache_trees_with_revision_ids([other_tree, base_tree, this_tree])
1519 merger.set_base_revision(get_revision_id(), this_branch)1537 merger.set_base_revision(get_revision_id(), this_branch)
1520 return merger.do_merge()1538 return merger.do_merge()
15211539
15221540
=== modified file 'bzrlib/missing.py'
--- bzrlib/missing.py 2009-03-23 14:59:43 +0000
+++ bzrlib/missing.py 2009-08-17 18:52:01 +0000
@@ -138,31 +138,13 @@
138 if not ancestry: #Empty ancestry, no need to do any work138 if not ancestry: #Empty ancestry, no need to do any work
139 return []139 return []
140140
141 mainline_revs, rev_nos, start_rev_id, end_rev_id = log._get_mainline_revs(141 merge_sorted_revisions = branch.iter_merge_sorted_revisions()
142 branch, None, tip_revno)
143 if not mainline_revs:
144 return []
145
146 # This asks for all mainline revisions, which is size-of-history and
147 # should be addressed (but currently the only way to get correct
148 # revnos).
149
150 # mainline_revisions always includes an extra revision at the
151 # beginning, so don't request it.
152 parent_map = dict(((key, value) for key, value
153 in graph.iter_ancestry(mainline_revs[1:])
154 if value is not None))
155 # filter out ghosts; merge_sort errors on ghosts.
156 # XXX: is this needed here ? -- vila080910
157 rev_graph = _mod_repository._strip_NULL_ghosts(parent_map)
158 # XXX: what if rev_graph is empty now ? -- vila080910
159 merge_sorted_revisions = tsort.merge_sort(rev_graph, tip,
160 mainline_revs,
161 generate_revno=True)
162 # Now that we got the correct revnos, keep only the relevant142 # Now that we got the correct revnos, keep only the relevant
163 # revisions.143 # revisions.
164 merge_sorted_revisions = [144 merge_sorted_revisions = [
165 (s, revid, n, d, e) for s, revid, n, d, e in merge_sorted_revisions145 # log.reverse_by_depth expects seq_num to be present, but it is
146 # stripped by iter_merge_sorted_revisions()
147 (0, revid, n, d, e) for revid, n, d, e in merge_sorted_revisions
166 if revid in ancestry]148 if revid in ancestry]
167 if not backward:149 if not backward:
168 merge_sorted_revisions = log.reverse_by_depth(merge_sorted_revisions)150 merge_sorted_revisions = log.reverse_by_depth(merge_sorted_revisions)
169151
=== modified file 'bzrlib/mutabletree.py'
--- bzrlib/mutabletree.py 2009-07-10 08:33:11 +0000
+++ bzrlib/mutabletree.py 2009-09-07 23:14:05 +0000
@@ -226,6 +226,9 @@
226 revprops=revprops,226 revprops=revprops,
227 possible_master_transports=possible_master_transports,227 possible_master_transports=possible_master_transports,
228 *args, **kwargs)228 *args, **kwargs)
229 post_hook_params = PostCommitHookParams(self)
230 for hook in MutableTree.hooks['post_commit']:
231 hook(post_hook_params)
229 return committed_id232 return committed_id
230233
231 def _gather_kinds(self, files, kinds):234 def _gather_kinds(self, files, kinds):
@@ -581,14 +584,32 @@
581 self.create_hook(hooks.HookPoint('start_commit',584 self.create_hook(hooks.HookPoint('start_commit',
582 "Called before a commit is performed on a tree. The start commit "585 "Called before a commit is performed on a tree. The start commit "
583 "hook is able to change the tree before the commit takes place. "586 "hook is able to change the tree before the commit takes place. "
584 "start_commit is called with the bzrlib.tree.MutableTree that the "587 "start_commit is called with the bzrlib.mutabletree.MutableTree "
585 "commit is being performed on.", (1, 4), None))588 "that the commit is being performed on.", (1, 4), None))
589 self.create_hook(hooks.HookPoint('post_commit',
590 "Called after a commit is performed on a tree. The hook is "
591 "called with a bzrlib.mutabletree.PostCommitHookParams object. "
592 "The mutable tree the commit was performed on is available via "
593 "the mutable_tree attribute of that object.", (2, 0), None))
586594
587595
588# install the default hooks into the MutableTree class.596# install the default hooks into the MutableTree class.
589MutableTree.hooks = MutableTreeHooks()597MutableTree.hooks = MutableTreeHooks()
590598
591599
600class PostCommitHookParams(object):
601 """Parameters for the post_commit hook.
602
603 To access the parameters, use the following attributes:
604
605 * mutable_tree - the MutableTree object
606 """
607
608 def __init__(self, mutable_tree):
609 """Create the parameters for the post_commit hook."""
610 self.mutable_tree = mutable_tree
611
612
592class _FastPath(object):613class _FastPath(object):
593 """A path object with fast accessors for things like basename."""614 """A path object with fast accessors for things like basename."""
594615
595616
=== modified file 'bzrlib/plugin.py'
--- bzrlib/plugin.py 2009-03-24 01:53:42 +0000
+++ bzrlib/plugin.py 2009-09-04 15:36:48 +0000
@@ -52,12 +52,16 @@
52from bzrlib import plugins as _mod_plugins52from bzrlib import plugins as _mod_plugins
53""")53""")
5454
55from bzrlib.symbol_versioning import deprecated_function55from bzrlib.symbol_versioning import (
56 deprecated_function,
57 deprecated_in,
58 )
5659
5760
58DEFAULT_PLUGIN_PATH = None61DEFAULT_PLUGIN_PATH = None
59_loaded = False62_loaded = False
6063
64@deprecated_function(deprecated_in((2, 0, 0)))
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches