##// END OF EJS Templates
share: don't recreate the source repo each time...
Durham Goode -
r29506:2550604f default
parent child Browse files
Show More
@@ -1,189 +1,194 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 cmdutil,
46 cmdutil,
47 commands,
47 commands,
48 error,
48 error,
49 extensions,
49 extensions,
50 hg,
50 hg,
51 util,
51 util,
52 )
52 )
53
53
54 repository = hg.repository
54 repository = hg.repository
55 parseurl = hg.parseurl
55 parseurl = hg.parseurl
56
56
57 cmdtable = {}
57 cmdtable = {}
58 command = cmdutil.command(cmdtable)
58 command = cmdutil.command(cmdtable)
59 # Note for extension authors: ONLY specify testedwith = 'internal' for
59 # Note for extension authors: ONLY specify testedwith = 'internal' for
60 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
60 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
61 # be specifying the version(s) of Mercurial they are tested with, or
61 # be specifying the version(s) of Mercurial they are tested with, or
62 # leave the attribute unspecified.
62 # leave the attribute unspecified.
63 testedwith = 'internal'
63 testedwith = 'internal'
64
64
65 @command('share',
65 @command('share',
66 [('U', 'noupdate', None, _('do not create a working directory')),
66 [('U', 'noupdate', None, _('do not create a working directory')),
67 ('B', 'bookmarks', None, _('also share bookmarks'))],
67 ('B', 'bookmarks', None, _('also share bookmarks'))],
68 _('[-U] [-B] SOURCE [DEST]'),
68 _('[-U] [-B] SOURCE [DEST]'),
69 norepo=True)
69 norepo=True)
70 def share(ui, source, dest=None, noupdate=False, bookmarks=False):
70 def share(ui, source, dest=None, noupdate=False, bookmarks=False):
71 """create a new shared repository
71 """create a new shared repository
72
72
73 Initialize a new repository and working directory that shares its
73 Initialize a new repository and working directory that shares its
74 history (and optionally bookmarks) with another repository.
74 history (and optionally bookmarks) with another repository.
75
75
76 .. note::
76 .. note::
77
77
78 using rollback or extensions that destroy/modify history (mq,
78 using rollback or extensions that destroy/modify history (mq,
79 rebase, etc.) can cause considerable confusion with shared
79 rebase, etc.) can cause considerable confusion with shared
80 clones. In particular, if two shared clones are both updated to
80 clones. In particular, if two shared clones are both updated to
81 the same changeset, and one of them destroys that changeset
81 the same changeset, and one of them destroys that changeset
82 with rollback, the other clone will suddenly stop working: all
82 with rollback, the other clone will suddenly stop working: all
83 operations will fail with "abort: working directory has unknown
83 operations will fail with "abort: working directory has unknown
84 parent". The only known workaround is to use debugsetparents on
84 parent". The only known workaround is to use debugsetparents on
85 the broken clone to reset it to a changeset that still exists.
85 the broken clone to reset it to a changeset that still exists.
86 """
86 """
87
87
88 return hg.share(ui, source, dest=dest, update=not noupdate,
88 return hg.share(ui, source, dest=dest, update=not noupdate,
89 bookmarks=bookmarks)
89 bookmarks=bookmarks)
90
90
91 @command('unshare', [], '')
91 @command('unshare', [], '')
92 def unshare(ui, repo):
92 def unshare(ui, repo):
93 """convert a shared repository to a normal one
93 """convert a shared repository to a normal one
94
94
95 Copy the store data to the repo and remove the sharedpath data.
95 Copy the store data to the repo and remove the sharedpath data.
96 """
96 """
97
97
98 if not repo.shared():
98 if not repo.shared():
99 raise error.Abort(_("this is not a shared repo"))
99 raise error.Abort(_("this is not a shared repo"))
100
100
101 destlock = lock = None
101 destlock = lock = None
102 lock = repo.lock()
102 lock = repo.lock()
103 try:
103 try:
104 # we use locks here because if we race with commit, we
104 # we use locks here because if we race with commit, we
105 # can end up with extra data in the cloned revlogs that's
105 # can end up with extra data in the cloned revlogs that's
106 # not pointed to by changesets, thus causing verify to
106 # not pointed to by changesets, thus causing verify to
107 # fail
107 # fail
108
108
109 destlock = hg.copystore(ui, repo, repo.path)
109 destlock = hg.copystore(ui, repo, repo.path)
110
110
111 sharefile = repo.join('sharedpath')
111 sharefile = repo.join('sharedpath')
112 util.rename(sharefile, sharefile + '.old')
112 util.rename(sharefile, sharefile + '.old')
113
113
114 repo.requirements.discard('sharedpath')
114 repo.requirements.discard('sharedpath')
115 repo._writerequirements()
115 repo._writerequirements()
116 finally:
116 finally:
117 destlock and destlock.release()
117 destlock and destlock.release()
118 lock and lock.release()
118 lock and lock.release()
119
119
120 # update store, spath, svfs and sjoin of repo
120 # update store, spath, svfs and sjoin of repo
121 repo.unfiltered().__init__(repo.baseui, repo.root)
121 repo.unfiltered().__init__(repo.baseui, repo.root)
122
122
123 # Wrap clone command to pass auto share options.
123 # Wrap clone command to pass auto share options.
124 def clone(orig, ui, source, *args, **opts):
124 def clone(orig, ui, source, *args, **opts):
125 pool = ui.config('share', 'pool', None)
125 pool = ui.config('share', 'pool', None)
126 if pool:
126 if pool:
127 pool = util.expandpath(pool)
127 pool = util.expandpath(pool)
128
128
129 opts['shareopts'] = dict(
129 opts['shareopts'] = dict(
130 pool=pool,
130 pool=pool,
131 mode=ui.config('share', 'poolnaming', 'identity'),
131 mode=ui.config('share', 'poolnaming', 'identity'),
132 )
132 )
133
133
134 return orig(ui, source, *args, **opts)
134 return orig(ui, source, *args, **opts)
135
135
136 def extsetup(ui):
136 def extsetup(ui):
137 extensions.wrapfunction(bookmarks, '_getbkfile', getbkfile)
137 extensions.wrapfunction(bookmarks, '_getbkfile', getbkfile)
138 extensions.wrapfunction(bookmarks.bmstore, 'recordchange', recordchange)
138 extensions.wrapfunction(bookmarks.bmstore, 'recordchange', recordchange)
139 extensions.wrapfunction(bookmarks.bmstore, '_writerepo', writerepo)
139 extensions.wrapfunction(bookmarks.bmstore, '_writerepo', writerepo)
140 extensions.wrapcommand(commands.table, 'clone', clone)
140 extensions.wrapcommand(commands.table, 'clone', clone)
141
141
142 def _hassharedbookmarks(repo):
142 def _hassharedbookmarks(repo):
143 """Returns whether this repo has shared bookmarks"""
143 """Returns whether this repo has shared bookmarks"""
144 try:
144 try:
145 shared = repo.vfs.read('shared').splitlines()
145 shared = repo.vfs.read('shared').splitlines()
146 except IOError as inst:
146 except IOError as inst:
147 if inst.errno != errno.ENOENT:
147 if inst.errno != errno.ENOENT:
148 raise
148 raise
149 return False
149 return False
150 return hg.sharedbookmarks in shared
150 return hg.sharedbookmarks in shared
151
151
152 def _getsrcrepo(repo):
152 def _getsrcrepo(repo):
153 """
153 """
154 Returns the source repository object for a given shared repository.
154 Returns the source repository object for a given shared repository.
155 If repo is not a shared repository, return None.
155 If repo is not a shared repository, return None.
156 """
156 """
157 if repo.sharedpath == repo.path:
157 if repo.sharedpath == repo.path:
158 return None
158 return None
159
159
160 if util.safehasattr(repo, 'srcrepo') and repo.srcrepo:
161 return repo.srcrepo
162
160 # the sharedpath always ends in the .hg; we want the path to the repo
163 # the sharedpath always ends in the .hg; we want the path to the repo
161 source = repo.vfs.split(repo.sharedpath)[0]
164 source = repo.vfs.split(repo.sharedpath)[0]
162 srcurl, branches = parseurl(source)
165 srcurl, branches = parseurl(source)
163 return repository(repo.ui, srcurl)
166 srcrepo = repository(repo.ui, srcurl)
167 repo.srcrepo = srcrepo
168 return srcrepo
164
169
165 def getbkfile(orig, repo):
170 def getbkfile(orig, repo):
166 if _hassharedbookmarks(repo):
171 if _hassharedbookmarks(repo):
167 srcrepo = _getsrcrepo(repo)
172 srcrepo = _getsrcrepo(repo)
168 if srcrepo is not None:
173 if srcrepo is not None:
169 repo = srcrepo
174 repo = srcrepo
170 return orig(repo)
175 return orig(repo)
171
176
172 def recordchange(orig, self, tr):
177 def recordchange(orig, self, tr):
173 # Continue with write to local bookmarks file as usual
178 # Continue with write to local bookmarks file as usual
174 orig(self, tr)
179 orig(self, tr)
175
180
176 if _hassharedbookmarks(self._repo):
181 if _hassharedbookmarks(self._repo):
177 srcrepo = _getsrcrepo(self._repo)
182 srcrepo = _getsrcrepo(self._repo)
178 if srcrepo is not None:
183 if srcrepo is not None:
179 category = 'share-bookmarks'
184 category = 'share-bookmarks'
180 tr.addpostclose(category, lambda tr: self._writerepo(srcrepo))
185 tr.addpostclose(category, lambda tr: self._writerepo(srcrepo))
181
186
182 def writerepo(orig, self, repo):
187 def writerepo(orig, self, repo):
183 # First write local bookmarks file in case we ever unshare
188 # First write local bookmarks file in case we ever unshare
184 orig(self, repo)
189 orig(self, repo)
185
190
186 if _hassharedbookmarks(self._repo):
191 if _hassharedbookmarks(self._repo):
187 srcrepo = _getsrcrepo(self._repo)
192 srcrepo = _getsrcrepo(self._repo)
188 if srcrepo is not None:
193 if srcrepo is not None:
189 orig(self, srcrepo)
194 orig(self, srcrepo)
General Comments 0
You need to be logged in to leave comments. Login now