~richard-wilbur/trac-bzr/fix-2.6.0

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: