Fix spurious testsuite bustage introduced in r1390.
[cvs2svn.git] / design-notes.txt
blob8fdc05d600cbe390b7f80c5d9f7c1bdc1ca78f13
1                          How cvs2svn Works
2                          =================
4 A cvs2svn run consists of eight passes.  Each pass saves the data it
5 produces to files on disk, so that a) we don't hold huge amounts of
6 state in memory, and b) the conversion process is resumable.
8 Pass 1:
9 =======
11 The goal of this pass is to write to 'cvs2svn-data.revs' a summary of
12 all the revisions for each RCS file.  Each revision will be
13 represented by one line.  At the end of this stage, the revisions
14 (i.e., the lines) will be grouped by RCS file, not by logical commits.
16 We walk over the repository, processing each RCS file with
17 rcsparse.parse(), using cvs2svn's CollectData class, which is a
18 subclass of rcsparse.Sink(), the parser's callback class.  For each
19 RCS file, the first thing the parser encounters is the administrative
20 header, including the head revision, the principal branch, symbolic
21 names, RCS comments, etc.  The main thing that happens here is that
22 CollectData.define_tag() is invoked on each symbolic name and its
23 attached revision, so all the tags and branches of this file get
24 collected.
26 Next, the parser hits the revision summary section.  That's the part
27 of the RCS file that looks like this:
29    1.6
30    date 2002.06.12.04.54.12;    author captnmark;       state Exp;
31    branches
32         1.6.2.1;
33    next 1.5;
34    
35    1.5
36    date 2002.05.28.18.02.11;    author captnmark;       state Exp;
37    branches;
38    next 1.4;
40    [...]
42 For each revision summary, CollectData.define_revision() is invoked,
43 recording that revision's metadata in various variables of the
44 CollectData class instance.
46 After finishing the revision summaries, the parser invokes
47 CollectData.tree_completed(), which loops over the revision
48 information stored, determining if there are instances where a higher
49 revision was committed "before" a lower one (rare, but it can happen
50 when there was clock skew on the repository machine).  If there are
51 any, it "resyncs" the timestamp of the earlier rev to be just before
52 that of the later rev, but saves the original timestamp in
53 self.rev_data[blah][2], so we can later write out a record to the
54 resync file indicating that an adjustment was made (this makes it
55 possible to catch the other parts of this commit and resync them
56 similarly, more details below).
58 Next, the parser encounters the *real* revision data, which has the
59 log messages and file contents.  For each revision, it invokes
60 CollectData.set_revision_info(), which writes a new line to
61 cvs2svn-data.revs.  The line is constructed by the CVSRevision class -
62 one of its many roles. Here is an example:
64    3dc32955 5afe9b4ba41843d8eb52ae7db47a43eaa9573254 3dc32954 C 1.1 1.2 1.3 1 1 1024 N * 0 0 foo/bar,v
66 The fields are:
68    1.  a fixed-width timestamp
69    2.  a digest of the log message + author
70    3.  a fixed-width timestamp indicating the timestamp of this
71        revision's previous revision (or "*", if it's the first
72        revision on this line of development).
73    4.  the type of change ("A"dd, "C"hange, or "D"elete)
74    5.  the revision number of the previous revision along this line of
75        development (or "*", if it's the first revision on this line of
76        development).
77    6.  the revision number
78    7.  the revision number of the next revision along this line of
79        development (or "*", if it's the last revision on this line of
80        development).
81    8.  1 if the RCS file is in the Attic, "*" if it isn't.
82    9.  1 is the RCS file has the executable bit set, "*" if not.
83    10. The size of the RCS file, in bytes.
84    11. "N" if this revision has non-empty deltatext, else "E" for empty
85    12. the RCS keyword substitution mode ("k", "b", etc), or "*" if none
86    13. the branch on which this commit happened, or "*" if not on a branch
87    14. the number of tags rooted at this revision (followed by their
88        names, space-delimited)  
89    15. the number of branches rooted at this revision (followed by
90        their names, space-delimited) 
91    16. the path of the RCS file in the repository
93 (Of course, in the above example, fields 14 and 15 are "0", so they have
94 no additional data.)
96 Also, for resync'd revisions, a line like this is written out to
97 'cvs2svn-data.resync':
99    3d6c1329 18a215a05abea1c6c155dcc7283b88ae7ce23502 3d6c1328
101 The fields are:
103    NEW_TIMESTAMP   DIGEST   OLD_TIMESTAMP
105 (The resync file will be explained later.)
107 That's it -- the RCS file is done.
109 When every RCS file is done, Pass 1 is complete, and:
111    - cvs2svn-data.revs contains a summary of every RCS file's
112      revisions.  All the revisions for a given RCS file are grouped
113      together, but note that the groups are in no particular order.
114      In other words, you can't yet identify the commits from looking
115      at these lines; a multi-file commit will be scattered all over
116      the place.
118    - cvs2svn-data.resync contains a small amount of resync data, in
119      no particular order.
121 Pass 2:
122 =======
124 This is where the resync file is used.  The goal of this pass is to
125 convert cvs2svn-data.revs to a new file, 'cvs2svn-data.c-revs' (clean
126 revs).  It's the same as the original file, except for some resync'd
127 timestamps.
129 First, read the whole resync file into a hash table that maps each
130 author+log digest to a list of lists.  Each sublist represents one of
131 the timestamp adjustments from Pass 1, and looks like this:
133    [old_time_lower, old_time_upper, new_time]
135 The reason to map each digest to a list of sublists, instead of to one
136 list, is that sometimes you'll get the same digest for unrelated
137 commits (for example, the same author commits many times using the
138 empty log message, or a log message that just says "Doc tweaks.").  So
139 each digest may need to "fan out" to cover multiple commits, but
140 without accidentally unifying those commits.
142 Now we loop over cvs2svn-data.revs, writing each line out to
143 'cvs2svn-data.c-revs'.  Most lines are written out unchanged, but
144 those whose digest matches some resync entry, and appear to be part of
145 the same commit as one of the sublists in that entry, get tweaked.
146 The tweak is to adjust the commit time of the line to the new_time,
147 which is taken from the resync hash and results from the adjustment
148 described in Pass 1.
150 The way we figure out whether a given line needs to be tweaked is to
151 loop over all the sublists, seeing if this commit's original time
152 falls within the old<-->new time range for the current sublist.  If it
153 does, we tweak the line before writing it out, and then conditionally
154 adjust the sublist's range to account for the timestamp we just
155 adjusted (since it could be an outlier).  Note that this could, in
156 theory, result in separate commits being accidentally unified, since
157 we might gradually adjust the two sides of the range such that they are
158 eventually more than COMMIT_THRESHOLD seconds apart.  However, this is
159 really a case of CVS not recording enough information to disambiguate
160 the commits; we'd know we have a time range that exceeds the
161 COMMIT_THRESHOLD, but we wouldn't necessarily know where to divide it
162 up.  We could try some clever heuristic, but for now it's not
163 important -- after all, we're talking about commits that weren't
164 important enough to have a distinctive log message anyway, so does it
165 really matter if a couple of them accidentally get unified?  Probably
166 not.
168 NOTE: We currently have a fairly major bug in our resync code.  The
169 resync_bug test demonstrates it.  The bug is that, when resyncing in
170 pass 2, we take no care not to move cvs revisions before previous
171 cvs revisions of the same file, thus creating the very problem we were
172 attempting to avoid.
174 Pass 3:
175 =======
177 This is where we deduce the changesets, that is, the grouping of file
178 changes into single commits.
180 It's very simple -- run 'sort' on cvs2svn-data.c-revs, converting it
181 to 'cvs2svn-data.s-revs'.  Because of the way the data is laid out,
182 this causes commits with the same digest (that is, the same author and
183 log message) to be grouped together.  Poof!  We now have the CVS
184 changes grouped by logical commit.
186 In some cases, the changes in a given commit may be interleaved with
187 other commits that went on at the same time, because the sort gives
188 precedence to date before log digest.  However, Pass 4 detects this by
189 seeing that the log digest is different, and reseparates the commits.
191 Pass 4:
192 =======
194 This pass has two primary objectives:
196 1. Create a database that maps CVSRevision unique keys to the actual
197    CVSRevision string from the revs file (whose format is described
198    above in pass 1).  This results in a database containing one
199    key-value pair for each line in the revs file.  This gives us the
200    ability to pass around these smaller keys instead of whole CVS
201    revisions (which look like lines from the s-revs file).  See the
202    CVSRevision class for more details on what the unique key is.
204 2. Find and create a database containing the last CVS revision that is
205    a source (also referred to as an "opening" revision) for all
206    symbolic names.  This will result in a database containing
207    key-value pairs whose key is the unique key for a CVSRevision, and
208    whose value is a list of symbolic names for which that CVSRevision
209    is the last "opening."
211    The format for this file is:
213        cvs-symname-last-revs.db:
214             Key                      Value    
215             CVS Revision             array of Symbolic names
216      
217        For example:
219             1.38/foo/bar/baz.txt,v  --> [TAG11, BRANCH38]
220             1.93/foo/qux/bat.c,v    --> [TAG39]
221             1.4/foo/bar/baz.txt,v   --> [BRANCH48, BRANCH37]
222             1.18/foo/bar/quux.txt,v --> [TAG320, TAG1178]
223    
224 Pass 5:
225 =======
227 Primarily, this pass gathers CVS revisions into Subversion revisions
228 (a Subversion revision is comprised of one or more CVS revisions)
229 before we actually begin committing (where "committing" means either
230 to a Subversion repository or to a dump file).
232 This pass does the following:
234 1. Creates a database file to map Subversion Revision numbers to their
235    corresponding CVS Revisions (cvs2svn-svn-revnums-to-cvs-revs.db).
236    Creates another database file to map CVS Revisions to their
237    Subversion Revision numbers (cvs2svn-cvs-revs-to-svn-revnums.db).
239 2. When a file is copied to a symbolic name in cvs2svn, there are a
240    range of valid Subversion revisions that we can copy the file from.
241    The first valid Subversion revision number for a symbolic name is
242    called the "Opening", and the first *invalid* Subversion revision
243    number encountered after the "Opening" is called the "Closing".  In
244    this pass, the SymbolingsLogger class writes one line to
245    cvs2svn-symbolic-names.txt per CVS file, per symbolic name, per
246    opening or closing. 
248 3. For each CVS Revision in s-revs, we write out a line (for each
249    symbolic name that it opens) to a symbolic-names.txt file if it is
250    the first possible source revision (the "opening" revision) for a
251    copy to create a branch or tag, or if it is the last possible
252    revision (the "closing" revision) for a copy to create a branch or
253    tag.  Not every opening will have a corresponding closing.
255    The format of each line is:
257        SYMBOLIC_NAME SVN_REVNUM TYPE CVSRevision.unique_key()
258        
259    For example:
261        MY_TAG1 234 O 1.3/foo/bar/baz.txt,v
262        MY_BRANCH3 245 O 1.13/foo/qux/bat.c,v
263        MY_TAG1 241 C 1.4/foo/bar/baz.txt,v
264        MY_BRANCH_BLAH 201 O 1.1/foo/bar/quux.txt,v
266    Here is what the columns mean:
268    SYMBOLIC_NAME: The name of the branch or tag that starts or ends
269                   in this CVS Revision (There can be multiples per
270                   CVS rev)
272    SVN_REVNUM: The Subversion revision number that is the opening or
273                closing for this SYMBOLIC_NAME.
275    TYPE: "O" for Openings and "C" for Closings.
277    CVSRevision.unique_key(): This is a unique key that identifies
278                              the CVSRevision where this opening or
279                              closing happened.
280                                
281    See SymbolingsLogger for more details.
283 Pass 6:
284 =======
286 This pass merely sorts cvs2svn-symbolic-names.txt into
287 cvs2svn-symbolic-names-s.txt.  This orders the file first by symbolic
288 name, and second by Subversion revision number, thus grouping all
289 openings and closings for each symbolic name together.
291 Pass 7:
292 =======
294 This pass iterates through all the lines in
295 cvs2svn-symbolic-names-s.txt, writing out a database file mapping
296 SYMBOLIC_NAME to the file offset in SYMBOL_OPENINGS_CLOSINGS_SORTED
297 where SYMBOLIC_NAME is first encountered.  This will allow us to seek
298 to the various offsets in the file and sequentially read only the
299 openings and closings that we need.
301 Pass 8:
302 =======
304 The 8th pass will has very little "thinking" to do--it basically going
305 opens the svn-nums-to-cvs-revs.db and, starting with Subversion
306 revision 2 (revision 1 creates /trunk, /tags, and /branches), and
307 sequentially play out all the commits to either a Subversion
308 repository or to a dumpfile.
310 In --dump-only mode, the result of this pass is a Subversion
311 repository dumpfile (suitable for input to 'svnadmin load').  The
312 dumpfile is the data's last static stage: last chance to check over
313 the data, run it through svndumpfilter, move the dumpfile to another
314 machine, etc.
316 However, when not in --dump-only mode, no full dumpfile is created for
317 subsequent load into a Subversion repository.  Instead, miniature
318 dumpfiles represent a single revision are created, loaded into the
319 repository, and then removed.
321 In both modes, the dumpfile revisions are created by walking through
322 cvs2svn-data.s-revs.
324                   ===============================
325                       Branches and Tags Plan.
326                   ===============================
328 This pass is also where tag and branch creation is done.  Since
329 subversion does tags and branches by copying from existing revisions
330 (then maybe editing the copy, making subcopies underneath, etc), the
331 big question for cvs2svn is how to achieve the minimum number of
332 operations per creation.  For example, if it's possible to get the
333 right tag by just copying revision 53, then it's better to do that
334 than, say, copying revision 51 and then sub-copying in bits of
335 revision 52 and 53.
337 Also, since CVS does not version symbolic names, there is the
338 secondary question of *when* to create a particular tag or branch.
339 For example, a tag might have been made at any time after the youngest
340 commit included in it, or might even have been made piecemeal; and the
341 same is true for a branch, with the added constraint that for any
342 particular file, the branch must have been created before the first
343 commit on the branch.
345 Answering the second question first: cvs2svn creates tags as soon as
346 possible and branches as late as possible.
348 Tags are created as soon cvs2svn encounters the last CVS Revision that
349 is a source for that tag.  The whole tag is created in one Subversion
350 commit.
352 For branches, this is "just in time" creation -- the moment it sees
353 the first commit on a branch, it snaps the entire branch into
354 existence (or as much of it as possible), and then outputs the branch
355 commit.
357 The reason we say "as much of it as possible" is that it's possible to
358 have a branch where some files have branch commits occuring earlier
359 than the other files even have the source revisions from which the
360 branch sprouts (this can happen if the branch was created piecemeal,
361 for example).  In this case, we create as much of the branch as we
362 can, that is, as much of it as there are source revisions available to
363 copy, and leave the rest for later.  "Later" might mean just until
364 other branch commits come in, or else during a cleanup stage that
365 happens at the end of this pass (about which more later).
367 How just-in-time branch creation works:
369 In order to make the "best" set of copies/deletes when creating a
370 branch, cvs2svn keeps track of two sets of trees while it's making
371 commits:
373    1. A skeleton mirror of the subversion repository, that is, an
374       array of revisions, with a tree hanging off each revision.  (The
375       "array" is actually implemented as an anydbm database itself,
376       mapping string representations of numbers to root keys.)
378    2. A tree for each CVS symbolic name, and the svn file/directory
379       revisions from which various parts of that tree could be copied.
381 Both tree sets live in anydbm databases, using the same basic schema:
382 unique keys map to marshal.dumps() representations of dictionaries,
383 which in turn map entry names to other unique keys:
385    root_key  ==> { entryname1 : entrykey1, entryname2 : entrykey2, ... }
386    entrykey1 ==> { entrynameX : entrykeyX, ... }
387    entrykey2 ==> { entrynameY : entrykeyY, ... }
388    entrykeyX ==> { etc, etc ...}
389    entrykeyY ==> { etc, etc ...}
391 (The leaf nodes -- files -- are also dictionaries, for simplicity.)
393 The repository mirror allows cvs2svn to remember what paths exist in
394 what revisions.
396 For details on how branches and tags are created, please see the
397 docstring the SymbolingsLogger class (and its methods).
399 -*- -*- -*- -*- -*- -*- -*- -*- -*- -*- -*- -*- -*- -*- -*- -*- -*- -*-
400 - -*- -*- -*- -*- -*- -*- -*- -*- -*- -*- -*- -*- -*- -*- -*- -*- -*- -
401 -*- -*- -*- -*- -*- -*- -*- -*- -*- -*- -*- -*- -*- -*- -*- -*- -*- -*-
403 Some older notes and ideas about cvs2svn.  Not deleted, because they
404 may contain suggestions for future improvements in design.
406 -----------------------------------------------------------------------
408 An email from John Gardiner Myers <jgmyers@speakeasy.net> about some
409 considerations for the tool.
411 ------
412 From: John Gardiner Myers <jgmyers@speakeasy.net>                     
413 Subject: Thoughts on CVS to SVN conversion
414 To: gstein@lyra.org                                  
415 Date: Sun, 15 Apr 2001 17:47:10 -0700
417 Some things you may want to consider for a CVS to SVN conversion utility:
419 If converting a CVS repository to SVN takes days, it would be good for     
420 the conversion utility to keep its progress state on disk.  If the
421 conversion fails halfway through due to a network outage or power
422 failure, that would allow the conversion to be resumed where it left off
423 instead of having to start over from an empty SVN repository.
425 It is a short step from there to allowing periodic updates of a
426 read-only SVN repository from a read/write CVS repository.  This allows
427 the more relaxed conversion procedure:
429 1) Create SVN repository writable only by the conversion tool.
430 2) Update SVN repository from CVS repository.
431 3) Announce the time of CVS to SVN cutover.
432 4) Repeat step (2) as needed.
433 5) Disable commits to CVS repository, making it read-only.
434 6) Repeat step (2).
435 7) Enable commits to SVN repository.
436 8) Wait for developers to move their workspaces to SVN.
437 9) Decomission the CVS repository.
439 You may forward this message or parts of it as you seem fit.
440 ------
442 -----------------------------------------------------------------------
444 Further design thoughts from Greg Stein <gstein@lyra.org>
446 * timestamp the beginning of the process. ignore any commits that
447   occur after that timestamp; otherwise, you could miss portions of a
448   commit (e.g. scan A; commit occurs to A and B; scan B; create SVN
449   revision for items in B; we missed A)
451 * the above timestamp can also be used for John's "grab any updates
452   that were missed in the previous pass."
454 * for each file processed, watch out for simultaneous commits. this
455   may cause a problem during the reading/scanning/parsing of the file,
456   or the parse succeeds but the results are garbaged. this could be
457   fixed with a CVS lock, but I'd prefer read-only access.
459   algorithm: get the mtime before opening the file. if an error occurs
460   during reading, and the mtime has changed, then restart the file. if
461   the read is successful, but the mtime changed, then restart the
462   file.
464 * use a separate log to track unique branches and non-branched forks
465   of revision history (Q: is it possible to create, say, 1.4.1.3
466   without a "real" branch?). this log can then be used to create a
467   /branches/ directory in the SVN repository.
469   Note: we want to determine some way to coalesce branches across
470   files. It can't be based on name, though, since the same branch name
471   could be used in multiple places, yet they are semantically
472   different branches. Given files R, S, and T with branch B, we can
473   tie those files' branch B into a "semantic group" whenever we see
474   commit groups on a branch touching multiple files. Files that are
475   have a (named) branch but no commits on it are simply ignored. For
476   each "semantic group" of a branch, we'd create a branch based on
477   their common ancestor, then make the changes on the children as
478   necessary. For single-file commits to a branch, we could use
479   heuristics (pathname analysis) to add these to a group (and log what
480   we did), or we could put them in a "reject" kind of file for a human
481   to tell us what to do (the human would edit a config file of some
482   kind to instruct the converter).
484 * if we have access to the CVSROOT/history, then we could process tags
485   properly. otherwise, we can only use heuristics or configuration
486   info to group up tags (branches can use commits; there are no
487   commits associated with tags)
489 * ideally, we store every bit of data from the ,v files to enable a
490   complete restoration of the CVS repository. this could be done by
491   storing properties with CVS revision numbers and stuff (i.e. all
492   metadata not already embodied by SVN would go into properties)
494 * how do we track the "states"? I presume "dead" is simply deleting
495   the entry from SVN. what are the other legal states, and do we need
496   to do anything with them?
498 * where do we put the "description"? how about locks, access list,
499   keyword flags, etc.
501 * note that using something like the SourceForge repository will be an
502   ideal test case. people *move* their repositories there, which means
503   that all kinds of stuff can be found in those repositories, from
504   wherever people used to run them, and under whatever development
505   policies may have been used.
507   For example: I found one of the projects with a "permissions 644;"
508   line in the "gnuplot" repository.  Most RCS releases issue warnings
509   about that (although they properly handle/skip the lines), and CVS
510   ignores RCS newphrases altogether.