##// END OF EJS Templates
clone: fall back to pull source repo cannot be locked, 937ee88da3ef was a noop...
Benoit Boissinot -
r8649:2c097e22 default
parent child Browse files
Show More
@@ -1,299 +1,299
1 # hg.py - repository classes for mercurial
1 # hg.py - repository classes for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2, incorporated herein by reference.
7 # GNU General Public License version 2, incorporated herein by reference.
8
8
9 from i18n import _
9 from i18n import _
10 from lock import release
10 from lock import release
11 import localrepo, bundlerepo, httprepo, sshrepo, statichttprepo
11 import localrepo, bundlerepo, httprepo, sshrepo, statichttprepo
12 import lock, util, extensions, error
12 import lock, util, extensions, error
13 import merge as _merge
13 import merge as _merge
14 import verify as _verify
14 import verify as _verify
15 import errno, os, shutil
15 import errno, os, shutil
16
16
17 def _local(path):
17 def _local(path):
18 return (os.path.isfile(util.drop_scheme('file', path)) and
18 return (os.path.isfile(util.drop_scheme('file', path)) and
19 bundlerepo or localrepo)
19 bundlerepo or localrepo)
20
20
21 def parseurl(url, revs=[]):
21 def parseurl(url, revs=[]):
22 '''parse url#branch, returning url, branch + revs'''
22 '''parse url#branch, returning url, branch + revs'''
23
23
24 if '#' not in url:
24 if '#' not in url:
25 return url, (revs or None), revs and revs[-1] or None
25 return url, (revs or None), revs and revs[-1] or None
26
26
27 url, branch = url.split('#', 1)
27 url, branch = url.split('#', 1)
28 checkout = revs and revs[-1] or branch
28 checkout = revs and revs[-1] or branch
29 return url, (revs or []) + [branch], checkout
29 return url, (revs or []) + [branch], checkout
30
30
31 schemes = {
31 schemes = {
32 'bundle': bundlerepo,
32 'bundle': bundlerepo,
33 'file': _local,
33 'file': _local,
34 'http': httprepo,
34 'http': httprepo,
35 'https': httprepo,
35 'https': httprepo,
36 'ssh': sshrepo,
36 'ssh': sshrepo,
37 'static-http': statichttprepo,
37 'static-http': statichttprepo,
38 }
38 }
39
39
40 def _lookup(path):
40 def _lookup(path):
41 scheme = 'file'
41 scheme = 'file'
42 if path:
42 if path:
43 c = path.find(':')
43 c = path.find(':')
44 if c > 0:
44 if c > 0:
45 scheme = path[:c]
45 scheme = path[:c]
46 thing = schemes.get(scheme) or schemes['file']
46 thing = schemes.get(scheme) or schemes['file']
47 try:
47 try:
48 return thing(path)
48 return thing(path)
49 except TypeError:
49 except TypeError:
50 return thing
50 return thing
51
51
52 def islocal(repo):
52 def islocal(repo):
53 '''return true if repo or path is local'''
53 '''return true if repo or path is local'''
54 if isinstance(repo, str):
54 if isinstance(repo, str):
55 try:
55 try:
56 return _lookup(repo).islocal(repo)
56 return _lookup(repo).islocal(repo)
57 except AttributeError:
57 except AttributeError:
58 return False
58 return False
59 return repo.local()
59 return repo.local()
60
60
61 def repository(ui, path='', create=False):
61 def repository(ui, path='', create=False):
62 """return a repository object for the specified path"""
62 """return a repository object for the specified path"""
63 repo = _lookup(path).instance(ui, path, create)
63 repo = _lookup(path).instance(ui, path, create)
64 ui = getattr(repo, "ui", ui)
64 ui = getattr(repo, "ui", ui)
65 for name, module in extensions.extensions():
65 for name, module in extensions.extensions():
66 hook = getattr(module, 'reposetup', None)
66 hook = getattr(module, 'reposetup', None)
67 if hook:
67 if hook:
68 hook(ui, repo)
68 hook(ui, repo)
69 return repo
69 return repo
70
70
71 def defaultdest(source):
71 def defaultdest(source):
72 '''return default destination of clone if none is given'''
72 '''return default destination of clone if none is given'''
73 return os.path.basename(os.path.normpath(source))
73 return os.path.basename(os.path.normpath(source))
74
74
75 def localpath(path):
75 def localpath(path):
76 if path.startswith('file://localhost/'):
76 if path.startswith('file://localhost/'):
77 return path[16:]
77 return path[16:]
78 if path.startswith('file://'):
78 if path.startswith('file://'):
79 return path[7:]
79 return path[7:]
80 if path.startswith('file:'):
80 if path.startswith('file:'):
81 return path[5:]
81 return path[5:]
82 return path
82 return path
83
83
84 def clone(ui, source, dest=None, pull=False, rev=None, update=True,
84 def clone(ui, source, dest=None, pull=False, rev=None, update=True,
85 stream=False):
85 stream=False):
86 """Make a copy of an existing repository.
86 """Make a copy of an existing repository.
87
87
88 Create a copy of an existing repository in a new directory. The
88 Create a copy of an existing repository in a new directory. The
89 source and destination are URLs, as passed to the repository
89 source and destination are URLs, as passed to the repository
90 function. Returns a pair of repository objects, the source and
90 function. Returns a pair of repository objects, the source and
91 newly created destination.
91 newly created destination.
92
92
93 The location of the source is added to the new repository's
93 The location of the source is added to the new repository's
94 .hg/hgrc file, as the default to be used for future pulls and
94 .hg/hgrc file, as the default to be used for future pulls and
95 pushes.
95 pushes.
96
96
97 If an exception is raised, the partly cloned/updated destination
97 If an exception is raised, the partly cloned/updated destination
98 repository will be deleted.
98 repository will be deleted.
99
99
100 Arguments:
100 Arguments:
101
101
102 source: repository object or URL
102 source: repository object or URL
103
103
104 dest: URL of destination repository to create (defaults to base
104 dest: URL of destination repository to create (defaults to base
105 name of source repository)
105 name of source repository)
106
106
107 pull: always pull from source repository, even in local case
107 pull: always pull from source repository, even in local case
108
108
109 stream: stream raw data uncompressed from repository (fast over
109 stream: stream raw data uncompressed from repository (fast over
110 LAN, slow over WAN)
110 LAN, slow over WAN)
111
111
112 rev: revision to clone up to (implies pull=True)
112 rev: revision to clone up to (implies pull=True)
113
113
114 update: update working directory after clone completes, if
114 update: update working directory after clone completes, if
115 destination is local repository (True means update to default rev,
115 destination is local repository (True means update to default rev,
116 anything else is treated as a revision)
116 anything else is treated as a revision)
117 """
117 """
118
118
119 if isinstance(source, str):
119 if isinstance(source, str):
120 origsource = ui.expandpath(source)
120 origsource = ui.expandpath(source)
121 source, rev, checkout = parseurl(origsource, rev)
121 source, rev, checkout = parseurl(origsource, rev)
122 src_repo = repository(ui, source)
122 src_repo = repository(ui, source)
123 else:
123 else:
124 src_repo = source
124 src_repo = source
125 origsource = source = src_repo.url()
125 origsource = source = src_repo.url()
126 checkout = rev and rev[-1] or None
126 checkout = rev and rev[-1] or None
127
127
128 if dest is None:
128 if dest is None:
129 dest = defaultdest(source)
129 dest = defaultdest(source)
130 ui.status(_("destination directory: %s\n") % dest)
130 ui.status(_("destination directory: %s\n") % dest)
131
131
132 dest = localpath(dest)
132 dest = localpath(dest)
133 source = localpath(source)
133 source = localpath(source)
134
134
135 if os.path.exists(dest):
135 if os.path.exists(dest):
136 if not os.path.isdir(dest):
136 if not os.path.isdir(dest):
137 raise util.Abort(_("destination '%s' already exists") % dest)
137 raise util.Abort(_("destination '%s' already exists") % dest)
138 elif os.listdir(dest):
138 elif os.listdir(dest):
139 raise util.Abort(_("destination '%s' is not empty") % dest)
139 raise util.Abort(_("destination '%s' is not empty") % dest)
140
140
141 class DirCleanup(object):
141 class DirCleanup(object):
142 def __init__(self, dir_):
142 def __init__(self, dir_):
143 self.rmtree = shutil.rmtree
143 self.rmtree = shutil.rmtree
144 self.dir_ = dir_
144 self.dir_ = dir_
145 def close(self):
145 def close(self):
146 self.dir_ = None
146 self.dir_ = None
147 def cleanup(self):
147 def cleanup(self):
148 if self.dir_:
148 if self.dir_:
149 self.rmtree(self.dir_, True)
149 self.rmtree(self.dir_, True)
150
150
151 src_lock = dest_lock = dir_cleanup = None
151 src_lock = dest_lock = dir_cleanup = None
152 try:
152 try:
153 if islocal(dest):
153 if islocal(dest):
154 dir_cleanup = DirCleanup(dest)
154 dir_cleanup = DirCleanup(dest)
155
155
156 abspath = origsource
156 abspath = origsource
157 copy = False
157 copy = False
158 if src_repo.cancopy() and islocal(dest):
158 if src_repo.cancopy() and islocal(dest):
159 abspath = os.path.abspath(util.drop_scheme('file', origsource))
159 abspath = os.path.abspath(util.drop_scheme('file', origsource))
160 copy = not pull and not rev
160 copy = not pull and not rev
161
161
162 if copy:
162 if copy:
163 try:
163 try:
164 # we use a lock here because if we race with commit, we
164 # we use a lock here because if we race with commit, we
165 # can end up with extra data in the cloned revlogs that's
165 # can end up with extra data in the cloned revlogs that's
166 # not pointed to by changesets, thus causing verify to
166 # not pointed to by changesets, thus causing verify to
167 # fail
167 # fail
168 src_lock = src_repo.lock()
168 src_lock = src_repo.lock(wait=False)
169 except error.LockError:
169 except error.LockError:
170 copy = False
170 copy = False
171
171
172 if copy:
172 if copy:
173 hgdir = os.path.realpath(os.path.join(dest, ".hg"))
173 hgdir = os.path.realpath(os.path.join(dest, ".hg"))
174 if not os.path.exists(dest):
174 if not os.path.exists(dest):
175 os.mkdir(dest)
175 os.mkdir(dest)
176 else:
176 else:
177 # only clean up directories we create ourselves
177 # only clean up directories we create ourselves
178 dir_cleanup.dir_ = hgdir
178 dir_cleanup.dir_ = hgdir
179 try:
179 try:
180 dest_path = hgdir
180 dest_path = hgdir
181 os.mkdir(dest_path)
181 os.mkdir(dest_path)
182 except OSError, inst:
182 except OSError, inst:
183 if inst.errno == errno.EEXIST:
183 if inst.errno == errno.EEXIST:
184 dir_cleanup.close()
184 dir_cleanup.close()
185 raise util.Abort(_("destination '%s' already exists")
185 raise util.Abort(_("destination '%s' already exists")
186 % dest)
186 % dest)
187 raise
187 raise
188
188
189 for f in src_repo.store.copylist():
189 for f in src_repo.store.copylist():
190 src = os.path.join(src_repo.path, f)
190 src = os.path.join(src_repo.path, f)
191 dst = os.path.join(dest_path, f)
191 dst = os.path.join(dest_path, f)
192 dstbase = os.path.dirname(dst)
192 dstbase = os.path.dirname(dst)
193 if dstbase and not os.path.exists(dstbase):
193 if dstbase and not os.path.exists(dstbase):
194 os.mkdir(dstbase)
194 os.mkdir(dstbase)
195 if os.path.exists(src):
195 if os.path.exists(src):
196 if dst.endswith('data'):
196 if dst.endswith('data'):
197 # lock to avoid premature writing to the target
197 # lock to avoid premature writing to the target
198 dest_lock = lock.lock(os.path.join(dstbase, "lock"))
198 dest_lock = lock.lock(os.path.join(dstbase, "lock"))
199 util.copyfiles(src, dst)
199 util.copyfiles(src, dst)
200
200
201 # we need to re-init the repo after manually copying the data
201 # we need to re-init the repo after manually copying the data
202 # into it
202 # into it
203 dest_repo = repository(ui, dest)
203 dest_repo = repository(ui, dest)
204
204
205 else:
205 else:
206 try:
206 try:
207 dest_repo = repository(ui, dest, create=True)
207 dest_repo = repository(ui, dest, create=True)
208 except OSError, inst:
208 except OSError, inst:
209 if inst.errno == errno.EEXIST:
209 if inst.errno == errno.EEXIST:
210 dir_cleanup.close()
210 dir_cleanup.close()
211 raise util.Abort(_("destination '%s' already exists")
211 raise util.Abort(_("destination '%s' already exists")
212 % dest)
212 % dest)
213 raise
213 raise
214
214
215 revs = None
215 revs = None
216 if rev:
216 if rev:
217 if 'lookup' not in src_repo.capabilities:
217 if 'lookup' not in src_repo.capabilities:
218 raise util.Abort(_("src repository does not support revision "
218 raise util.Abort(_("src repository does not support revision "
219 "lookup and so doesn't support clone by "
219 "lookup and so doesn't support clone by "
220 "revision"))
220 "revision"))
221 revs = [src_repo.lookup(r) for r in rev]
221 revs = [src_repo.lookup(r) for r in rev]
222 checkout = revs[0]
222 checkout = revs[0]
223 if dest_repo.local():
223 if dest_repo.local():
224 dest_repo.clone(src_repo, heads=revs, stream=stream)
224 dest_repo.clone(src_repo, heads=revs, stream=stream)
225 elif src_repo.local():
225 elif src_repo.local():
226 src_repo.push(dest_repo, revs=revs)
226 src_repo.push(dest_repo, revs=revs)
227 else:
227 else:
228 raise util.Abort(_("clone from remote to remote not supported"))
228 raise util.Abort(_("clone from remote to remote not supported"))
229
229
230 if dir_cleanup:
230 if dir_cleanup:
231 dir_cleanup.close()
231 dir_cleanup.close()
232
232
233 if dest_repo.local():
233 if dest_repo.local():
234 fp = dest_repo.opener("hgrc", "w", text=True)
234 fp = dest_repo.opener("hgrc", "w", text=True)
235 fp.write("[paths]\n")
235 fp.write("[paths]\n")
236 fp.write("default = %s\n" % abspath)
236 fp.write("default = %s\n" % abspath)
237 fp.close()
237 fp.close()
238
238
239 if update:
239 if update:
240 dest_repo.ui.status(_("updating working directory\n"))
240 dest_repo.ui.status(_("updating working directory\n"))
241 if update is not True:
241 if update is not True:
242 checkout = update
242 checkout = update
243 for test in (checkout, 'default', 'tip'):
243 for test in (checkout, 'default', 'tip'):
244 try:
244 try:
245 uprev = dest_repo.lookup(test)
245 uprev = dest_repo.lookup(test)
246 break
246 break
247 except:
247 except:
248 continue
248 continue
249 _update(dest_repo, uprev)
249 _update(dest_repo, uprev)
250
250
251 return src_repo, dest_repo
251 return src_repo, dest_repo
252 finally:
252 finally:
253 release(src_lock, dest_lock)
253 release(src_lock, dest_lock)
254 if dir_cleanup is not None:
254 if dir_cleanup is not None:
255 dir_cleanup.cleanup()
255 dir_cleanup.cleanup()
256
256
257 def _showstats(repo, stats):
257 def _showstats(repo, stats):
258 stats = ((stats[0], _("updated")),
258 stats = ((stats[0], _("updated")),
259 (stats[1], _("merged")),
259 (stats[1], _("merged")),
260 (stats[2], _("removed")),
260 (stats[2], _("removed")),
261 (stats[3], _("unresolved")))
261 (stats[3], _("unresolved")))
262 note = ", ".join([_("%d files %s") % s for s in stats])
262 note = ", ".join([_("%d files %s") % s for s in stats])
263 repo.ui.status("%s\n" % note)
263 repo.ui.status("%s\n" % note)
264
264
265 def update(repo, node):
265 def update(repo, node):
266 """update the working directory to node, merging linear changes"""
266 """update the working directory to node, merging linear changes"""
267 stats = _merge.update(repo, node, False, False, None)
267 stats = _merge.update(repo, node, False, False, None)
268 _showstats(repo, stats)
268 _showstats(repo, stats)
269 if stats[3]:
269 if stats[3]:
270 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n"))
270 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n"))
271 return stats[3] > 0
271 return stats[3] > 0
272
272
273 # naming conflict in clone()
273 # naming conflict in clone()
274 _update = update
274 _update = update
275
275
276 def clean(repo, node, show_stats=True):
276 def clean(repo, node, show_stats=True):
277 """forcibly switch the working directory to node, clobbering changes"""
277 """forcibly switch the working directory to node, clobbering changes"""
278 stats = _merge.update(repo, node, False, True, None)
278 stats = _merge.update(repo, node, False, True, None)
279 if show_stats: _showstats(repo, stats)
279 if show_stats: _showstats(repo, stats)
280 return stats[3] > 0
280 return stats[3] > 0
281
281
282 def merge(repo, node, force=None, remind=True):
282 def merge(repo, node, force=None, remind=True):
283 """branch merge with node, resolving changes"""
283 """branch merge with node, resolving changes"""
284 stats = _merge.update(repo, node, True, force, False)
284 stats = _merge.update(repo, node, True, force, False)
285 _showstats(repo, stats)
285 _showstats(repo, stats)
286 if stats[3]:
286 if stats[3]:
287 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
287 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
288 "or 'hg up --clean' to abandon\n"))
288 "or 'hg up --clean' to abandon\n"))
289 elif remind:
289 elif remind:
290 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
290 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
291 return stats[3] > 0
291 return stats[3] > 0
292
292
293 def revert(repo, node, choose):
293 def revert(repo, node, choose):
294 """revert changes to revision in node without updating dirstate"""
294 """revert changes to revision in node without updating dirstate"""
295 return _merge.update(repo, node, False, True, choose)[3] > 0
295 return _merge.update(repo, node, False, True, choose)[3] > 0
296
296
297 def verify(repo):
297 def verify(repo):
298 """verify the consistency of a repository"""
298 """verify the consistency of a repository"""
299 return _verify.verify(repo)
299 return _verify.verify(repo)
General Comments 0
You need to be logged in to leave comments. Login now