##// END OF EJS Templates
configitems: register the 'share.poolnaming' config
Boris Feld -
r34499:308aa280 default
parent child Browse files
Show More
@@ -1,228 +1,231 b''
1 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
1 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
2 #
2 #
3 # This software may be used and distributed according to the terms of the
3 # This software may be used and distributed according to the terms of the
4 # GNU General Public License version 2 or any later version.
4 # GNU General Public License version 2 or any later version.
5
5
6 '''share a common history between several working directories
6 '''share a common history between several working directories
7
7
8 Automatic Pooled Storage for Clones
8 Automatic Pooled Storage for Clones
9 -----------------------------------
9 -----------------------------------
10
10
11 When this extension is active, :hg:`clone` can be configured to
11 When this extension is active, :hg:`clone` can be configured to
12 automatically share/pool storage across multiple clones. This
12 automatically share/pool storage across multiple clones. This
13 mode effectively converts :hg:`clone` to :hg:`clone` + :hg:`share`.
13 mode effectively converts :hg:`clone` to :hg:`clone` + :hg:`share`.
14 The benefit of using this mode is the automatic management of
14 The benefit of using this mode is the automatic management of
15 store paths and intelligent pooling of related repositories.
15 store paths and intelligent pooling of related repositories.
16
16
17 The following ``share.`` config options influence this feature:
17 The following ``share.`` config options influence this feature:
18
18
19 ``share.pool``
19 ``share.pool``
20 Filesystem path where shared repository data will be stored. When
20 Filesystem path where shared repository data will be stored. When
21 defined, :hg:`clone` will automatically use shared repository
21 defined, :hg:`clone` will automatically use shared repository
22 storage instead of creating a store inside each clone.
22 storage instead of creating a store inside each clone.
23
23
24 ``share.poolnaming``
24 ``share.poolnaming``
25 How directory names in ``share.pool`` are constructed.
25 How directory names in ``share.pool`` are constructed.
26
26
27 "identity" means the name is derived from the first changeset in the
27 "identity" means the name is derived from the first changeset in the
28 repository. In this mode, different remotes share storage if their
28 repository. In this mode, different remotes share storage if their
29 root/initial changeset is identical. In this mode, the local shared
29 root/initial changeset is identical. In this mode, the local shared
30 repository is an aggregate of all encountered remote repositories.
30 repository is an aggregate of all encountered remote repositories.
31
31
32 "remote" means the name is derived from the source repository's
32 "remote" means the name is derived from the source repository's
33 path or URL. In this mode, storage is only shared if the path or URL
33 path or URL. In this mode, storage is only shared if the path or URL
34 requested in the :hg:`clone` command matches exactly to a repository
34 requested in the :hg:`clone` command matches exactly to a repository
35 that was cloned before.
35 that was cloned before.
36
36
37 The default naming mode is "identity."
37 The default naming mode is "identity."
38 '''
38 '''
39
39
40 from __future__ import absolute_import
40 from __future__ import absolute_import
41
41
42 import errno
42 import errno
43 from mercurial.i18n import _
43 from mercurial.i18n import _
44 from mercurial import (
44 from mercurial import (
45 bookmarks,
45 bookmarks,
46 commands,
46 commands,
47 error,
47 error,
48 extensions,
48 extensions,
49 hg,
49 hg,
50 registrar,
50 registrar,
51 txnutil,
51 txnutil,
52 util,
52 util,
53 )
53 )
54
54
55 repository = hg.repository
55 repository = hg.repository
56 parseurl = hg.parseurl
56 parseurl = hg.parseurl
57
57
58 cmdtable = {}
58 cmdtable = {}
59 command = registrar.command(cmdtable)
59 command = registrar.command(cmdtable)
60 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
60 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
61 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
61 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
62 # be specifying the version(s) of Mercurial they are tested with, or
62 # be specifying the version(s) of Mercurial they are tested with, or
63 # leave the attribute unspecified.
63 # leave the attribute unspecified.
64 testedwith = 'ships-with-hg-core'
64 testedwith = 'ships-with-hg-core'
65
65
66 configtable = {}
66 configtable = {}
67 configitem = registrar.configitem(configtable)
67 configitem = registrar.configitem(configtable)
68
68
69 configitem('share', 'pool',
69 configitem('share', 'pool',
70 default=None,
70 default=None,
71 )
71 )
72 configitem('share', 'poolnaming',
73 default='identity',
74 )
72
75
73 @command('share',
76 @command('share',
74 [('U', 'noupdate', None, _('do not create a working directory')),
77 [('U', 'noupdate', None, _('do not create a working directory')),
75 ('B', 'bookmarks', None, _('also share bookmarks')),
78 ('B', 'bookmarks', None, _('also share bookmarks')),
76 ('', 'relative', None, _('point to source using a relative path '
79 ('', 'relative', None, _('point to source using a relative path '
77 '(EXPERIMENTAL)')),
80 '(EXPERIMENTAL)')),
78 ],
81 ],
79 _('[-U] [-B] SOURCE [DEST]'),
82 _('[-U] [-B] SOURCE [DEST]'),
80 norepo=True)
83 norepo=True)
81 def share(ui, source, dest=None, noupdate=False, bookmarks=False,
84 def share(ui, source, dest=None, noupdate=False, bookmarks=False,
82 relative=False):
85 relative=False):
83 """create a new shared repository
86 """create a new shared repository
84
87
85 Initialize a new repository and working directory that shares its
88 Initialize a new repository and working directory that shares its
86 history (and optionally bookmarks) with another repository.
89 history (and optionally bookmarks) with another repository.
87
90
88 .. note::
91 .. note::
89
92
90 using rollback or extensions that destroy/modify history (mq,
93 using rollback or extensions that destroy/modify history (mq,
91 rebase, etc.) can cause considerable confusion with shared
94 rebase, etc.) can cause considerable confusion with shared
92 clones. In particular, if two shared clones are both updated to
95 clones. In particular, if two shared clones are both updated to
93 the same changeset, and one of them destroys that changeset
96 the same changeset, and one of them destroys that changeset
94 with rollback, the other clone will suddenly stop working: all
97 with rollback, the other clone will suddenly stop working: all
95 operations will fail with "abort: working directory has unknown
98 operations will fail with "abort: working directory has unknown
96 parent". The only known workaround is to use debugsetparents on
99 parent". The only known workaround is to use debugsetparents on
97 the broken clone to reset it to a changeset that still exists.
100 the broken clone to reset it to a changeset that still exists.
98 """
101 """
99
102
100 return hg.share(ui, source, dest=dest, update=not noupdate,
103 return hg.share(ui, source, dest=dest, update=not noupdate,
101 bookmarks=bookmarks, relative=relative)
104 bookmarks=bookmarks, relative=relative)
102
105
103 @command('unshare', [], '')
106 @command('unshare', [], '')
104 def unshare(ui, repo):
107 def unshare(ui, repo):
105 """convert a shared repository to a normal one
108 """convert a shared repository to a normal one
106
109
107 Copy the store data to the repo and remove the sharedpath data.
110 Copy the store data to the repo and remove the sharedpath data.
108 """
111 """
109
112
110 if not repo.shared():
113 if not repo.shared():
111 raise error.Abort(_("this is not a shared repo"))
114 raise error.Abort(_("this is not a shared repo"))
112
115
113 destlock = lock = None
116 destlock = lock = None
114 lock = repo.lock()
117 lock = repo.lock()
115 try:
118 try:
116 # we use locks here because if we race with commit, we
119 # we use locks here because if we race with commit, we
117 # can end up with extra data in the cloned revlogs that's
120 # can end up with extra data in the cloned revlogs that's
118 # not pointed to by changesets, thus causing verify to
121 # not pointed to by changesets, thus causing verify to
119 # fail
122 # fail
120
123
121 destlock = hg.copystore(ui, repo, repo.path)
124 destlock = hg.copystore(ui, repo, repo.path)
122
125
123 sharefile = repo.vfs.join('sharedpath')
126 sharefile = repo.vfs.join('sharedpath')
124 util.rename(sharefile, sharefile + '.old')
127 util.rename(sharefile, sharefile + '.old')
125
128
126 repo.requirements.discard('shared')
129 repo.requirements.discard('shared')
127 repo.requirements.discard('relshared')
130 repo.requirements.discard('relshared')
128 repo._writerequirements()
131 repo._writerequirements()
129 finally:
132 finally:
130 destlock and destlock.release()
133 destlock and destlock.release()
131 lock and lock.release()
134 lock and lock.release()
132
135
133 # update store, spath, svfs and sjoin of repo
136 # update store, spath, svfs and sjoin of repo
134 repo.unfiltered().__init__(repo.baseui, repo.root)
137 repo.unfiltered().__init__(repo.baseui, repo.root)
135
138
136 # Wrap clone command to pass auto share options.
139 # Wrap clone command to pass auto share options.
137 def clone(orig, ui, source, *args, **opts):
140 def clone(orig, ui, source, *args, **opts):
138 pool = ui.config('share', 'pool')
141 pool = ui.config('share', 'pool')
139 if pool:
142 if pool:
140 pool = util.expandpath(pool)
143 pool = util.expandpath(pool)
141
144
142 opts[r'shareopts'] = {
145 opts[r'shareopts'] = {
143 'pool': pool,
146 'pool': pool,
144 'mode': ui.config('share', 'poolnaming', 'identity'),
147 'mode': ui.config('share', 'poolnaming'),
145 }
148 }
146
149
147 return orig(ui, source, *args, **opts)
150 return orig(ui, source, *args, **opts)
148
151
149 def extsetup(ui):
152 def extsetup(ui):
150 extensions.wrapfunction(bookmarks, '_getbkfile', getbkfile)
153 extensions.wrapfunction(bookmarks, '_getbkfile', getbkfile)
151 extensions.wrapfunction(bookmarks.bmstore, '_recordchange', recordchange)
154 extensions.wrapfunction(bookmarks.bmstore, '_recordchange', recordchange)
152 extensions.wrapfunction(bookmarks.bmstore, '_writerepo', writerepo)
155 extensions.wrapfunction(bookmarks.bmstore, '_writerepo', writerepo)
153 extensions.wrapcommand(commands.table, 'clone', clone)
156 extensions.wrapcommand(commands.table, 'clone', clone)
154
157
155 def _hassharedbookmarks(repo):
158 def _hassharedbookmarks(repo):
156 """Returns whether this repo has shared bookmarks"""
159 """Returns whether this repo has shared bookmarks"""
157 try:
160 try:
158 shared = repo.vfs.read('shared').splitlines()
161 shared = repo.vfs.read('shared').splitlines()
159 except IOError as inst:
162 except IOError as inst:
160 if inst.errno != errno.ENOENT:
163 if inst.errno != errno.ENOENT:
161 raise
164 raise
162 return False
165 return False
163 return hg.sharedbookmarks in shared
166 return hg.sharedbookmarks in shared
164
167
165 def _getsrcrepo(repo):
168 def _getsrcrepo(repo):
166 """
169 """
167 Returns the source repository object for a given shared repository.
170 Returns the source repository object for a given shared repository.
168 If repo is not a shared repository, return None.
171 If repo is not a shared repository, return None.
169 """
172 """
170 if repo.sharedpath == repo.path:
173 if repo.sharedpath == repo.path:
171 return None
174 return None
172
175
173 if util.safehasattr(repo, 'srcrepo') and repo.srcrepo:
176 if util.safehasattr(repo, 'srcrepo') and repo.srcrepo:
174 return repo.srcrepo
177 return repo.srcrepo
175
178
176 # the sharedpath always ends in the .hg; we want the path to the repo
179 # the sharedpath always ends in the .hg; we want the path to the repo
177 source = repo.vfs.split(repo.sharedpath)[0]
180 source = repo.vfs.split(repo.sharedpath)[0]
178 srcurl, branches = parseurl(source)
181 srcurl, branches = parseurl(source)
179 srcrepo = repository(repo.ui, srcurl)
182 srcrepo = repository(repo.ui, srcurl)
180 repo.srcrepo = srcrepo
183 repo.srcrepo = srcrepo
181 return srcrepo
184 return srcrepo
182
185
183 def getbkfile(orig, repo):
186 def getbkfile(orig, repo):
184 if _hassharedbookmarks(repo):
187 if _hassharedbookmarks(repo):
185 srcrepo = _getsrcrepo(repo)
188 srcrepo = _getsrcrepo(repo)
186 if srcrepo is not None:
189 if srcrepo is not None:
187 # just orig(srcrepo) doesn't work as expected, because
190 # just orig(srcrepo) doesn't work as expected, because
188 # HG_PENDING refers repo.root.
191 # HG_PENDING refers repo.root.
189 try:
192 try:
190 fp, pending = txnutil.trypending(repo.root, repo.vfs,
193 fp, pending = txnutil.trypending(repo.root, repo.vfs,
191 'bookmarks')
194 'bookmarks')
192 if pending:
195 if pending:
193 # only in this case, bookmark information in repo
196 # only in this case, bookmark information in repo
194 # is up-to-date.
197 # is up-to-date.
195 return fp
198 return fp
196 fp.close()
199 fp.close()
197 except IOError as inst:
200 except IOError as inst:
198 if inst.errno != errno.ENOENT:
201 if inst.errno != errno.ENOENT:
199 raise
202 raise
200
203
201 # otherwise, we should read bookmarks from srcrepo,
204 # otherwise, we should read bookmarks from srcrepo,
202 # because .hg/bookmarks in srcrepo might be already
205 # because .hg/bookmarks in srcrepo might be already
203 # changed via another sharing repo
206 # changed via another sharing repo
204 repo = srcrepo
207 repo = srcrepo
205
208
206 # TODO: Pending changes in repo are still invisible in
209 # TODO: Pending changes in repo are still invisible in
207 # srcrepo, because bookmarks.pending is written only into repo.
210 # srcrepo, because bookmarks.pending is written only into repo.
208 # See also https://www.mercurial-scm.org/wiki/SharedRepository
211 # See also https://www.mercurial-scm.org/wiki/SharedRepository
209 return orig(repo)
212 return orig(repo)
210
213
211 def recordchange(orig, self, tr):
214 def recordchange(orig, self, tr):
212 # Continue with write to local bookmarks file as usual
215 # Continue with write to local bookmarks file as usual
213 orig(self, tr)
216 orig(self, tr)
214
217
215 if _hassharedbookmarks(self._repo):
218 if _hassharedbookmarks(self._repo):
216 srcrepo = _getsrcrepo(self._repo)
219 srcrepo = _getsrcrepo(self._repo)
217 if srcrepo is not None:
220 if srcrepo is not None:
218 category = 'share-bookmarks'
221 category = 'share-bookmarks'
219 tr.addpostclose(category, lambda tr: self._writerepo(srcrepo))
222 tr.addpostclose(category, lambda tr: self._writerepo(srcrepo))
220
223
221 def writerepo(orig, self, repo):
224 def writerepo(orig, self, repo):
222 # First write local bookmarks file in case we ever unshare
225 # First write local bookmarks file in case we ever unshare
223 orig(self, repo)
226 orig(self, repo)
224
227
225 if _hassharedbookmarks(self._repo):
228 if _hassharedbookmarks(self._repo):
226 srcrepo = _getsrcrepo(self._repo)
229 srcrepo = _getsrcrepo(self._repo)
227 if srcrepo is not None:
230 if srcrepo is not None:
228 orig(self, srcrepo)
231 orig(self, srcrepo)
General Comments 0
You need to be logged in to leave comments. Login now