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