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