| Implementation for "Locking" feature | |
| This document describes proposed implementation details for a new | |
| locking system in Subversion. | |
| I. Introduction | |
| II. Client Implementation | |
| A. Overview | |
| B. The "svn:needs-lock" property | |
| 1. Property as enforcement system | |
| 2. Property as communication system | |
| C. Lock manipulation via client | |
| 1. Lock Tokens | |
| Stored as 'entryprops'. That is, just like | |
| last-changed-rev, last-author, etc., tokens come into the | |
| client with an svn:entry: prefix, are filtered by libsvn_wc, | |
| and stored in .svn/entries. We'll call the new entryprops | |
| "svn:entry:lock-token", "svn:entry:lock-owner", | |
| "svn:entry:lock-comment and "svn:entry:lock-creation-date". | |
| (In DAV parlance, what | |
| we call 'entryprops' are called 'live props'.) | |
| libsvn_wc stores the other fields of a lock (owner, | |
| comment, creation-date) in the entries file, so that they | |
| are available for the info command. Note that this | |
| information is only stored if the current WC has a lock on | |
| the file. | |
| 2. New client subcommands | |
| a. Creating a lock | |
| 'svn lock' calls new RA->lock() function, which marshals | |
| BASE rev of file to svn_fs_lock(). FS does a | |
| complimentary out-of-dateness check before creating the | |
| lock. The lock is marshaled back to client, stored in | |
| .svn/entries file. | |
| b. Using a lock | |
| 1. Using a lock to Commit | |
| A new RA layer get_commit_editor2() function will be created. | |
| It takes a hash of path -> lock token from the working | |
| copy. This will be used by the server to check that | |
| the WC locks match the current locks on the paths. A | |
| flag keep_locks will also be added, specifying whether | |
| the committables should be unlocked after a successful commit. | |
| libsvn_client will collect lock tokens during the | |
| harvesting of commit items. If --no-unlock was not | |
| specified, unmodified objects will be treated as commit | |
| items if the WC has a lock on them. A new status flag | |
| in svn_client_commit_info_t indicates that the object | |
| has a lock token. | |
| A new svn_wc_process_committed2 WC function will be | |
| created with a flag indicating whether the lock toke | |
| should be removed as part of the post-commit entry update. | |
| 2. Releasing a lock | |
| svn unlock uses the lock token stored in the WC and | |
| issues an ra->unlock command to the server. | |
| c. Breaking a lock | |
| svn unlock --force will first ask the server for a lock | |
| token and use it in the ra->unlock command to break the | |
| lock. | |
| d. Stealing a lock | |
| svn lock --force uses ra->lock with the force argument set | |
| to TRUE to steal a lock. | |
| e. Discovering/examining locks | |
| 1. seeing lock tokens in a working copy | |
| The client uses the lock information stored in the | |
| entries file to show lock information with svn info and | |
| svn status. | |
| 2. seeing locks in a repository | |
| The server will marshal the lock information as | |
| entryprops when calling the status editor. | |
| svn info URL will use RA->get_lock to get the lock for the | |
| path specified. | |
| 3. 'svn update' behavior | |
| A. At the start of an update, a new version of the | |
| 'reporter' vtable is used to describe not only mixed | |
| revnums to the server, but also existing locktokens. | |
| We need to be careful with protocols when marshaling | |
| this new information to older or newer servers. | |
| In ra_svn a new command will be added to the report | |
| command set for this purpose. | |
| B. If a locktoken is defunct (expired, broken, | |
| whatever), then the server sends a 'deletion' of the | |
| locktoken entryprop, through normal means: the prop | |
| deletion comes into the update_editor, and thus is | |
| removed from .svn/entries. | |
| III. Server Implementation | |
| A. Overview | |
| B. Tracking locks | |
| 1. Define a lock-token: | |
| UUID | |
| owner | |
| comment [optional] | |
| creation-date | |
| expiration-date [optional] | |
| 2. Define a lock-table that maps [fs-path --> lock-token] | |
| Beware the "deletion problem": if a certain path is locked, | |
| then no parent directory of that path can be deleted. | |
| The bad way to solve this problem is to do an O(N) recursive | |
| search of the directory being deleted, making sure no child | |
| is locked. | |
| The good way to solve this problem is to implement the 'lock | |
| table' as a tree. When an object is locked, we create the | |
| locked path in the lock-tree. Then, the mere existence of a | |
| directory in the lock-tree means it has at least one locked | |
| child, and cannot be deleted. This is a much more acceptable | |
| O(logN) search. | |
| C. How to implement locks in libsvn_fs | |
| This option implies that both BDB and FSFS would need to | |
| implement the 'lock tree' in their own way. Any user of | |
| libsvn_fs would automatically get lock-enforcement. | |
| 1. Define an API for associating a user with an open | |
| filesystem. Locks cannot be created/destroyed without a | |
| username, except that the filesystem allows breaking a lock | |
| without a username. | |
| 2. New fs functions for locks: | |
| svn_fs_lock() --> locks a file | |
| svn_fs_unlock() --> unlocks a file | |
| svn_fs_get_locks() --> returns list of locked paths | |
| svn_fs_get_lock() --> discover if a path is locked | |
| These functions don't do anything special, other than | |
| allow one to create/release/examine locks. BDB and FSFS | |
| need to implement these functions independently. | |
| 3. Wrap two of the functions in libsvn_repos, to invoke hooks. | |
| svn_repos_fs_lock() | |
| svn_repos_fs_unlock() | |
| As usually, encourage "good citizens" to use these | |
| wrappers, since they'll invoke the new hook scripts. The | |
| only thing which calls the fs functions directly (and | |
| circumvents hooks) would be a tool like svnadmin (see | |
| 'svnadmin unlock' in UI document.) | |
| 4. Teach a number of fs functions to check for locks, and deal | |
| with them: | |
| svn_fs_node_prop() | |
| svn_fs_apply_textdelta() | |
| svn_fs_apply_text() | |
| svn_fs_make_file() | |
| svn_fs_make_dir() | |
| Check to see if the incoming path is locked. If so, use | |
| the access descriptor to see if the caller has the lock token. | |
| 1. check that the lock-token correctly matches the | |
| lock. (i.e. that the caller isn't using some defunct | |
| or malformed token). | |
| 2. check that the lock owner matches whatever | |
| authenticated username is currently attached to the fs. | |
| svn_fs_copy() | |
| svn_fs_revision_link() | |
| svn_fs_delete() | |
| Same logic as above, except that because these operations | |
| can operate on entire trees, *multiple* lock-tokens might | |
| need to be checked in the access descriptor. | |
| svn_fs_commit_txn() | |
| Same logic, but this is the "final" check. This function | |
| already briefly locks the revisions-table in order to do a | |
| final out-of-date check on everything. In that same vein, | |
| it needs to briefly lock the locks-table, and verify every | |
| single lock. | |
| 5. auto-expiration of locks | |
| The common code which reads locks should be implemented in a | |
| special way: whenever a lock is read, lock expiration should | |
| be checked. If a lock has expired, then the lock should be | |
| removed, and the caller (doing the read operation) should | |
| get nothing back. | |
| As discussed on the list -- the svn_fs_lock() command should | |
| take an optional argument for the 'expiration-date' field. | |
| But this field should *never* be used by anything other than | |
| mod_dav_svn responding to generic DAV clients. We don't | |
| want to expose this feature to the svn client. | |
| D. Configurable Mechanisms | |
| 1. New "pre-" hook scripts | |
| a. pre-lock | |
| b. pre-unlock | |
| 2. New "post-" hook scripts | |
| a. post-lock | |
| b. post-unlock | |
| E. Lock manipulation with server tools | |
| 1. 'svnlook listlocks' | |
| 2. 'svnadmin unlock' | |