| Anyone who has worked on the libsvn_wc code will have discovered that | |
| the current code is a complicated mess of special cases, and that it | |
| is difficult to understand, inconsistent, slow and buggy. I know this | |
| because I wrote some of it. It's possible that the libsvn_wc code | |
| will gradually evolve into an elegant, efficient, code base; on the | |
| other hand comments like "when we rewrite libsvn_wc" regularly appear | |
| on the dev list. This document is *not* a plan or design for a | |
| rewrite, it's just some of the thoughts of a libsvn_wc hacker. | |
| From Past to Present | |
| ==================== | |
| The original code for libsvn_wc used an implementation that stored | |
| more or less all state information on disk in the .svn area on disk, | |
| so during most operations the entries files were read and written many | |
| times. This led to the development of an API that passed around a lot | |
| of path parameters (as svn_string_t* originally, as const char* now) | |
| and to the development of the svn_io_xxx functions, which also accept | |
| path parameters. The implementation was slow, and didn't scale | |
| particularly well as working copies got larger. To improve things the | |
| current access baton and entries caching system was gradually hacked | |
| in, and libsvn_wc is now faster and scales a bit better, but problems | |
| still remain. | |
| My favourite example of the problems caused by the "path as parameter" | |
| API is svn_wc_props_modified_p. It's basic function is to determine | |
| if the base props file and the working props file are the same or | |
| different, but physical IO operations have to be repeated because they | |
| are buried behind several layers of API. It's difficult to fix | |
| without rewriting, or duplicating, a number of svn_io_xxx and | |
| svn_wc_xxx functions. Aside from the repeated IO itself, each IO | |
| operation also has to repeat the UTF-8 to native path conversion. | |
| The current entries caching makes things faster than in the past, but | |
| has it's own problems. Most operations now cache the entire entries | |
| hierarchy in memory which limits the size of the working copies that | |
| can be handled. The problem is difficult to solve as some operations | |
| make multiple passes--commit for instance makes a first pass searching | |
| for modifications, a second pass reporting to the repository, and a | |
| third pass to do post-commit processing. | |
| The original code also did not always make a distinction between the | |
| versioned hierarchy in the entries file and the physical hierarchy on | |
| disk. Things like using stat() or svn_io_check_path() calls to | |
| determine whether an item was versioned as file or directory do not | |
| work when the working copy on disk is obstructed or incomplete. | |
| The Future | |
| ========== | |
| Some of these ideas are trivial, some of them are difficult to | |
| implement, some of them may not work at all. | |
| - Have an svn_wc_t context object, opaque outside the library, that | |
| would replace the access batons. This would get passed through most | |
| of the libsvn_wc functions and could read/cache the entries files on | |
| demand as the working copy was traversed. It could also cache the | |
| UTF-8 xlate handle. | |
| - Have an API to svn_wc_entry_t, perhaps make the struct opaque, so | |
| that things like URL need not be constructed when the entries file | |
| is read but can be created on demand if required and possibly cached | |
| once created. The aim would be to reduce the memory used by the | |
| entries cache. | |
| - Consider caching physical IO results in svn_wc_entry_t/svn_wc_t. | |
| Should we really stat() any file more than once? This becomes less | |
| important as we reduce the number of IO operations. | |
| - Consider caching UTF-8 to native path conversions either in | |
| svn_wc_t, or svn_wc_entry_t, or locally in functions and using | |
| svn_io_xxx equivalents that accept native paths. This becomes less | |
| important as we reduce the number of IO operations. | |
| - Make interfaces pass svn_wc_entry_t* rather than simple paths. The | |
| public API using const char* paths would remain to be used by | |
| libsvn_client et al. | |
| - Maintain a clear distinction between the versioned hierarchy and the | |
| physical hierarchy when writing code, it's usually a mistake to use | |
| one when the other should be used. To this end, audit the use of | |
| svn_io_check_path(). | |
| - Avoid using stat() to determine if an item is present on disk before | |
| using the item, just use it straight away and handle the error if it | |
| doesn't exist. | |
| - Search out and destroy functions that read and discard entries files | |
| e.g. the apparently "simple" functions like svn_wc_is_wc_root or | |
| check_wc_root. Such overhead is expensive when used by operations | |
| that are not going to do much other work, running status on a single | |
| file for example. The overhead may not matter to a command line | |
| client, but it can matter to a GUI that makes many such calls. | |
| - Get rid of .svn/README.txt and .svn/format. Put the version | |
| information into .svn/entries instead, either in the xml or just as | |
| text on the first line. Reading one file rather then two will put | |
| less load on the physical filesystem. Consider supporting out of | |
| tree .svn directories. Is the size of .svn/entries critical? Would | |
| a custom, less verbose, format be better than XML? | |
| - In the present code most operations are IO bound and have CPU to | |
| spare. Perhaps compressed text-bases would make things faster | |
| rather than slower, by trading spare CPU for reduced IO? | |
| - Keep track of the last text/prop time written into an entries file | |
| and store it in svn_wc_t. Then when we come to do a timestamp sleep | |
| we can do it from the that time rather than the current time. | |
| - Store working file size in the entries file and use it as another | |
| shortcut to detect modifications. This should not need any extra | |
| system calls, the stat() for timestamp can also return the size. | |
| When it triggers it will be much faster than possibly detranslating | |
| and then doing a byte-by-byte comparison. | |
| - Make the entries file smaller. The properties committed-date | |
| committed-rev and last-author are really only needed for keyword | |
| expansion, so only store them if the appropriate svn:keywords value | |
| is present. Note that committed-rev has a more general use as rPREV, | |
| however just about all uses of rPREV involve repository access so | |
| rPREV could be determined via an RA call. Removing the three | |
| properties could reduce entries file size by as much as one third, | |
| it's possible that might make reading, writing and parsing faster. | |
| It would reduce the memory used to cache the entries, an ABI change | |
| to svn_wc_entry_t might reduce it further. | |
| - Optimise property handling | |
| http://svn.haxx.se/dev/archive-2004-11/0318.shtml | |
| * When an item has no props dispense with the base and working | |
| property files. When an item has no property modifications | |
| dispense with the working property file. These changes should make | |
| it possible for svn_wc_props_modified_p to use a single stat() | |
| when there are no property mods. | |
| * It might be possible to replace prop-time in the entries file | |
| with a props-modified flag and so have svn_wc_props_modified_p | |
| avoid all disk IO. | |
| * Consider caching svn:special in the entries file instead of (or as | |
| well as?) the properties file. Status calls svn_wc__get_special | |
| for nearly every file, even unmodified ones, and that reads the | |
| properties file. If the above property handling optimisations | |
| mean that svn_wc_props_modified_p usually avoids all disk IO then | |
| svn_wc__get_special could become a major bottleneck. | |
| * Look at calls to svn_wc__get_keywords, svn_wc__get_eol_style and | |
| svn_wc__get_special, each of those reads the properties file. If | |
| they occur together then consider replacing them with a single call | |
| to svn_wc_prop_list, and perhaps write some functions that accept | |
| the properties hash as an argument. | |
| - Optimise update/incomplete handling to reduce the number of times | |
| the entry file gets written. | |
| http://svn.haxx.se/dev/archive-2005-03/0060.shtml | |
| * Avoid adding incomplete="true" if the revision is not changing. | |
| * Don't write incomplete="true" immediately, cache it in the access | |
| baton and only write it when next writing the entries file. | |
| * Combine removing incomplete="true", and revision bumping, with the | |
| last change due to the update. | |
| - Optimise wcprops handling | |
| The wcprops code suffers from a lot of the historical problems | |
| described in the earlier section. Follow svn_wc__wcprop_get and see | |
| repeated calls to svn_io_check_path, a call to svn_wc_check_wc, etc. | |
| The svn_wc__wcprop_get function already has an access baton so it | |
| should be able to determine whether the path is versioned and whether | |
| it is a file or directory without doing any disk IO. The function | |
| svn_wc__wcprop_set has similar problems. (Run 'svn update' over | |
| ra_dav to trigger these.) Perhaps wcprops could be stored in the | |
| entries file? | |
| - The svn_wc_t context could work in conjunction with a more advanced | |
| svn_wc_crawl_revisions system. This would provide a way of plugging | |
| multiple callbacks into a queue, probably with some sort of ordering | |
| and filtering ability, the aim being to replace most/all of the | |
| existing explicit loops. This would put more of the pool handling in | |
| one central location, it may even be possible to provide different | |
| entry caching schemes. I don't know how practical this idea is, or | |
| even if it is desirable. | |
| - Have a .svn/deleted directory so that schedule delete directories | |
| can be moved out of the working copy. At present a skeleton hierarchy | |
| of schedule delete directories remains in the working copy until the | |
| delete is committed. | |
| - When handling a delete received during update/switch perhaps do it | |
| in two stages. First move the item into a holding area within .svn | |
| and finally delete all such items at the end of the update. This | |
| would allow adds-with-history to use the deleted item and so might | |
| be a way to handle moves (implemented as delete plus add) in the | |
| presence of local modifications. Thought would have to be given to | |
| the revision of the local deleted item, what happens if it doesn't | |
| match the copyfrom revision? Perhaps we could get diffs, rather | |
| than full text, for adds-with-history if the copyfrom source is | |
| reported to the repository? | |
| - Consider implementing atomic move for wc-to-wc moves, rather than | |
| using copy+delete. This would be considerably faster for big | |
| directories, would lead to better revert behaviour, and avoid | |
| case-insensitivity problems (and if we ever get atomic mv in | |
| libsvn_fs then the wc code would be ready for it). | |
| - Consider writing some libsvn_wc compiled C regression tests to allow | |
| more complete coverage. Most of the current libsvn_wc testing is | |
| done via the command line client and it can be hard to get a working | |
| copy into the state necessary to test all code paths. | |
| - There are some basic features that are fragile. Switch has some | |
| bugs that can break a working copy, see issue 1906. I don't know | |
| how the system is supposed to work in theory, let alone how it | |
| should be implemented. Non-recursive checkout is broken, see issue | |
| 695; this probably applies to non-recursive update and switch as well. | |
| - Use absolute paths within libsvn_wc so that "." is not automatically | |
| a wc root. | |
| - Read notes/entries-caching for some details of the logging/caching | |
| in the current libsvn_wc. It's important that writing the entries | |
| file is handled efficiently. |