-*- text -*- SUBVERSION LOCKING PROPOSAL [See notes/webdav-general-summary for important background information] ------------------------------------------------------------------- HIGH-LEVEL GOALS: * General WebDAV interoperability: In particular, with basic versioning-unaware WebDAV (rfc 2518) clients that expect support for LOCK and UNLOCK methods, such as MS Office and OpenOffice. * Exclusive and advisory locking for regular svn clients. ------------------------------------------------------------------- LOW-LEVEL FEATURES: * Locks have the following attributes: owner, unique token, scope (exlusive|shared), type (write), depth (0|infinity). These are required for DAV compatibility. * Locked objects can be modified many times without intermediate commits. * Readers of locked paths see the objects as last committed plus any changes made while locked regardless of whether they are reading from the transaction which holds the outstanding changes or from some notion of HEAD. Readers of unlocked paths continue to see only last-committed (unless reading from a transaction which contains modifications to that path). * Locks (and attributes thereof) are not secret. ------------------------------------------------------------------- IMPLEMENTATION * libsvn_fs - New 'locks' table: path -> lock_id. - New root types, "head_root" and "lock_root". - New transaction type "lock". - New transaction properties 'svn:lock-depth', 'svn:lock-path'. - New functions: . svn_fs_lock (svn_fs_lock_t **lock, fs, path, pool); . svn_fs_lock_root (svn_fs_root_t **root, lock_id, pool); . svn_fs_unlock (lock_id, pool); . svn_fs_get_path_lock (svn_fs_lock_t **lock, fs, path, pool); . svn_fs_commit_txn_ex (**conflict_p, *new_revision, txn, apr_array_header_t *lock_ids, pool) - Functions that read from the filesystem must know if they are being asked to read HEAD (versus a specific revision). If reading HEAD, they must let locked nodes override last-committed nodes (???). This depends on the aforementioned head_root, and is absolutely susceptible to fluctuation as HEAD changes. - Functions that write to the filesystem work as normal, except that if writing to a lock transaction, they first verify that the path is indeed locked with that lock transaction. For early out-of-dateness protection, we can wrap fs-write functions with repos-layer code that disallows unauthorized writes to locked paths. This means using svn_fs_get_path_lock(), comparing result->lock_id with the lock_id provided to them. - Commit process still takes a main transaction (which might have no changes), but now additionally takes an array of lock transactions. It first merges the lock transactions into the main transaction, and then does the normal merge of outstanding changes into the main transaction. A single DB transaction guards the promotion of the merged transactions to a revision, the removal of the locks table items (paths taken from the svn:lock-path property of each txn), and the removal of the lock transactions themselves. ------------------------------------------------------------------- CHALLENGES/ANNOYANCES * Operations like move and delete must verify that no locks exist for paths below the deleted/moved thing. This requires a table scan. * Checking for locked-ness involves either scanning the locks table for our path, or doing the "reverse directory walk" checks (like trying to find repos_root from a URL). * In order to set locked files read-only on checkout/update/switch, we have to know the username (because the locking user doesn't get his files read-only). But update is a read operation, and we generally won't query for auth on that. Actually, for updates I think that as long as we pass a lock-id, we're okay to not set stuff to read-only. [[[ This is backwards. You do *not* make locked files read-only for other users; instead, all files that the user doesn't own a lock for should be read-only. That means that on update, etc, files that do _not_ have a lock-id in the WC are set to read-only. Since we want to keep update-modify-merge for mergeable files, the above holds only for unmergeable files. Which files are mergeable is a repository config. By default, text is mergeable, binary is not. But the repos admin could decide that no files are mergeable (i.e., everything reverts to checkout-modify-checkin), or that all are mergeable, or decides based on mime type or another property value... -- brane]]] ------------------------------------------------------------------- ------------------------------------------------------------------- ------------------------------------------------------------------- ------------------------------------------------------------------- UNEDITED STUFF Filesystem changes ================== * svn_fs_lock(path, scope, owner, depth, type) ==> returns a lock-object. Creates a txn if necessary (i.e. in the case of an exclusive lock) [Possible lazy optimization: don't create txn till first write happens] * all svn_fs_* write routines now take ({txn-name | lock-object}, path) if lock-object: - lock-object is converted to a txn via table lookup. - make sure the path's lock object matches the one passed in. - do the infinity check ??? - do the write to the txn. else: - do the write to the txn, but make sure no exclusive locks already exist on the path, else throw error. * all svn_fs_* read routines still take (root, path), but in the special case where root represents the HEAD revision, each read routine must now look up 'path' in the lock table, because the latest version of the file might be in a txn. * svn_fs_unlock(lock-object) aborts the txn, removes lock object from the lock table. * svn_fs_commit_txn([list of lock-objects], regular-txn, cleanup-locks-p): - if called by normal concurrent caller (list == NULL): make sure every mutable node is not only out-of-date, but has no exclusive lock attached. else, throw a conflict. - if called by svn_fs_unlock() (list != NULL): no need to check for out-of-dateness or locks at all. new automerging logic: 1- Automerge the regular-txn, then the list of lock-txns. 2- Possibly loop back to step 1, not needing to remerge the list. 3- Begin db txn: commit and possibly remove all lock txns. * by the way, we want this behavior: an exclusive lock request should be denied if a shared lock already exists. don't know if DAV works this way or not. MOD_DAV_SVN CHANGES * LOCK method calls svn_fs_lock(). sends back lock URI to client. * client does GET: see svn_fs_* read routines above. * client does PUT with lock URI: see svn_write_* routines above. * UNLOCK method with lock URI: calls svn_fs_commit_txn(cleanup-locks-p = true) CLIENT CHANGES [this proposal is only a design for exclusive locks. the discussion about how to do shared "advisory" locks got very complex and heated, so we punted for now.] * call an exclusive lock a "lock" * 'svn st -u' should show not just out-of-date objects, but locks too. * 'svn lock foo.c' creates a lock object on the server. returns a lock-token to client, which is stored in the file's entry. the lock-token is the "auth" for the application, so that only the same application can commit or release the lock. * 'svn unlock foo.c' does the reverse of above. requires that the working copy have the lock-token. --force means you don't need the token (or rather, the client simply "discovers" it by querying the server for the token.) * 'svn commit' changes behavior: when driving the commit editor, the client supplies any lock tokens attached to committables. the commit_editor's close_edit() then passes the tokens as a list to svn_fs_commit_txn(). * 'svn locklist' (?name) asks the server to list all locks within a directory, or if a lock is attached to a file. * 'svn lockrecover' re-fetches the lock-id back into the working copy, in case you've checked out (or are using) a new working copy. * breaking of locks: 1. implement 'svnadmin rmlock' 2. Add pre-/post-lock-hook and pre-/post-unlock-hook, called when locks are taken out or released. 'hook REPOS PATH USER'. These can send email notifications, or even deny locking or unlocking. Can deny anonymous locking, and can deny breaking of locks that