share.py
216 lines
| 7.3 KiB
| text/x-python
|
PythonLexer
/ hgext / share.py
Matt Mackall
|
r8801 | # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com> | ||
# | ||||
# This software may be used and distributed according to the terms of the | ||||
Matt Mackall
|
r10263 | # GNU General Public License version 2 or any later version. | ||
Matt Mackall
|
r8801 | |||
Gregory Szorc
|
r25761 | '''share a common history between several working directories | ||
Automatic Pooled Storage for Clones | ||||
----------------------------------- | ||||
When this extension is active, :hg:`clone` can be configured to | ||||
automatically share/pool storage across multiple clones. This | ||||
mode effectively converts :hg:`clone` to :hg:`clone` + :hg:`share`. | ||||
The benefit of using this mode is the automatic management of | ||||
store paths and intelligent pooling of related repositories. | ||||
The following ``share.`` config options influence this feature: | ||||
Matt Mackall
|
r25851 | ``share.pool`` | ||
Gregory Szorc
|
r25761 | Filesystem path where shared repository data will be stored. When | ||
defined, :hg:`clone` will automatically use shared repository | ||||
storage instead of creating a store inside each clone. | ||||
Matt Mackall
|
r25851 | ``share.poolnaming`` | ||
Gregory Szorc
|
r25761 | How directory names in ``share.pool`` are constructed. | ||
"identity" means the name is derived from the first changeset in the | ||||
repository. In this mode, different remotes share storage if their | ||||
root/initial changeset is identical. In this mode, the local shared | ||||
repository is an aggregate of all encountered remote repositories. | ||||
"remote" means the name is derived from the source repository's | ||||
path or URL. In this mode, storage is only shared if the path or URL | ||||
requested in the :hg:`clone` command matches exactly to a repository | ||||
that was cloned before. | ||||
The default naming mode is "identity." | ||||
''' | ||||
Dirkjan Ochtman
|
r8873 | |||
Pulkit Goyal
|
r29485 | from __future__ import absolute_import | ||
import errno | ||||
Matt Mackall
|
r8801 | from mercurial.i18n import _ | ||
Pulkit Goyal
|
r29485 | from mercurial import ( | ||
bookmarks, | ||||
cmdutil, | ||||
commands, | ||||
error, | ||||
extensions, | ||||
hg, | ||||
FUJIWARA Katsunori
|
r31052 | txnutil, | ||
Pulkit Goyal
|
r29485 | util, | ||
) | ||||
repository = hg.repository | ||||
parseurl = hg.parseurl | ||||
Simon Heimberg
|
r15079 | |||
Gregory Szorc
|
r21253 | cmdtable = {} | ||
command = cmdutil.command(cmdtable) | ||||
Augie Fackler
|
r29841 | # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for | ||
Augie Fackler
|
r25186 | # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should | ||
# be specifying the version(s) of Mercurial they are tested with, or | ||||
# leave the attribute unspecified. | ||||
Augie Fackler
|
r29841 | testedwith = 'ships-with-hg-core' | ||
Augie Fackler
|
r16743 | |||
Gregory Szorc
|
r21253 | @command('share', | ||
Yuya Nishihara
|
r24364 | [('U', 'noupdate', None, _('do not create a working directory')), | ||
Ryan McElroy
|
r23614 | ('B', 'bookmarks', None, _('also share bookmarks'))], | ||
_('[-U] [-B] SOURCE [DEST]'), | ||||
Gregory Szorc
|
r21772 | norepo=True) | ||
Ryan McElroy
|
r23614 | def share(ui, source, dest=None, noupdate=False, bookmarks=False): | ||
Martin Geisler
|
r10798 | """create a new shared repository | ||
Matt Mackall
|
r8801 | |||
Martin Geisler
|
r9273 | Initialize a new repository and working directory that shares its | ||
Ryan McElroy
|
r23614 | history (and optionally bookmarks) with another repository. | ||
Matt Mackall
|
r8801 | |||
Erik Zielke
|
r12389 | .. note:: | ||
Simon Heimberg
|
r19997 | |||
Erik Zielke
|
r12389 | using rollback or extensions that destroy/modify history (mq, | ||
rebase, etc.) can cause considerable confusion with shared | ||||
clones. In particular, if two shared clones are both updated to | ||||
the same changeset, and one of them destroys that changeset | ||||
with rollback, the other clone will suddenly stop working: all | ||||
operations will fail with "abort: working directory has unknown | ||||
parent". The only known workaround is to use debugsetparents on | ||||
Matt Mackall
|
r19399 | the broken clone to reset it to a changeset that still exists. | ||
Matt Mackall
|
r8801 | """ | ||
Gregory Szorc
|
r27353 | return hg.share(ui, source, dest=dest, update=not noupdate, | ||
bookmarks=bookmarks) | ||||
Matt Mackall
|
r8801 | |||
Gregory Szorc
|
r21253 | @command('unshare', [], '') | ||
Simon Heimberg
|
r15079 | def unshare(ui, repo): | ||
"""convert a shared repository to a normal one | ||||
Copy the store data to the repo and remove the sharedpath data. | ||||
""" | ||||
Angel Ezquerra
|
r23666 | if not repo.shared(): | ||
Pierre-Yves David
|
r26587 | raise error.Abort(_("this is not a shared repo")) | ||
Simon Heimberg
|
r15079 | |||
destlock = lock = None | ||||
lock = repo.lock() | ||||
try: | ||||
# we use locks here because if we race with commit, we | ||||
# can end up with extra data in the cloned revlogs that's | ||||
# not pointed to by changesets, thus causing verify to | ||||
# fail | ||||
destlock = hg.copystore(ui, repo, repo.path) | ||||
sharefile = repo.join('sharedpath') | ||||
util.rename(sharefile, sharefile + '.old') | ||||
repo.requirements.discard('sharedpath') | ||||
repo._writerequirements() | ||||
finally: | ||||
destlock and destlock.release() | ||||
lock and lock.release() | ||||
Siddharth Agarwal
|
r25666 | # update store, spath, svfs and sjoin of repo | ||
Brodie Rao
|
r20056 | repo.unfiltered().__init__(repo.baseui, repo.root) | ||
Ryan McElroy
|
r23548 | |||
Gregory Szorc
|
r25761 | # Wrap clone command to pass auto share options. | ||
def clone(orig, ui, source, *args, **opts): | ||||
pool = ui.config('share', 'pool', None) | ||||
if pool: | ||||
pool = util.expandpath(pool) | ||||
opts['shareopts'] = dict( | ||||
pool=pool, | ||||
mode=ui.config('share', 'poolnaming', 'identity'), | ||||
) | ||||
return orig(ui, source, *args, **opts) | ||||
Ryan McElroy
|
r23548 | def extsetup(ui): | ||
Augie Fackler
|
r27186 | extensions.wrapfunction(bookmarks, '_getbkfile', getbkfile) | ||
Ryan McElroy
|
r23548 | extensions.wrapfunction(bookmarks.bmstore, 'recordchange', recordchange) | ||
FUJIWARA Katsunori
|
r26933 | extensions.wrapfunction(bookmarks.bmstore, '_writerepo', writerepo) | ||
Gregory Szorc
|
r25761 | extensions.wrapcommand(commands.table, 'clone', clone) | ||
Ryan McElroy
|
r23548 | |||
def _hassharedbookmarks(repo): | ||||
"""Returns whether this repo has shared bookmarks""" | ||||
try: | ||||
Angel Ezquerra
|
r23883 | shared = repo.vfs.read('shared').splitlines() | ||
Gregory Szorc
|
r25660 | except IOError as inst: | ||
Ryan McElroy
|
r23548 | if inst.errno != errno.ENOENT: | ||
raise | ||||
return False | ||||
Martijn Pieters
|
r29424 | return hg.sharedbookmarks in shared | ||
Ryan McElroy
|
r23548 | |||
def _getsrcrepo(repo): | ||||
""" | ||||
Returns the source repository object for a given shared repository. | ||||
If repo is not a shared repository, return None. | ||||
""" | ||||
Matt Harbison
|
r23626 | if repo.sharedpath == repo.path: | ||
return None | ||||
Durham Goode
|
r29506 | if util.safehasattr(repo, 'srcrepo') and repo.srcrepo: | ||
return repo.srcrepo | ||||
Matt Harbison
|
r23626 | # the sharedpath always ends in the .hg; we want the path to the repo | ||
source = repo.vfs.split(repo.sharedpath)[0] | ||||
srcurl, branches = parseurl(source) | ||||
Durham Goode
|
r29506 | srcrepo = repository(repo.ui, srcurl) | ||
repo.srcrepo = srcrepo | ||||
return srcrepo | ||||
Ryan McElroy
|
r23548 | |||
Augie Fackler
|
r27186 | def getbkfile(orig, repo): | ||
Ryan McElroy
|
r23548 | if _hassharedbookmarks(repo): | ||
srcrepo = _getsrcrepo(repo) | ||||
if srcrepo is not None: | ||||
FUJIWARA Katsunori
|
r31052 | # just orig(srcrepo) doesn't work as expected, because | ||
# HG_PENDING refers repo.root. | ||||
try: | ||||
fp, pending = txnutil.trypending(repo.root, repo.vfs, | ||||
'bookmarks') | ||||
if pending: | ||||
# only in this case, bookmark information in repo | ||||
# is up-to-date. | ||||
return fp | ||||
fp.close() | ||||
except IOError as inst: | ||||
if inst.errno != errno.ENOENT: | ||||
raise | ||||
# otherwise, we should read bookmarks from srcrepo, | ||||
# because .hg/bookmarks in srcrepo might be already | ||||
# changed via another sharing repo | ||||
Ryan McElroy
|
r23548 | repo = srcrepo | ||
FUJIWARA Katsunori
|
r31052 | |||
# TODO: Pending changes in repo are still invisible in | ||||
# srcrepo, because bookmarks.pending is written only into repo. | ||||
# See also https://www.mercurial-scm.org/wiki/SharedRepository | ||||
Augie Fackler
|
r27186 | return orig(repo) | ||
Ryan McElroy
|
r23548 | |||
def recordchange(orig, self, tr): | ||||
# Continue with write to local bookmarks file as usual | ||||
orig(self, tr) | ||||
if _hassharedbookmarks(self._repo): | ||||
srcrepo = _getsrcrepo(self._repo) | ||||
if srcrepo is not None: | ||||
category = 'share-bookmarks' | ||||
tr.addpostclose(category, lambda tr: self._writerepo(srcrepo)) | ||||
FUJIWARA Katsunori
|
r26933 | def writerepo(orig, self, repo): | ||
Ryan McElroy
|
r23548 | # First write local bookmarks file in case we ever unshare | ||
FUJIWARA Katsunori
|
r26933 | orig(self, repo) | ||
Ryan McElroy
|
r23548 | if _hassharedbookmarks(self._repo): | ||
srcrepo = _getsrcrepo(self._repo) | ||||
if srcrepo is not None: | ||||
FUJIWARA Katsunori
|
r26933 | orig(self, srcrepo) | ||