Centralized file-based locking.
There are two extensions called lock: an older and smaller extension by Søren Mathiasen, and a newer and bigger extension by Martin Geisler. This page describes the the newer extension, see https://bitbucket.org/sorenmat/hglock/ for the other extension.
This extension is not distributed with Mercurial.
Author: Martin Geisler
Current Maintainer: Lantiq
Issue tracker: https://bitbucket.org/lantiq/hglock/issues
Mercurial is not well suited for development that involves (binary) files that cannot be merged. One example is specification development using tools such as Framemaker or Word. Parallel versions of files occur naturally in Mercurial, so in trying to prevent this, we are "going against the grain". At the same time, introducing a different tool like Subversion is obviously not very attractive.
3. Lock workflow
The crucial thing is that users stay up to date with changes from others by pulling from the central team repository often and pushing as soon as modifications are done. The workflow looks like this:
- Pull from team repository and update to latest revision.
- Lock files to be modified. Do not modify files locked by someone else!
- Modify and commit.
- Push to team repository. The extension automatically unlocks files when push occurs.
As an exception, locks can be "stolen" or unlocks can be "forced" (e.g. someone went on holiday and left a file locked). Modifications to a previous revision are allowed as long as a separate named branch is used (e.g. to do a bug-fix release). If the team follows this workflow, (unintended) parallel versions will rarely occur --- only when a lock is stolen or unlock forced. The project can specify files for which locking is mandatory. Other files can also be locked.
4. Lock Extension
The proposed design for the lock extension follows.
- The "lock repository" is the repository in which lock information is stored. This is typically the project's central repository, which is shared by the work repositories. It must be explicitly initialized.
- A file is said to be "up to date" in a work repository if there is no later revision on the same branch including it in the lock repository.
To set up the extension, the command 'hg lock-init' is executed in the lock repository, which creates storage inside .hg for lock information. If the lock storage doesn't exist, lock commands fail with an appropriate error message.
The lock repository location can be defined in a configuration file as default-lock in the [paths] section. If the location is not defined, the default path is used. If the lock repository cannot be found or it contains no lock information, lock operations fail with an appropriate message.
File patterns can be defined in a controlled file in the repository root directory called .hglocks. The syntax is as for .hgignore. If a file in the repository matches a pattern in this file, locking is mandatory. If a file does not match any pattern, locking is optional.
Note: Some commands can take multiple files as argument. The operation is not expected to be atomic (that is, 'hg lock', for example, can lock some files and fail to lock others). The command fails overall if the operation fails for an least one file. We ignore this complication in the description of the commands.
'hg init-lock': Turn the current repository into a lock repository. This should be done on the central repository from which others will make clones.
'hg locks': List the locks in the lock repository. The fields and order are: user email, branch, how long the file has been locked, last commit time, path.
'hg lock FILE...': If the file is unmodified, up to date and not locked, lock the file. A user can re-lock a file he has already locked, this refreshes the lock timestamp. Otherwise, fail with an appropriate message. The users email address is sent to the server as part of the lock request (ui.username or email.from)
'hg lock FILE... --force': If the file is unmodified, up to date and locked by someone else, transfer the lock to the user and send an email notifying the original user. Otherwise, fail with an appropriate message.
'hg unlock FILE...': If the file is unmodified, up to date and locked by the user, unlock the file. Otherwise, fail with an appropriate message.
'hg unlock FILE... --force': If the file is locked by someone else, unlock the file and send an email from the server notifying the original user. If the file is not up to date, unlock the file. Otherwise, fail with an appropriate message.
'hg commit': Ignores modified, unlocked files for which locking is mandatory; outputs a warning message for each such file or aborts if such a file was explicitly named on the command line Operates like normal on other files. The lock is refreshed in the lock repository for each locked file that was part of the commit.
'hg push': Unlocks locked files that are part of the revisions to be pushed. Fails if there are any unlocked files in the revisions for which locking is mandatory. (This can happen when a lock is stolen.)
'hg update': New versions of files are handled as follows:
modified, unlocked files for which locking is mandatory: Local versions are overwritten by other versions using the internal:other merge tool and listed as such in the output.
modified, locked files: Other versions are created using the internal:dump merge tool. Then the user resolves. (This case should only occur if a lock has been stolen or an unlock forced. Otherwise the lock policy ensures that merges of files for which locking is mandatory are trivial.)
- All other files are handled as normal.
'hg rename SRC DST': No change needed. If 'SRC' is locked, then it should remain locked on the server even after being renamed to prevent others from trying to lock it.
'hg remove': As for 'hg rename', no change is needed.
5.1. File forcibly unlocked
Scenario with mandatory locking is in effect for 'foo':
I lock foo, change, and commit it. Someone forcibly unlocks foo and I try to push. This fails since foo is not locked. Locking foo fails because the local version is unknown in the repository.
Solution: We must get foo back in the locked state and 'hg lock --force foo' does that. After that, the changesets can be pushed like normal.
The following diagram show the state changes:
As can be seen, the orange error state only has one exit: 'hg lock --force'.
6. Comparison with Subversion
Subversion has a similar feature where clients can lock files in the central repository. This extension is very similar with the following exceptions:
'svn lock' supports an optional message describing the lock.
'svn lock' creates a lock associated with the working copy where 'svn lock' is executed, whereas 'hg lock' creates a lock associated with the user calling 'hg lock'.
'svn commit' will release all locks held by the user, not just locks on files affected by the commit, whereas 'hg push' only releases locks touched by the pushed changesets.
'hg lock --force' and 'hg unlock --force' will try to send an email to notify the user who had his lock stolen.
Subversion uses a per-file property (svn:needs-lock) to manage mandatory locks whereas the extension uses a file (.hglocks) with glob patterns.
Files with svn:needs-lock can be committed without holding a lock whereas the extension will reject a push that touches unlocked files matched by .hglocks.
7. See Also
There is an older and smaller extension by Søren Mathiasen with the same name: https://bitbucket.org/sorenmat/hglock/.