##// END OF EJS Templates
share: directly use repo.vfs.join...
Pierre-Yves David -
r31334:553680d1 default
parent child Browse files
Show More
@@ -1,221 +1,221
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 cmdutil,
46 cmdutil,
47 commands,
47 commands,
48 error,
48 error,
49 extensions,
49 extensions,
50 hg,
50 hg,
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 = cmdutil.command(cmdtable)
59 command = cmdutil.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 @command('share',
66 @command('share',
67 [('U', 'noupdate', None, _('do not create a working directory')),
67 [('U', 'noupdate', None, _('do not create a working directory')),
68 ('B', 'bookmarks', None, _('also share bookmarks')),
68 ('B', 'bookmarks', None, _('also share bookmarks')),
69 ('', 'relative', None, _('point to source using a relative path '
69 ('', 'relative', None, _('point to source using a relative path '
70 '(EXPERIMENTAL)')),
70 '(EXPERIMENTAL)')),
71 ],
71 ],
72 _('[-U] [-B] SOURCE [DEST]'),
72 _('[-U] [-B] SOURCE [DEST]'),
73 norepo=True)
73 norepo=True)
74 def share(ui, source, dest=None, noupdate=False, bookmarks=False,
74 def share(ui, source, dest=None, noupdate=False, bookmarks=False,
75 relative=False):
75 relative=False):
76 """create a new shared repository
76 """create a new shared repository
77
77
78 Initialize a new repository and working directory that shares its
78 Initialize a new repository and working directory that shares its
79 history (and optionally bookmarks) with another repository.
79 history (and optionally bookmarks) with another repository.
80
80
81 .. note::
81 .. note::
82
82
83 using rollback or extensions that destroy/modify history (mq,
83 using rollback or extensions that destroy/modify history (mq,
84 rebase, etc.) can cause considerable confusion with shared
84 rebase, etc.) can cause considerable confusion with shared
85 clones. In particular, if two shared clones are both updated to
85 clones. In particular, if two shared clones are both updated to
86 the same changeset, and one of them destroys that changeset
86 the same changeset, and one of them destroys that changeset
87 with rollback, the other clone will suddenly stop working: all
87 with rollback, the other clone will suddenly stop working: all
88 operations will fail with "abort: working directory has unknown
88 operations will fail with "abort: working directory has unknown
89 parent". The only known workaround is to use debugsetparents on
89 parent". The only known workaround is to use debugsetparents on
90 the broken clone to reset it to a changeset that still exists.
90 the broken clone to reset it to a changeset that still exists.
91 """
91 """
92
92
93 return hg.share(ui, source, dest=dest, update=not noupdate,
93 return hg.share(ui, source, dest=dest, update=not noupdate,
94 bookmarks=bookmarks, relative=relative)
94 bookmarks=bookmarks, relative=relative)
95
95
96 @command('unshare', [], '')
96 @command('unshare', [], '')
97 def unshare(ui, repo):
97 def unshare(ui, repo):
98 """convert a shared repository to a normal one
98 """convert a shared repository to a normal one
99
99
100 Copy the store data to the repo and remove the sharedpath data.
100 Copy the store data to the repo and remove the sharedpath data.
101 """
101 """
102
102
103 if not repo.shared():
103 if not repo.shared():
104 raise error.Abort(_("this is not a shared repo"))
104 raise error.Abort(_("this is not a shared repo"))
105
105
106 destlock = lock = None
106 destlock = lock = None
107 lock = repo.lock()
107 lock = repo.lock()
108 try:
108 try:
109 # we use locks here because if we race with commit, we
109 # we use locks here because if we race with commit, we
110 # can end up with extra data in the cloned revlogs that's
110 # can end up with extra data in the cloned revlogs that's
111 # not pointed to by changesets, thus causing verify to
111 # not pointed to by changesets, thus causing verify to
112 # fail
112 # fail
113
113
114 destlock = hg.copystore(ui, repo, repo.path)
114 destlock = hg.copystore(ui, repo, repo.path)
115
115
116 sharefile = repo.join('sharedpath')
116 sharefile = repo.vfs.join('sharedpath')
117 util.rename(sharefile, sharefile + '.old')
117 util.rename(sharefile, sharefile + '.old')
118
118
119 repo.requirements.discard('shared')
119 repo.requirements.discard('shared')
120 repo.requirements.discard('relshared')
120 repo.requirements.discard('relshared')
121 repo._writerequirements()
121 repo._writerequirements()
122 finally:
122 finally:
123 destlock and destlock.release()
123 destlock and destlock.release()
124 lock and lock.release()
124 lock and lock.release()
125
125
126 # update store, spath, svfs and sjoin of repo
126 # update store, spath, svfs and sjoin of repo
127 repo.unfiltered().__init__(repo.baseui, repo.root)
127 repo.unfiltered().__init__(repo.baseui, repo.root)
128
128
129 # Wrap clone command to pass auto share options.
129 # Wrap clone command to pass auto share options.
130 def clone(orig, ui, source, *args, **opts):
130 def clone(orig, ui, source, *args, **opts):
131 pool = ui.config('share', 'pool', None)
131 pool = ui.config('share', 'pool', None)
132 if pool:
132 if pool:
133 pool = util.expandpath(pool)
133 pool = util.expandpath(pool)
134
134
135 opts['shareopts'] = dict(
135 opts['shareopts'] = dict(
136 pool=pool,
136 pool=pool,
137 mode=ui.config('share', 'poolnaming', 'identity'),
137 mode=ui.config('share', 'poolnaming', 'identity'),
138 )
138 )
139
139
140 return orig(ui, source, *args, **opts)
140 return orig(ui, source, *args, **opts)
141
141
142 def extsetup(ui):
142 def extsetup(ui):
143 extensions.wrapfunction(bookmarks, '_getbkfile', getbkfile)
143 extensions.wrapfunction(bookmarks, '_getbkfile', getbkfile)
144 extensions.wrapfunction(bookmarks.bmstore, 'recordchange', recordchange)
144 extensions.wrapfunction(bookmarks.bmstore, 'recordchange', recordchange)
145 extensions.wrapfunction(bookmarks.bmstore, '_writerepo', writerepo)
145 extensions.wrapfunction(bookmarks.bmstore, '_writerepo', writerepo)
146 extensions.wrapcommand(commands.table, 'clone', clone)
146 extensions.wrapcommand(commands.table, 'clone', clone)
147
147
148 def _hassharedbookmarks(repo):
148 def _hassharedbookmarks(repo):
149 """Returns whether this repo has shared bookmarks"""
149 """Returns whether this repo has shared bookmarks"""
150 try:
150 try:
151 shared = repo.vfs.read('shared').splitlines()
151 shared = repo.vfs.read('shared').splitlines()
152 except IOError as inst:
152 except IOError as inst:
153 if inst.errno != errno.ENOENT:
153 if inst.errno != errno.ENOENT:
154 raise
154 raise
155 return False
155 return False
156 return hg.sharedbookmarks in shared
156 return hg.sharedbookmarks in shared
157
157
158 def _getsrcrepo(repo):
158 def _getsrcrepo(repo):
159 """
159 """
160 Returns the source repository object for a given shared repository.
160 Returns the source repository object for a given shared repository.
161 If repo is not a shared repository, return None.
161 If repo is not a shared repository, return None.
162 """
162 """
163 if repo.sharedpath == repo.path:
163 if repo.sharedpath == repo.path:
164 return None
164 return None
165
165
166 if util.safehasattr(repo, 'srcrepo') and repo.srcrepo:
166 if util.safehasattr(repo, 'srcrepo') and repo.srcrepo:
167 return repo.srcrepo
167 return repo.srcrepo
168
168
169 # the sharedpath always ends in the .hg; we want the path to the repo
169 # the sharedpath always ends in the .hg; we want the path to the repo
170 source = repo.vfs.split(repo.sharedpath)[0]
170 source = repo.vfs.split(repo.sharedpath)[0]
171 srcurl, branches = parseurl(source)
171 srcurl, branches = parseurl(source)
172 srcrepo = repository(repo.ui, srcurl)
172 srcrepo = repository(repo.ui, srcurl)
173 repo.srcrepo = srcrepo
173 repo.srcrepo = srcrepo
174 return srcrepo
174 return srcrepo
175
175
176 def getbkfile(orig, repo):
176 def getbkfile(orig, repo):
177 if _hassharedbookmarks(repo):
177 if _hassharedbookmarks(repo):
178 srcrepo = _getsrcrepo(repo)
178 srcrepo = _getsrcrepo(repo)
179 if srcrepo is not None:
179 if srcrepo is not None:
180 # just orig(srcrepo) doesn't work as expected, because
180 # just orig(srcrepo) doesn't work as expected, because
181 # HG_PENDING refers repo.root.
181 # HG_PENDING refers repo.root.
182 try:
182 try:
183 fp, pending = txnutil.trypending(repo.root, repo.vfs,
183 fp, pending = txnutil.trypending(repo.root, repo.vfs,
184 'bookmarks')
184 'bookmarks')
185 if pending:
185 if pending:
186 # only in this case, bookmark information in repo
186 # only in this case, bookmark information in repo
187 # is up-to-date.
187 # is up-to-date.
188 return fp
188 return fp
189 fp.close()
189 fp.close()
190 except IOError as inst:
190 except IOError as inst:
191 if inst.errno != errno.ENOENT:
191 if inst.errno != errno.ENOENT:
192 raise
192 raise
193
193
194 # otherwise, we should read bookmarks from srcrepo,
194 # otherwise, we should read bookmarks from srcrepo,
195 # because .hg/bookmarks in srcrepo might be already
195 # because .hg/bookmarks in srcrepo might be already
196 # changed via another sharing repo
196 # changed via another sharing repo
197 repo = srcrepo
197 repo = srcrepo
198
198
199 # TODO: Pending changes in repo are still invisible in
199 # TODO: Pending changes in repo are still invisible in
200 # srcrepo, because bookmarks.pending is written only into repo.
200 # srcrepo, because bookmarks.pending is written only into repo.
201 # See also https://www.mercurial-scm.org/wiki/SharedRepository
201 # See also https://www.mercurial-scm.org/wiki/SharedRepository
202 return orig(repo)
202 return orig(repo)
203
203
204 def recordchange(orig, self, tr):
204 def recordchange(orig, self, tr):
205 # Continue with write to local bookmarks file as usual
205 # Continue with write to local bookmarks file as usual
206 orig(self, tr)
206 orig(self, tr)
207
207
208 if _hassharedbookmarks(self._repo):
208 if _hassharedbookmarks(self._repo):
209 srcrepo = _getsrcrepo(self._repo)
209 srcrepo = _getsrcrepo(self._repo)
210 if srcrepo is not None:
210 if srcrepo is not None:
211 category = 'share-bookmarks'
211 category = 'share-bookmarks'
212 tr.addpostclose(category, lambda tr: self._writerepo(srcrepo))
212 tr.addpostclose(category, lambda tr: self._writerepo(srcrepo))
213
213
214 def writerepo(orig, self, repo):
214 def writerepo(orig, self, repo):
215 # First write local bookmarks file in case we ever unshare
215 # First write local bookmarks file in case we ever unshare
216 orig(self, repo)
216 orig(self, repo)
217
217
218 if _hassharedbookmarks(self._repo):
218 if _hassharedbookmarks(self._repo):
219 srcrepo = _getsrcrepo(self._repo)
219 srcrepo = _getsrcrepo(self._repo)
220 if srcrepo is not None:
220 if srcrepo is not None:
221 orig(self, srcrepo)
221 orig(self, srcrepo)
General Comments 0
You need to be logged in to leave comments. Login now