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