1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
|
=======================
Implementation notes
=======================
Revision format
===============
The format of trac "revs" is in flux.
Currently the following guidelines are used:
- A revision as Trac sees is always consists of a branch name followed
by a comma and some string identifying the revision. The branch name
is important, as Trac thinks of the repository as a single tree, so
a revision id by itself wouldn't give us an absolute path in that
tree if there were multiple branches containing that revision.
- Since Trac assumes revision numbers are global (as they are for svn)
and it doesn't give enough context for queries (e.g. path), there's no
way around this for now. This may change in the future when multi-repo
support is available (Trac 0.12) and supported on the trac-bzr side.
- If there is only a single branch at the root of the directory
configured as the bzr repository in Trac, then no branch name will
be included in revision strings. In this case, simple revision
numbers will work as Trac links, too. Maybe a future version of
trac-bzr will allow users to configure a "default branch" to obtain
simple revision names even when dealing with multiple branches.
- In order to be able to use simple revision numbers in TracLinks, one could
write a wiki syntax parser which translates e.g. specifications like
``[tracsource:/foo/bar/baz@10]`` into link to the source browser with full
revision specification (i.e. ``.../browser/foo/bar/baz?rev=foo,10``) while
making sure things are unambiguous.
- The way revision numbers are displayed in the source browser is controlled
by Trac's internal Genshi template. In Trac >= 0.11 it would be possible
to affect that by writing a component which would act as postprocessing
hook with it's own Genshi template which reformats such links (i.e. the
hrefs would still need to use the same format but the visible text can be
rendered as simple numbers, e.g. check the ``Branches`` macro).
- When generating revision strings, we prefer (dotted) revision
numbers over bzr revision identifiers. Only when the revision in
question hasn't been merged into that branch yet we emit a revision
id instead. Usually people should never encounter such revisions
trough Trac links, and we might simply reject them in the future,
but for now they're handled in this fashion.
- Wenn accepting revision strings, we accept both dotted revision
numbers and revision identifiers.
- Some characters are special to Trac, and shouldn't be part of a
revision as Trac sees it. These include ``/`` and ``@``. For this
reason, slashes in the branch name are replaced by commas, and
revision identifiers will always be url-quoted. Branch names
containing commas or percent signs will be url-quoted as well, which
will ensure backwards compatibility with versions that used ot
url-quote slashes and colons, so we have to url-unquote branch
paths.
- It seems that Trac takes care of quoting special characters, percent
signs in particular, when generating links, and will unquote them
before handing them to Trac. If you find an indication to the
contrary, it might be a bug in Trac.
Miscellaneous
=============
- trac wants to have a total ordering on revs. If one of the two
revisions to be compared is an ancestor of the other, then we can
compare them using the revision graph. bzrlib provides
``Graph.is_ancestor`` for a one-way comparison. It internally uses
``Graph.heads``, which we can use to get both ways at once. If the
two branches are uncomparable using the partial ordering given by
the ancestry graph, we compare them using timestamps instead. In the
presence of a clock skew, this can lead to inconsistent results: if
*a* is ancestor of *b* but *a* has a later timestamp than *b*, and
if *c* is uncomparable to either, then by ancestry copparison
*a* < *b* but by time comparison *b* < *c* < *a*.
- trac occasionally compares path values, which means we have to be
careful about "normalizing" those. Currently the format is the one
bzr hands us: always omit the starting "/" (which makes the root "").
What trac uses for svn is "/" for the root, "foo/bar" for everything
else. Hopefully it will cope with our usage. trac also occasionally
passes a None path, which is taken as meaning the root ("").
(Example of where this bit in the past: if Node.get_history changes
the path it returns trac interprets this as a move, even if we do not
pass the corresponding constant. This means that if the Node is
constructed from a non-normalized path starting with a "/" and we switch
to a path with no starting "/" when the node *content* first changes
trac picks it up as a move.)
Again trac may pass in a normalized path, so make sure normalizing twice
is safe. Notice that we do not always normalize passed in paths, since
bzr should handle anything trac hands us. We just need to make sure
everything that escapes back to trac is normalized.
- trac wants the revision log of a dir to include what happens to its
contents. bzr only notices changes to the dir itself (renames and moves
of the dir, not its contents).
One case where this is very important is get_previous of the root
node. Since you can't rename or move the root this simply does not
exist for bzr. Trac uses this to get the previous revision to
display a changeset, so we really need to do this in the trac (svn)
way.
Another case is the revision attribute of BzrDirNode. If we pick the
entry.revision for that we do not see the changes in subdirectories
in the directory listings (as people expect from bzr/svn). If we
pick the revisiontree revision id (which is the one the user is
using to find us) then all subdirectories get the same log message
(the one of the most recent commit to the tree, not to something
under that subdir).
Performance
===========
Constructing a BzrDirNode is slow because it walks its children to
determine the right "most recent" revision. Because it is pretty
common to iterate over the children afterwards and we need to
determine their most recent revision anyway we cache those values
(``BzrDirNode.revcache``). Speeds up the source browser in a directory
with a couple of subdirs.
``BzrDirNode.get_history`` is a bit slow and pretty memory-hungry. Again
the need to manually pick up changes to children is the root cause.
It has to construct full inventories and/or deltas between revisions
to pick up changes to children, while for the other node types we just
have to open the "versionedfile" for that particular file.
Probably the most questionable optimization is keeping branches locked
for the whole request. This provides a *very* noticable speed boost by
keeping the branch locked for the entire web request (try timing a
"log" of the root dir, which pulls in a hundred BzrChangesets). And at
a glance it looks like trac was designed with this thing in mind: it
has a close method for "closing the connection". Unfortunately it does
not actually call this method... We have a __del__ method to try to
improve the chance the branch is unlocked, and bzr has one too, and
this seems to be working so far. It is probably not really reliable
though. And care has to be taken to avoid uncollectable cycles with
objects containing __del__ methods, as this would cause serious memory
leaks.
Testsuite
=========
The testsuite can be run using one of the many Python testsuite runners.
Probably the easiest way to run all tests is using distribute:
$ python setup.py test
.. vim: ft=rst
.. emacs
Local Variables:
mode: rst
End:
|