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 or any later version. |
7 # GNU General Public License version 2 or any later version. |
8 |
8 |
9 from i18n import _ |
9 from i18n import _ |
10 from lock import release |
10 from lock import release |
11 from node import hex, nullid, nullrev, short |
11 from node import hex, nullid |
12 import localrepo, bundlerepo, httprepo, sshrepo, statichttprepo |
12 import localrepo, bundlerepo, httprepo, sshrepo, statichttprepo, bookmarks |
13 import lock, util, extensions, error, encoding, node |
13 import lock, util, extensions, error, node |
14 import cmdutil, discovery, url |
14 import cmdutil, discovery |
15 import merge as mergemod |
15 import merge as mergemod |
16 import verify as verifymod |
16 import verify as verifymod |
17 import errno, os, shutil |
17 import errno, os, shutil |
18 |
18 |
19 def _local(path): |
19 def _local(path): |
20 path = util.expandpath(util.drop_scheme('file', path)) |
20 path = util.expandpath(util.localpath(path)) |
21 return (os.path.isfile(path) and bundlerepo or localrepo) |
21 return (os.path.isfile(path) and bundlerepo or localrepo) |
22 |
22 |
23 def addbranchrevs(lrepo, repo, branches, revs): |
23 def addbranchrevs(lrepo, repo, branches, revs): |
24 hashbranch, branches = branches |
24 hashbranch, branches = branches |
25 if not hashbranch and not branches: |
25 if not hashbranch and not branches: |
49 if hashbranch: |
49 if hashbranch: |
50 if not primary(hashbranch): |
50 if not primary(hashbranch): |
51 revs.append(hashbranch) |
51 revs.append(hashbranch) |
52 return revs, revs[0] |
52 return revs, revs[0] |
53 |
53 |
54 def parseurl(url, branches=None): |
54 def parseurl(path, branches=None): |
55 '''parse url#branch, returning (url, (branch, branches))''' |
55 '''parse url#branch, returning (url, (branch, branches))''' |
56 |
56 |
57 if '#' not in url: |
57 u = util.url(path) |
58 return url, (None, branches or []) |
58 branch = None |
59 url, branch = url.split('#', 1) |
59 if u.fragment: |
60 return url, (branch, branches or []) |
60 branch = u.fragment |
|
61 u.fragment = None |
|
62 return str(u), (branch, branches or []) |
61 |
63 |
62 schemes = { |
64 schemes = { |
63 'bundle': bundlerepo, |
65 'bundle': bundlerepo, |
64 'file': _local, |
66 'file': _local, |
65 'http': httprepo, |
67 'http': httprepo, |
66 'https': httprepo, |
68 'https': httprepo, |
67 'ssh': sshrepo, |
69 'ssh': sshrepo, |
68 'static-http': statichttprepo, |
70 'static-http': statichttprepo, |
69 } |
71 } |
70 |
72 |
71 def _lookup(path): |
73 def _peerlookup(path): |
72 scheme = 'file' |
74 u = util.url(path) |
73 if path: |
75 scheme = u.scheme or 'file' |
74 c = path.find(':') |
|
75 if c > 0: |
|
76 scheme = path[:c] |
|
77 thing = schemes.get(scheme) or schemes['file'] |
76 thing = schemes.get(scheme) or schemes['file'] |
78 try: |
77 try: |
79 return thing(path) |
78 return thing(path) |
80 except TypeError: |
79 except TypeError: |
81 return thing |
80 return thing |
82 |
81 |
83 def islocal(repo): |
82 def islocal(repo): |
84 '''return true if repo or path is local''' |
83 '''return true if repo or path is local''' |
85 if isinstance(repo, str): |
84 if isinstance(repo, str): |
86 try: |
85 try: |
87 return _lookup(repo).islocal(repo) |
86 return _peerlookup(repo).islocal(repo) |
88 except AttributeError: |
87 except AttributeError: |
89 return False |
88 return False |
90 return repo.local() |
89 return repo.local() |
91 |
90 |
92 def repository(ui, path='', create=False): |
91 def repository(ui, path='', create=False): |
93 """return a repository object for the specified path""" |
92 """return a repository object for the specified path""" |
94 repo = _lookup(path).instance(ui, path, create) |
93 repo = _peerlookup(path).instance(ui, path, create) |
95 ui = getattr(repo, "ui", ui) |
94 ui = getattr(repo, "ui", ui) |
96 for name, module in extensions.extensions(): |
95 for name, module in extensions.extensions(): |
97 hook = getattr(module, 'reposetup', None) |
96 hook = getattr(module, 'reposetup', None) |
98 if hook: |
97 if hook: |
99 hook(ui, repo) |
98 hook(ui, repo) |
100 return repo |
99 return repo |
101 |
100 |
|
101 def peer(ui, opts, path, create=False): |
|
102 '''return a repository peer for the specified path''' |
|
103 rui = remoteui(ui, opts) |
|
104 return _peerlookup(path).instance(rui, path, create) |
|
105 |
102 def defaultdest(source): |
106 def defaultdest(source): |
103 '''return default destination of clone if none is given''' |
107 '''return default destination of clone if none is given''' |
104 return os.path.basename(os.path.normpath(source)) |
108 return os.path.basename(os.path.normpath(source)) |
105 |
|
106 def localpath(path): |
|
107 if path.startswith('file://localhost/'): |
|
108 return path[16:] |
|
109 if path.startswith('file://'): |
|
110 return path[7:] |
|
111 if path.startswith('file:'): |
|
112 return path[5:] |
|
113 return path |
|
114 |
109 |
115 def share(ui, source, dest=None, update=True): |
110 def share(ui, source, dest=None, update=True): |
116 '''create a shared repository''' |
111 '''create a shared repository''' |
117 |
112 |
118 if not islocal(source): |
113 if not islocal(source): |
141 if os.path.exists(roothg): |
136 if os.path.exists(roothg): |
142 raise util.Abort(_('destination already exists')) |
137 raise util.Abort(_('destination already exists')) |
143 |
138 |
144 if not os.path.isdir(root): |
139 if not os.path.isdir(root): |
145 os.mkdir(root) |
140 os.mkdir(root) |
146 os.mkdir(roothg) |
141 util.makedir(roothg, notindexed=True) |
147 |
142 |
148 requirements = '' |
143 requirements = '' |
149 try: |
144 try: |
150 requirements = srcrepo.opener('requires').read() |
145 requirements = srcrepo.opener.read('requires') |
151 except IOError, inst: |
146 except IOError, inst: |
152 if inst.errno != errno.ENOENT: |
147 if inst.errno != errno.ENOENT: |
153 raise |
148 raise |
154 |
149 |
155 requirements += 'shared\n' |
150 requirements += 'shared\n' |
156 file(os.path.join(roothg, 'requires'), 'w').write(requirements) |
151 util.writefile(os.path.join(roothg, 'requires'), requirements) |
157 file(os.path.join(roothg, 'sharedpath'), 'w').write(sharedpath) |
152 util.writefile(os.path.join(roothg, 'sharedpath'), sharedpath) |
|
153 |
|
154 r = repository(ui, root) |
158 |
155 |
159 default = srcrepo.ui.config('paths', 'default') |
156 default = srcrepo.ui.config('paths', 'default') |
160 if default: |
157 if default: |
161 f = file(os.path.join(roothg, 'hgrc'), 'w') |
158 fp = r.opener("hgrc", "w", text=True) |
162 f.write('[paths]\ndefault = %s\n' % default) |
159 fp.write("[paths]\n") |
163 f.close() |
160 fp.write("default = %s\n" % default) |
164 |
161 fp.close() |
165 r = repository(ui, root) |
|
166 |
162 |
167 if update: |
163 if update: |
168 r.ui.status(_("updating working directory\n")) |
164 r.ui.status(_("updating working directory\n")) |
169 if update is not True: |
165 if update is not True: |
170 checkout = update |
166 checkout = update |
216 """ |
212 """ |
217 |
213 |
218 if isinstance(source, str): |
214 if isinstance(source, str): |
219 origsource = ui.expandpath(source) |
215 origsource = ui.expandpath(source) |
220 source, branch = parseurl(origsource, branch) |
216 source, branch = parseurl(origsource, branch) |
221 src_repo = repository(ui, source) |
217 srcrepo = repository(remoteui(ui, peeropts), source) |
222 else: |
218 else: |
223 src_repo = source |
219 srcrepo = source |
224 branch = (None, branch or []) |
220 branch = (None, branch or []) |
225 origsource = source = src_repo.url() |
221 origsource = source = srcrepo.url() |
226 rev, checkout = addbranchrevs(src_repo, src_repo, branch, rev) |
222 rev, checkout = addbranchrevs(srcrepo, srcrepo, branch, rev) |
227 |
223 |
228 if dest is None: |
224 if dest is None: |
229 dest = defaultdest(source) |
225 dest = defaultdest(source) |
230 ui.status(_("destination directory: %s\n") % dest) |
226 ui.status(_("destination directory: %s\n") % dest) |
231 else: |
227 else: |
232 dest = ui.expandpath(dest) |
228 dest = ui.expandpath(dest) |
233 |
229 |
234 dest = localpath(dest) |
230 dest = util.localpath(dest) |
235 source = localpath(source) |
231 source = util.localpath(source) |
236 |
232 |
237 if os.path.exists(dest): |
233 if os.path.exists(dest): |
238 if not os.path.isdir(dest): |
234 if not os.path.isdir(dest): |
239 raise util.Abort(_("destination '%s' already exists") % dest) |
235 raise util.Abort(_("destination '%s' already exists") % dest) |
240 elif os.listdir(dest): |
236 elif os.listdir(dest): |
248 self.dir_ = None |
244 self.dir_ = None |
249 def cleanup(self): |
245 def cleanup(self): |
250 if self.dir_: |
246 if self.dir_: |
251 self.rmtree(self.dir_, True) |
247 self.rmtree(self.dir_, True) |
252 |
248 |
253 src_lock = dest_lock = dir_cleanup = None |
249 srclock = destlock = dircleanup = None |
254 try: |
250 try: |
|
251 abspath = origsource |
|
252 if islocal(origsource): |
|
253 abspath = os.path.abspath(util.localpath(origsource)) |
|
254 |
255 if islocal(dest): |
255 if islocal(dest): |
256 dir_cleanup = DirCleanup(dest) |
256 dircleanup = DirCleanup(dest) |
257 |
257 |
258 abspath = origsource |
|
259 copy = False |
258 copy = False |
260 if src_repo.cancopy() and islocal(dest): |
259 if srcrepo.cancopy() and islocal(dest): |
261 abspath = os.path.abspath(util.drop_scheme('file', origsource)) |
|
262 copy = not pull and not rev |
260 copy = not pull and not rev |
263 |
261 |
264 if copy: |
262 if copy: |
265 try: |
263 try: |
266 # we use a lock here because if we race with commit, we |
264 # we use a lock here because if we race with commit, we |
267 # can end up with extra data in the cloned revlogs that's |
265 # can end up with extra data in the cloned revlogs that's |
268 # not pointed to by changesets, thus causing verify to |
266 # not pointed to by changesets, thus causing verify to |
269 # fail |
267 # fail |
270 src_lock = src_repo.lock(wait=False) |
268 srclock = srcrepo.lock(wait=False) |
271 except error.LockError: |
269 except error.LockError: |
272 copy = False |
270 copy = False |
273 |
271 |
274 if copy: |
272 if copy: |
275 src_repo.hook('preoutgoing', throw=True, source='clone') |
273 srcrepo.hook('preoutgoing', throw=True, source='clone') |
276 hgdir = os.path.realpath(os.path.join(dest, ".hg")) |
274 hgdir = os.path.realpath(os.path.join(dest, ".hg")) |
277 if not os.path.exists(dest): |
275 if not os.path.exists(dest): |
278 os.mkdir(dest) |
276 os.mkdir(dest) |
279 else: |
277 else: |
280 # only clean up directories we create ourselves |
278 # only clean up directories we create ourselves |
281 dir_cleanup.dir_ = hgdir |
279 dircleanup.dir_ = hgdir |
282 try: |
280 try: |
283 dest_path = hgdir |
281 destpath = hgdir |
284 os.mkdir(dest_path) |
282 util.makedir(destpath, notindexed=True) |
285 except OSError, inst: |
283 except OSError, inst: |
286 if inst.errno == errno.EEXIST: |
284 if inst.errno == errno.EEXIST: |
287 dir_cleanup.close() |
285 dircleanup.close() |
288 raise util.Abort(_("destination '%s' already exists") |
286 raise util.Abort(_("destination '%s' already exists") |
289 % dest) |
287 % dest) |
290 raise |
288 raise |
291 |
289 |
292 hardlink = None |
290 hardlink = None |
293 num = 0 |
291 num = 0 |
294 for f in src_repo.store.copylist(): |
292 for f in srcrepo.store.copylist(): |
295 src = os.path.join(src_repo.sharedpath, f) |
293 src = os.path.join(srcrepo.sharedpath, f) |
296 dst = os.path.join(dest_path, f) |
294 dst = os.path.join(destpath, f) |
297 dstbase = os.path.dirname(dst) |
295 dstbase = os.path.dirname(dst) |
298 if dstbase and not os.path.exists(dstbase): |
296 if dstbase and not os.path.exists(dstbase): |
299 os.mkdir(dstbase) |
297 os.mkdir(dstbase) |
300 if os.path.exists(src): |
298 if os.path.exists(src): |
301 if dst.endswith('data'): |
299 if dst.endswith('data'): |
302 # lock to avoid premature writing to the target |
300 # lock to avoid premature writing to the target |
303 dest_lock = lock.lock(os.path.join(dstbase, "lock")) |
301 destlock = lock.lock(os.path.join(dstbase, "lock")) |
304 hardlink, n = util.copyfiles(src, dst, hardlink) |
302 hardlink, n = util.copyfiles(src, dst, hardlink) |
305 num += n |
303 num += n |
306 if hardlink: |
304 if hardlink: |
307 ui.debug("linked %d files\n" % num) |
305 ui.debug("linked %d files\n" % num) |
308 else: |
306 else: |
309 ui.debug("copied %d files\n" % num) |
307 ui.debug("copied %d files\n" % num) |
310 |
308 |
311 # we need to re-init the repo after manually copying the data |
309 # we need to re-init the repo after manually copying the data |
312 # into it |
310 # into it |
313 dest_repo = repository(ui, dest) |
311 destrepo = repository(remoteui(ui, peeropts), dest) |
314 src_repo.hook('outgoing', source='clone', |
312 srcrepo.hook('outgoing', source='clone', |
315 node=node.hex(node.nullid)) |
313 node=node.hex(node.nullid)) |
316 else: |
314 else: |
317 try: |
315 try: |
318 dest_repo = repository(ui, dest, create=True) |
316 destrepo = repository(remoteui(ui, peeropts), dest, |
|
317 create=True) |
319 except OSError, inst: |
318 except OSError, inst: |
320 if inst.errno == errno.EEXIST: |
319 if inst.errno == errno.EEXIST: |
321 dir_cleanup.close() |
320 dircleanup.close() |
322 raise util.Abort(_("destination '%s' already exists") |
321 raise util.Abort(_("destination '%s' already exists") |
323 % dest) |
322 % dest) |
324 raise |
323 raise |
325 |
324 |
326 revs = None |
325 revs = None |
327 if rev: |
326 if rev: |
328 if 'lookup' not in src_repo.capabilities: |
327 if not srcrepo.capable('lookup'): |
329 raise util.Abort(_("src repository does not support " |
328 raise util.Abort(_("src repository does not support " |
330 "revision lookup and so doesn't " |
329 "revision lookup and so doesn't " |
331 "support clone by revision")) |
330 "support clone by revision")) |
332 revs = [src_repo.lookup(r) for r in rev] |
331 revs = [srcrepo.lookup(r) for r in rev] |
333 checkout = revs[0] |
332 checkout = revs[0] |
334 if dest_repo.local(): |
333 if destrepo.local(): |
335 dest_repo.clone(src_repo, heads=revs, stream=stream) |
334 destrepo.clone(srcrepo, heads=revs, stream=stream) |
336 elif src_repo.local(): |
335 elif srcrepo.local(): |
337 src_repo.push(dest_repo, revs=revs) |
336 srcrepo.push(destrepo, revs=revs) |
338 else: |
337 else: |
339 raise util.Abort(_("clone from remote to remote not supported")) |
338 raise util.Abort(_("clone from remote to remote not supported")) |
340 |
339 |
341 if dir_cleanup: |
340 if dircleanup: |
342 dir_cleanup.close() |
341 dircleanup.close() |
343 |
342 |
344 if dest_repo.local(): |
343 if destrepo.local(): |
345 fp = dest_repo.opener("hgrc", "w", text=True) |
344 fp = destrepo.opener("hgrc", "w", text=True) |
346 fp.write("[paths]\n") |
345 fp.write("[paths]\n") |
347 fp.write("default = %s\n" % abspath) |
346 fp.write("default = %s\n" % abspath) |
348 fp.close() |
347 fp.close() |
349 |
348 |
350 dest_repo.ui.setconfig('paths', 'default', abspath) |
349 destrepo.ui.setconfig('paths', 'default', abspath) |
351 |
350 |
352 if update: |
351 if update: |
353 if update is not True: |
352 if update is not True: |
354 checkout = update |
353 checkout = update |
355 if src_repo.local(): |
354 if srcrepo.local(): |
356 checkout = src_repo.lookup(update) |
355 checkout = srcrepo.lookup(update) |
357 for test in (checkout, 'default', 'tip'): |
356 for test in (checkout, 'default', 'tip'): |
358 if test is None: |
357 if test is None: |
359 continue |
358 continue |
360 try: |
359 try: |
361 uprev = dest_repo.lookup(test) |
360 uprev = destrepo.lookup(test) |
362 break |
361 break |
363 except error.RepoLookupError: |
362 except error.RepoLookupError: |
364 continue |
363 continue |
365 bn = dest_repo[uprev].branch() |
364 bn = destrepo[uprev].branch() |
366 dest_repo.ui.status(_("updating to branch %s\n") % bn) |
365 destrepo.ui.status(_("updating to branch %s\n") % bn) |
367 _update(dest_repo, uprev) |
366 _update(destrepo, uprev) |
368 |
367 |
369 return src_repo, dest_repo |
368 # clone all bookmarks |
|
369 if destrepo.local() and srcrepo.capable("pushkey"): |
|
370 rb = srcrepo.listkeys('bookmarks') |
|
371 for k, n in rb.iteritems(): |
|
372 try: |
|
373 m = destrepo.lookup(n) |
|
374 destrepo._bookmarks[k] = m |
|
375 except error.RepoLookupError: |
|
376 pass |
|
377 if rb: |
|
378 bookmarks.write(destrepo) |
|
379 elif srcrepo.local() and destrepo.capable("pushkey"): |
|
380 for k, n in srcrepo._bookmarks.iteritems(): |
|
381 destrepo.pushkey('bookmarks', k, '', hex(n)) |
|
382 |
|
383 return srcrepo, destrepo |
370 finally: |
384 finally: |
371 release(src_lock, dest_lock) |
385 release(srclock, destlock) |
372 if dir_cleanup is not None: |
386 if dircleanup is not None: |
373 dir_cleanup.cleanup() |
387 dircleanup.cleanup() |
374 |
388 |
375 def _showstats(repo, stats): |
389 def _showstats(repo, stats): |
376 repo.ui.status(_("%d files updated, %d files merged, " |
390 repo.ui.status(_("%d files updated, %d files merged, " |
377 "%d files removed, %d files unresolved\n") % stats) |
391 "%d files removed, %d files unresolved\n") % stats) |
378 |
392 |
413 displaychlist gets called with |
427 displaychlist gets called with |
414 (remoterepo, incomingchangesetlist, displayer) parameters, |
428 (remoterepo, incomingchangesetlist, displayer) parameters, |
415 and is supposed to contain only code that can't be unified. |
429 and is supposed to contain only code that can't be unified. |
416 """ |
430 """ |
417 source, branches = parseurl(ui.expandpath(source), opts.get('branch')) |
431 source, branches = parseurl(ui.expandpath(source), opts.get('branch')) |
418 other = repository(remoteui(repo, opts), source) |
432 other = peer(repo, opts, source) |
419 ui.status(_('comparing with %s\n') % url.hidepassword(source)) |
433 ui.status(_('comparing with %s\n') % util.hidepassword(source)) |
420 revs, checkout = addbranchrevs(repo, other, branches, opts.get('rev')) |
434 revs, checkout = addbranchrevs(repo, other, branches, opts.get('rev')) |
421 |
435 |
422 if revs: |
436 if revs: |
423 revs = [other.lookup(rev) for rev in revs] |
437 revs = [other.lookup(rev) for rev in revs] |
424 other, incoming, bundle = bundlerepo.getremotechanges(ui, repo, other, revs, |
438 other, chlist, cleanupfn = bundlerepo.getremotechanges(ui, repo, other, |
425 opts["bundle"], opts["force"]) |
439 revs, opts["bundle"], opts["force"]) |
426 if incoming is None: |
|
427 ui.status(_("no changes found\n")) |
|
428 return subreporecurse() |
|
429 |
|
430 try: |
440 try: |
431 chlist = other.changelog.nodesbetween(incoming, revs)[0] |
441 if not chlist: |
|
442 ui.status(_("no changes found\n")) |
|
443 return subreporecurse() |
|
444 |
432 displayer = cmdutil.show_changeset(ui, other, opts, buffered) |
445 displayer = cmdutil.show_changeset(ui, other, opts, buffered) |
433 |
446 |
434 # XXX once graphlog extension makes it into core, |
447 # XXX once graphlog extension makes it into core, |
435 # should be replaced by a if graph/else |
448 # should be replaced by a if graph/else |
436 displaychlist(other, chlist, displayer) |
449 displaychlist(other, chlist, displayer) |
437 |
450 |
438 displayer.close() |
451 displayer.close() |
439 finally: |
452 finally: |
440 if hasattr(other, 'close'): |
453 cleanupfn() |
441 other.close() |
|
442 if bundle: |
|
443 os.unlink(bundle) |
|
444 subreporecurse() |
454 subreporecurse() |
445 return 0 # exit code is zero since we found incoming changes |
455 return 0 # exit code is zero since we found incoming changes |
446 |
456 |
447 def incoming(ui, repo, source, opts): |
457 def incoming(ui, repo, source, opts): |
448 def subreporecurse(): |
458 def subreporecurse(): |
470 return _incoming(display, subreporecurse, ui, repo, source, opts) |
480 return _incoming(display, subreporecurse, ui, repo, source, opts) |
471 |
481 |
472 def _outgoing(ui, repo, dest, opts): |
482 def _outgoing(ui, repo, dest, opts): |
473 dest = ui.expandpath(dest or 'default-push', dest or 'default') |
483 dest = ui.expandpath(dest or 'default-push', dest or 'default') |
474 dest, branches = parseurl(dest, opts.get('branch')) |
484 dest, branches = parseurl(dest, opts.get('branch')) |
475 ui.status(_('comparing with %s\n') % url.hidepassword(dest)) |
485 ui.status(_('comparing with %s\n') % util.hidepassword(dest)) |
476 revs, checkout = addbranchrevs(repo, repo, branches, opts.get('rev')) |
486 revs, checkout = addbranchrevs(repo, repo, branches, opts.get('rev')) |
477 if revs: |
487 if revs: |
478 revs = [repo.lookup(rev) for rev in revs] |
488 revs = [repo.lookup(rev) for rev in revs] |
479 |
489 |
480 other = repository(remoteui(repo, opts), dest) |
490 other = peer(repo, opts, dest) |
481 o = discovery.findoutgoing(repo, other, force=opts.get('force')) |
491 common, outheads = discovery.findcommonoutgoing(repo, other, revs, |
|
492 force=opts.get('force')) |
|
493 o = repo.changelog.findmissing(common, outheads) |
482 if not o: |
494 if not o: |
483 ui.status(_("no changes found\n")) |
495 ui.status(_("no changes found\n")) |
484 return None |
496 return None |
485 |
497 return o |
486 return repo.changelog.nodesbetween(o, revs)[0] |
|
487 |
498 |
488 def outgoing(ui, repo, dest, opts): |
499 def outgoing(ui, repo, dest, opts): |
489 def recurse(): |
500 def recurse(): |
490 ret = 1 |
501 ret = 1 |
491 if opts.get('subrepos'): |
502 if opts.get('subrepos'): |