##// END OF EJS Templates
clone: make default path absolute for all local paths...
Brendan Cully -
r14377:f90d5641 default
parent child Browse files
Show More
@@ -1,557 +1,559
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 of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 from i18n import _
10 10 from lock import release
11 11 from node import hex, nullid
12 12 import localrepo, bundlerepo, httprepo, sshrepo, statichttprepo, bookmarks
13 13 import lock, util, extensions, error, node
14 14 import cmdutil, discovery
15 15 import merge as mergemod
16 16 import verify as verifymod
17 17 import errno, os, shutil
18 18
19 19 def _local(path):
20 20 path = util.expandpath(util.localpath(path))
21 21 return (os.path.isfile(path) and bundlerepo or localrepo)
22 22
23 23 def addbranchrevs(lrepo, repo, branches, revs):
24 24 hashbranch, branches = branches
25 25 if not hashbranch and not branches:
26 26 return revs or None, revs and revs[0] or None
27 27 revs = revs and list(revs) or []
28 28 if not repo.capable('branchmap'):
29 29 if branches:
30 30 raise util.Abort(_("remote branch lookup not supported"))
31 31 revs.append(hashbranch)
32 32 return revs, revs[0]
33 33 branchmap = repo.branchmap()
34 34
35 35 def primary(branch):
36 36 if branch == '.':
37 37 if not lrepo or not lrepo.local():
38 38 raise util.Abort(_("dirstate branch not accessible"))
39 39 branch = lrepo.dirstate.branch()
40 40 if branch in branchmap:
41 41 revs.extend(node.hex(r) for r in reversed(branchmap[branch]))
42 42 return True
43 43 else:
44 44 return False
45 45
46 46 for branch in branches:
47 47 if not primary(branch):
48 48 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
49 49 if hashbranch:
50 50 if not primary(hashbranch):
51 51 revs.append(hashbranch)
52 52 return revs, revs[0]
53 53
54 54 def parseurl(path, branches=None):
55 55 '''parse url#branch, returning (url, (branch, branches))'''
56 56
57 57 u = util.url(path)
58 58 branch = None
59 59 if u.fragment:
60 60 branch = u.fragment
61 61 u.fragment = None
62 62 return str(u), (branch, branches or [])
63 63
64 64 schemes = {
65 65 'bundle': bundlerepo,
66 66 'file': _local,
67 67 'http': httprepo,
68 68 'https': httprepo,
69 69 'ssh': sshrepo,
70 70 'static-http': statichttprepo,
71 71 }
72 72
73 73 def _lookup(path):
74 74 u = util.url(path)
75 75 scheme = u.scheme or 'file'
76 76 thing = schemes.get(scheme) or schemes['file']
77 77 try:
78 78 return thing(path)
79 79 except TypeError:
80 80 return thing
81 81
82 82 def islocal(repo):
83 83 '''return true if repo or path is local'''
84 84 if isinstance(repo, str):
85 85 try:
86 86 return _lookup(repo).islocal(repo)
87 87 except AttributeError:
88 88 return False
89 89 return repo.local()
90 90
91 91 def repository(ui, path='', create=False):
92 92 """return a repository object for the specified path"""
93 93 repo = _lookup(path).instance(ui, path, create)
94 94 ui = getattr(repo, "ui", ui)
95 95 for name, module in extensions.extensions():
96 96 hook = getattr(module, 'reposetup', None)
97 97 if hook:
98 98 hook(ui, repo)
99 99 return repo
100 100
101 101 def defaultdest(source):
102 102 '''return default destination of clone if none is given'''
103 103 return os.path.basename(os.path.normpath(source))
104 104
105 105 def share(ui, source, dest=None, update=True):
106 106 '''create a shared repository'''
107 107
108 108 if not islocal(source):
109 109 raise util.Abort(_('can only share local repositories'))
110 110
111 111 if not dest:
112 112 dest = defaultdest(source)
113 113 else:
114 114 dest = ui.expandpath(dest)
115 115
116 116 if isinstance(source, str):
117 117 origsource = ui.expandpath(source)
118 118 source, branches = parseurl(origsource)
119 119 srcrepo = repository(ui, source)
120 120 rev, checkout = addbranchrevs(srcrepo, srcrepo, branches, None)
121 121 else:
122 122 srcrepo = source
123 123 origsource = source = srcrepo.url()
124 124 checkout = None
125 125
126 126 sharedpath = srcrepo.sharedpath # if our source is already sharing
127 127
128 128 root = os.path.realpath(dest)
129 129 roothg = os.path.join(root, '.hg')
130 130
131 131 if os.path.exists(roothg):
132 132 raise util.Abort(_('destination already exists'))
133 133
134 134 if not os.path.isdir(root):
135 135 os.mkdir(root)
136 136 util.makedir(roothg, notindexed=True)
137 137
138 138 requirements = ''
139 139 try:
140 140 requirements = srcrepo.opener.read('requires')
141 141 except IOError, inst:
142 142 if inst.errno != errno.ENOENT:
143 143 raise
144 144
145 145 requirements += 'shared\n'
146 146 util.writefile(os.path.join(roothg, 'requires'), requirements)
147 147 util.writefile(os.path.join(roothg, 'sharedpath'), sharedpath)
148 148
149 149 r = repository(ui, root)
150 150
151 151 default = srcrepo.ui.config('paths', 'default')
152 152 if default:
153 153 fp = r.opener("hgrc", "w", text=True)
154 154 fp.write("[paths]\n")
155 155 fp.write("default = %s\n" % default)
156 156 fp.close()
157 157
158 158 if update:
159 159 r.ui.status(_("updating working directory\n"))
160 160 if update is not True:
161 161 checkout = update
162 162 for test in (checkout, 'default', 'tip'):
163 163 if test is None:
164 164 continue
165 165 try:
166 166 uprev = r.lookup(test)
167 167 break
168 168 except error.RepoLookupError:
169 169 continue
170 170 _update(r, uprev)
171 171
172 172 def clone(ui, source, dest=None, pull=False, rev=None, update=True,
173 173 stream=False, branch=None):
174 174 """Make a copy of an existing repository.
175 175
176 176 Create a copy of an existing repository in a new directory. The
177 177 source and destination are URLs, as passed to the repository
178 178 function. Returns a pair of repository objects, the source and
179 179 newly created destination.
180 180
181 181 The location of the source is added to the new repository's
182 182 .hg/hgrc file, as the default to be used for future pulls and
183 183 pushes.
184 184
185 185 If an exception is raised, the partly cloned/updated destination
186 186 repository will be deleted.
187 187
188 188 Arguments:
189 189
190 190 source: repository object or URL
191 191
192 192 dest: URL of destination repository to create (defaults to base
193 193 name of source repository)
194 194
195 195 pull: always pull from source repository, even in local case
196 196
197 197 stream: stream raw data uncompressed from repository (fast over
198 198 LAN, slow over WAN)
199 199
200 200 rev: revision to clone up to (implies pull=True)
201 201
202 202 update: update working directory after clone completes, if
203 203 destination is local repository (True means update to default rev,
204 204 anything else is treated as a revision)
205 205
206 206 branch: branches to clone
207 207 """
208 208
209 209 if isinstance(source, str):
210 210 origsource = ui.expandpath(source)
211 211 source, branch = parseurl(origsource, branch)
212 212 src_repo = repository(ui, source)
213 213 else:
214 214 src_repo = source
215 215 branch = (None, branch or [])
216 216 origsource = source = src_repo.url()
217 217 rev, checkout = addbranchrevs(src_repo, src_repo, branch, rev)
218 218
219 219 if dest is None:
220 220 dest = defaultdest(source)
221 221 ui.status(_("destination directory: %s\n") % dest)
222 222 else:
223 223 dest = ui.expandpath(dest)
224 224
225 225 dest = util.localpath(dest)
226 226 source = util.localpath(source)
227 227
228 228 if os.path.exists(dest):
229 229 if not os.path.isdir(dest):
230 230 raise util.Abort(_("destination '%s' already exists") % dest)
231 231 elif os.listdir(dest):
232 232 raise util.Abort(_("destination '%s' is not empty") % dest)
233 233
234 234 class DirCleanup(object):
235 235 def __init__(self, dir_):
236 236 self.rmtree = shutil.rmtree
237 237 self.dir_ = dir_
238 238 def close(self):
239 239 self.dir_ = None
240 240 def cleanup(self):
241 241 if self.dir_:
242 242 self.rmtree(self.dir_, True)
243 243
244 244 src_lock = dest_lock = dir_cleanup = None
245 245 try:
246 abspath = origsource
247 if islocal(origsource):
248 abspath = os.path.abspath(util.localpath(origsource))
249
246 250 if islocal(dest):
247 251 dir_cleanup = DirCleanup(dest)
248 252
249 abspath = origsource
250 253 copy = False
251 254 if src_repo.cancopy() and islocal(dest):
252 abspath = os.path.abspath(util.localpath(origsource))
253 255 copy = not pull and not rev
254 256
255 257 if copy:
256 258 try:
257 259 # we use a lock here because if we race with commit, we
258 260 # can end up with extra data in the cloned revlogs that's
259 261 # not pointed to by changesets, thus causing verify to
260 262 # fail
261 263 src_lock = src_repo.lock(wait=False)
262 264 except error.LockError:
263 265 copy = False
264 266
265 267 if copy:
266 268 src_repo.hook('preoutgoing', throw=True, source='clone')
267 269 hgdir = os.path.realpath(os.path.join(dest, ".hg"))
268 270 if not os.path.exists(dest):
269 271 os.mkdir(dest)
270 272 else:
271 273 # only clean up directories we create ourselves
272 274 dir_cleanup.dir_ = hgdir
273 275 try:
274 276 dest_path = hgdir
275 277 util.makedir(dest_path, notindexed=True)
276 278 except OSError, inst:
277 279 if inst.errno == errno.EEXIST:
278 280 dir_cleanup.close()
279 281 raise util.Abort(_("destination '%s' already exists")
280 282 % dest)
281 283 raise
282 284
283 285 hardlink = None
284 286 num = 0
285 287 for f in src_repo.store.copylist():
286 288 src = os.path.join(src_repo.sharedpath, f)
287 289 dst = os.path.join(dest_path, f)
288 290 dstbase = os.path.dirname(dst)
289 291 if dstbase and not os.path.exists(dstbase):
290 292 os.mkdir(dstbase)
291 293 if os.path.exists(src):
292 294 if dst.endswith('data'):
293 295 # lock to avoid premature writing to the target
294 296 dest_lock = lock.lock(os.path.join(dstbase, "lock"))
295 297 hardlink, n = util.copyfiles(src, dst, hardlink)
296 298 num += n
297 299 if hardlink:
298 300 ui.debug("linked %d files\n" % num)
299 301 else:
300 302 ui.debug("copied %d files\n" % num)
301 303
302 304 # we need to re-init the repo after manually copying the data
303 305 # into it
304 306 dest_repo = repository(ui, dest)
305 307 src_repo.hook('outgoing', source='clone',
306 308 node=node.hex(node.nullid))
307 309 else:
308 310 try:
309 311 dest_repo = repository(ui, dest, create=True)
310 312 except OSError, inst:
311 313 if inst.errno == errno.EEXIST:
312 314 dir_cleanup.close()
313 315 raise util.Abort(_("destination '%s' already exists")
314 316 % dest)
315 317 raise
316 318
317 319 revs = None
318 320 if rev:
319 321 if 'lookup' not in src_repo.capabilities:
320 322 raise util.Abort(_("src repository does not support "
321 323 "revision lookup and so doesn't "
322 324 "support clone by revision"))
323 325 revs = [src_repo.lookup(r) for r in rev]
324 326 checkout = revs[0]
325 327 if dest_repo.local():
326 328 dest_repo.clone(src_repo, heads=revs, stream=stream)
327 329 elif src_repo.local():
328 330 src_repo.push(dest_repo, revs=revs)
329 331 else:
330 332 raise util.Abort(_("clone from remote to remote not supported"))
331 333
332 334 if dir_cleanup:
333 335 dir_cleanup.close()
334 336
335 337 if dest_repo.local():
336 338 fp = dest_repo.opener("hgrc", "w", text=True)
337 339 fp.write("[paths]\n")
338 340 fp.write("default = %s\n" % abspath)
339 341 fp.close()
340 342
341 343 dest_repo.ui.setconfig('paths', 'default', abspath)
342 344
343 345 if update:
344 346 if update is not True:
345 347 checkout = update
346 348 if src_repo.local():
347 349 checkout = src_repo.lookup(update)
348 350 for test in (checkout, 'default', 'tip'):
349 351 if test is None:
350 352 continue
351 353 try:
352 354 uprev = dest_repo.lookup(test)
353 355 break
354 356 except error.RepoLookupError:
355 357 continue
356 358 bn = dest_repo[uprev].branch()
357 359 dest_repo.ui.status(_("updating to branch %s\n") % bn)
358 360 _update(dest_repo, uprev)
359 361
360 362 # clone all bookmarks
361 363 if dest_repo.local() and src_repo.capable("pushkey"):
362 364 rb = src_repo.listkeys('bookmarks')
363 365 for k, n in rb.iteritems():
364 366 try:
365 367 m = dest_repo.lookup(n)
366 368 dest_repo._bookmarks[k] = m
367 369 except error.RepoLookupError:
368 370 pass
369 371 if rb:
370 372 bookmarks.write(dest_repo)
371 373 elif src_repo.local() and dest_repo.capable("pushkey"):
372 374 for k, n in src_repo._bookmarks.iteritems():
373 375 dest_repo.pushkey('bookmarks', k, '', hex(n))
374 376
375 377 return src_repo, dest_repo
376 378 finally:
377 379 release(src_lock, dest_lock)
378 380 if dir_cleanup is not None:
379 381 dir_cleanup.cleanup()
380 382
381 383 def _showstats(repo, stats):
382 384 repo.ui.status(_("%d files updated, %d files merged, "
383 385 "%d files removed, %d files unresolved\n") % stats)
384 386
385 387 def update(repo, node):
386 388 """update the working directory to node, merging linear changes"""
387 389 stats = mergemod.update(repo, node, False, False, None)
388 390 _showstats(repo, stats)
389 391 if stats[3]:
390 392 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n"))
391 393 return stats[3] > 0
392 394
393 395 # naming conflict in clone()
394 396 _update = update
395 397
396 398 def clean(repo, node, show_stats=True):
397 399 """forcibly switch the working directory to node, clobbering changes"""
398 400 stats = mergemod.update(repo, node, False, True, None)
399 401 if show_stats:
400 402 _showstats(repo, stats)
401 403 return stats[3] > 0
402 404
403 405 def merge(repo, node, force=None, remind=True):
404 406 """Branch merge with node, resolving changes. Return true if any
405 407 unresolved conflicts."""
406 408 stats = mergemod.update(repo, node, True, force, False)
407 409 _showstats(repo, stats)
408 410 if stats[3]:
409 411 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
410 412 "or 'hg update -C .' to abandon\n"))
411 413 elif remind:
412 414 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
413 415 return stats[3] > 0
414 416
415 417 def _incoming(displaychlist, subreporecurse, ui, repo, source,
416 418 opts, buffered=False):
417 419 """
418 420 Helper for incoming / gincoming.
419 421 displaychlist gets called with
420 422 (remoterepo, incomingchangesetlist, displayer) parameters,
421 423 and is supposed to contain only code that can't be unified.
422 424 """
423 425 source, branches = parseurl(ui.expandpath(source), opts.get('branch'))
424 426 other = repository(remoteui(repo, opts), source)
425 427 ui.status(_('comparing with %s\n') % util.hidepassword(source))
426 428 revs, checkout = addbranchrevs(repo, other, branches, opts.get('rev'))
427 429
428 430 if revs:
429 431 revs = [other.lookup(rev) for rev in revs]
430 432 other, chlist, cleanupfn = bundlerepo.getremotechanges(ui, repo, other,
431 433 revs, opts["bundle"], opts["force"])
432 434 try:
433 435 if not chlist:
434 436 ui.status(_("no changes found\n"))
435 437 return subreporecurse()
436 438
437 439 displayer = cmdutil.show_changeset(ui, other, opts, buffered)
438 440
439 441 # XXX once graphlog extension makes it into core,
440 442 # should be replaced by a if graph/else
441 443 displaychlist(other, chlist, displayer)
442 444
443 445 displayer.close()
444 446 finally:
445 447 cleanupfn()
446 448 subreporecurse()
447 449 return 0 # exit code is zero since we found incoming changes
448 450
449 451 def incoming(ui, repo, source, opts):
450 452 def subreporecurse():
451 453 ret = 1
452 454 if opts.get('subrepos'):
453 455 ctx = repo[None]
454 456 for subpath in sorted(ctx.substate):
455 457 sub = ctx.sub(subpath)
456 458 ret = min(ret, sub.incoming(ui, source, opts))
457 459 return ret
458 460
459 461 def display(other, chlist, displayer):
460 462 limit = cmdutil.loglimit(opts)
461 463 if opts.get('newest_first'):
462 464 chlist.reverse()
463 465 count = 0
464 466 for n in chlist:
465 467 if limit is not None and count >= limit:
466 468 break
467 469 parents = [p for p in other.changelog.parents(n) if p != nullid]
468 470 if opts.get('no_merges') and len(parents) == 2:
469 471 continue
470 472 count += 1
471 473 displayer.show(other[n])
472 474 return _incoming(display, subreporecurse, ui, repo, source, opts)
473 475
474 476 def _outgoing(ui, repo, dest, opts):
475 477 dest = ui.expandpath(dest or 'default-push', dest or 'default')
476 478 dest, branches = parseurl(dest, opts.get('branch'))
477 479 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
478 480 revs, checkout = addbranchrevs(repo, repo, branches, opts.get('rev'))
479 481 if revs:
480 482 revs = [repo.lookup(rev) for rev in revs]
481 483
482 484 other = repository(remoteui(repo, opts), dest)
483 485 common, outheads = discovery.findcommonoutgoing(repo, other, revs,
484 486 force=opts.get('force'))
485 487 o = repo.changelog.findmissing(common, outheads)
486 488 if not o:
487 489 ui.status(_("no changes found\n"))
488 490 return None
489 491 return o
490 492
491 493 def outgoing(ui, repo, dest, opts):
492 494 def recurse():
493 495 ret = 1
494 496 if opts.get('subrepos'):
495 497 ctx = repo[None]
496 498 for subpath in sorted(ctx.substate):
497 499 sub = ctx.sub(subpath)
498 500 ret = min(ret, sub.outgoing(ui, dest, opts))
499 501 return ret
500 502
501 503 limit = cmdutil.loglimit(opts)
502 504 o = _outgoing(ui, repo, dest, opts)
503 505 if o is None:
504 506 return recurse()
505 507
506 508 if opts.get('newest_first'):
507 509 o.reverse()
508 510 displayer = cmdutil.show_changeset(ui, repo, opts)
509 511 count = 0
510 512 for n in o:
511 513 if limit is not None and count >= limit:
512 514 break
513 515 parents = [p for p in repo.changelog.parents(n) if p != nullid]
514 516 if opts.get('no_merges') and len(parents) == 2:
515 517 continue
516 518 count += 1
517 519 displayer.show(repo[n])
518 520 displayer.close()
519 521 recurse()
520 522 return 0 # exit code is zero since we found outgoing changes
521 523
522 524 def revert(repo, node, choose):
523 525 """revert changes to revision in node without updating dirstate"""
524 526 return mergemod.update(repo, node, False, True, choose)[3] > 0
525 527
526 528 def verify(repo):
527 529 """verify the consistency of a repository"""
528 530 return verifymod.verify(repo)
529 531
530 532 def remoteui(src, opts):
531 533 'build a remote ui from ui or repo and opts'
532 534 if hasattr(src, 'baseui'): # looks like a repository
533 535 dst = src.baseui.copy() # drop repo-specific config
534 536 src = src.ui # copy target options from repo
535 537 else: # assume it's a global ui object
536 538 dst = src.copy() # keep all global options
537 539
538 540 # copy ssh-specific options
539 541 for o in 'ssh', 'remotecmd':
540 542 v = opts.get(o) or src.config('ui', o)
541 543 if v:
542 544 dst.setconfig("ui", o, v)
543 545
544 546 # copy bundle-specific options
545 547 r = src.config('bundle', 'mainreporoot')
546 548 if r:
547 549 dst.setconfig('bundle', 'mainreporoot', r)
548 550
549 551 # copy selected local settings to the remote ui
550 552 for sect in ('auth', 'hostfingerprints', 'http_proxy'):
551 553 for key, val in src.configitems(sect):
552 554 dst.setconfig(sect, key, val)
553 555 v = src.config('web', 'cacerts')
554 556 if v:
555 557 dst.setconfig('web', 'cacerts', util.expandpath(v))
556 558
557 559 return dst
General Comments 0
You need to be logged in to leave comments. Login now