##// END OF EJS Templates
clone: check update rev for being True...
Sean Farley -
r26354:c1fb2cab default
parent child Browse files
Show More
@@ -1,899 +1,900
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 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 __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 import errno
11 import errno
12 import os
12 import os
13 import shutil
13 import shutil
14
14
15 from .i18n import _
15 from .i18n import _
16 from .node import nullid
16 from .node import nullid
17
17
18 from . import (
18 from . import (
19 bookmarks,
19 bookmarks,
20 bundlerepo,
20 bundlerepo,
21 cmdutil,
21 cmdutil,
22 discovery,
22 discovery,
23 error,
23 error,
24 exchange,
24 exchange,
25 extensions,
25 extensions,
26 httppeer,
26 httppeer,
27 localrepo,
27 localrepo,
28 lock,
28 lock,
29 merge as mergemod,
29 merge as mergemod,
30 node,
30 node,
31 phases,
31 phases,
32 repoview,
32 repoview,
33 scmutil,
33 scmutil,
34 sshpeer,
34 sshpeer,
35 statichttprepo,
35 statichttprepo,
36 ui as uimod,
36 ui as uimod,
37 unionrepo,
37 unionrepo,
38 url,
38 url,
39 util,
39 util,
40 verify as verifymod,
40 verify as verifymod,
41 )
41 )
42
42
43 release = lock.release
43 release = lock.release
44
44
45 def _local(path):
45 def _local(path):
46 path = util.expandpath(util.urllocalpath(path))
46 path = util.expandpath(util.urllocalpath(path))
47 return (os.path.isfile(path) and bundlerepo or localrepo)
47 return (os.path.isfile(path) and bundlerepo or localrepo)
48
48
49 def addbranchrevs(lrepo, other, branches, revs):
49 def addbranchrevs(lrepo, other, branches, revs):
50 peer = other.peer() # a courtesy to callers using a localrepo for other
50 peer = other.peer() # a courtesy to callers using a localrepo for other
51 hashbranch, branches = branches
51 hashbranch, branches = branches
52 if not hashbranch and not branches:
52 if not hashbranch and not branches:
53 x = revs or None
53 x = revs or None
54 if util.safehasattr(revs, 'first'):
54 if util.safehasattr(revs, 'first'):
55 y = revs.first()
55 y = revs.first()
56 elif revs:
56 elif revs:
57 y = revs[0]
57 y = revs[0]
58 else:
58 else:
59 y = None
59 y = None
60 return x, y
60 return x, y
61 if revs:
61 if revs:
62 revs = list(revs)
62 revs = list(revs)
63 else:
63 else:
64 revs = []
64 revs = []
65
65
66 if not peer.capable('branchmap'):
66 if not peer.capable('branchmap'):
67 if branches:
67 if branches:
68 raise util.Abort(_("remote branch lookup not supported"))
68 raise util.Abort(_("remote branch lookup not supported"))
69 revs.append(hashbranch)
69 revs.append(hashbranch)
70 return revs, revs[0]
70 return revs, revs[0]
71 branchmap = peer.branchmap()
71 branchmap = peer.branchmap()
72
72
73 def primary(branch):
73 def primary(branch):
74 if branch == '.':
74 if branch == '.':
75 if not lrepo:
75 if not lrepo:
76 raise util.Abort(_("dirstate branch not accessible"))
76 raise util.Abort(_("dirstate branch not accessible"))
77 branch = lrepo.dirstate.branch()
77 branch = lrepo.dirstate.branch()
78 if branch in branchmap:
78 if branch in branchmap:
79 revs.extend(node.hex(r) for r in reversed(branchmap[branch]))
79 revs.extend(node.hex(r) for r in reversed(branchmap[branch]))
80 return True
80 return True
81 else:
81 else:
82 return False
82 return False
83
83
84 for branch in branches:
84 for branch in branches:
85 if not primary(branch):
85 if not primary(branch):
86 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
86 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
87 if hashbranch:
87 if hashbranch:
88 if not primary(hashbranch):
88 if not primary(hashbranch):
89 revs.append(hashbranch)
89 revs.append(hashbranch)
90 return revs, revs[0]
90 return revs, revs[0]
91
91
92 def parseurl(path, branches=None):
92 def parseurl(path, branches=None):
93 '''parse url#branch, returning (url, (branch, branches))'''
93 '''parse url#branch, returning (url, (branch, branches))'''
94
94
95 u = util.url(path)
95 u = util.url(path)
96 branch = None
96 branch = None
97 if u.fragment:
97 if u.fragment:
98 branch = u.fragment
98 branch = u.fragment
99 u.fragment = None
99 u.fragment = None
100 return str(u), (branch, branches or [])
100 return str(u), (branch, branches or [])
101
101
102 schemes = {
102 schemes = {
103 'bundle': bundlerepo,
103 'bundle': bundlerepo,
104 'union': unionrepo,
104 'union': unionrepo,
105 'file': _local,
105 'file': _local,
106 'http': httppeer,
106 'http': httppeer,
107 'https': httppeer,
107 'https': httppeer,
108 'ssh': sshpeer,
108 'ssh': sshpeer,
109 'static-http': statichttprepo,
109 'static-http': statichttprepo,
110 }
110 }
111
111
112 def _peerlookup(path):
112 def _peerlookup(path):
113 u = util.url(path)
113 u = util.url(path)
114 scheme = u.scheme or 'file'
114 scheme = u.scheme or 'file'
115 thing = schemes.get(scheme) or schemes['file']
115 thing = schemes.get(scheme) or schemes['file']
116 try:
116 try:
117 return thing(path)
117 return thing(path)
118 except TypeError:
118 except TypeError:
119 # we can't test callable(thing) because 'thing' can be an unloaded
119 # we can't test callable(thing) because 'thing' can be an unloaded
120 # module that implements __call__
120 # module that implements __call__
121 if not util.safehasattr(thing, 'instance'):
121 if not util.safehasattr(thing, 'instance'):
122 raise
122 raise
123 return thing
123 return thing
124
124
125 def islocal(repo):
125 def islocal(repo):
126 '''return true if repo (or path pointing to repo) is local'''
126 '''return true if repo (or path pointing to repo) is local'''
127 if isinstance(repo, str):
127 if isinstance(repo, str):
128 try:
128 try:
129 return _peerlookup(repo).islocal(repo)
129 return _peerlookup(repo).islocal(repo)
130 except AttributeError:
130 except AttributeError:
131 return False
131 return False
132 return repo.local()
132 return repo.local()
133
133
134 def openpath(ui, path):
134 def openpath(ui, path):
135 '''open path with open if local, url.open if remote'''
135 '''open path with open if local, url.open if remote'''
136 pathurl = util.url(path, parsequery=False, parsefragment=False)
136 pathurl = util.url(path, parsequery=False, parsefragment=False)
137 if pathurl.islocal():
137 if pathurl.islocal():
138 return util.posixfile(pathurl.localpath(), 'rb')
138 return util.posixfile(pathurl.localpath(), 'rb')
139 else:
139 else:
140 return url.open(ui, path)
140 return url.open(ui, path)
141
141
142 # a list of (ui, repo) functions called for wire peer initialization
142 # a list of (ui, repo) functions called for wire peer initialization
143 wirepeersetupfuncs = []
143 wirepeersetupfuncs = []
144
144
145 def _peerorrepo(ui, path, create=False):
145 def _peerorrepo(ui, path, create=False):
146 """return a repository object for the specified path"""
146 """return a repository object for the specified path"""
147 obj = _peerlookup(path).instance(ui, path, create)
147 obj = _peerlookup(path).instance(ui, path, create)
148 ui = getattr(obj, "ui", ui)
148 ui = getattr(obj, "ui", ui)
149 for name, module in extensions.extensions(ui):
149 for name, module in extensions.extensions(ui):
150 hook = getattr(module, 'reposetup', None)
150 hook = getattr(module, 'reposetup', None)
151 if hook:
151 if hook:
152 hook(ui, obj)
152 hook(ui, obj)
153 if not obj.local():
153 if not obj.local():
154 for f in wirepeersetupfuncs:
154 for f in wirepeersetupfuncs:
155 f(ui, obj)
155 f(ui, obj)
156 return obj
156 return obj
157
157
158 def repository(ui, path='', create=False):
158 def repository(ui, path='', create=False):
159 """return a repository object for the specified path"""
159 """return a repository object for the specified path"""
160 peer = _peerorrepo(ui, path, create)
160 peer = _peerorrepo(ui, path, create)
161 repo = peer.local()
161 repo = peer.local()
162 if not repo:
162 if not repo:
163 raise util.Abort(_("repository '%s' is not local") %
163 raise util.Abort(_("repository '%s' is not local") %
164 (path or peer.url()))
164 (path or peer.url()))
165 return repo.filtered('visible')
165 return repo.filtered('visible')
166
166
167 def peer(uiorrepo, opts, path, create=False):
167 def peer(uiorrepo, opts, path, create=False):
168 '''return a repository peer for the specified path'''
168 '''return a repository peer for the specified path'''
169 rui = remoteui(uiorrepo, opts)
169 rui = remoteui(uiorrepo, opts)
170 return _peerorrepo(rui, path, create).peer()
170 return _peerorrepo(rui, path, create).peer()
171
171
172 def defaultdest(source):
172 def defaultdest(source):
173 '''return default destination of clone if none is given
173 '''return default destination of clone if none is given
174
174
175 >>> defaultdest('foo')
175 >>> defaultdest('foo')
176 'foo'
176 'foo'
177 >>> defaultdest('/foo/bar')
177 >>> defaultdest('/foo/bar')
178 'bar'
178 'bar'
179 >>> defaultdest('/')
179 >>> defaultdest('/')
180 ''
180 ''
181 >>> defaultdest('')
181 >>> defaultdest('')
182 ''
182 ''
183 >>> defaultdest('http://example.org/')
183 >>> defaultdest('http://example.org/')
184 ''
184 ''
185 >>> defaultdest('http://example.org/foo/')
185 >>> defaultdest('http://example.org/foo/')
186 'foo'
186 'foo'
187 '''
187 '''
188 path = util.url(source).path
188 path = util.url(source).path
189 if not path:
189 if not path:
190 return ''
190 return ''
191 return os.path.basename(os.path.normpath(path))
191 return os.path.basename(os.path.normpath(path))
192
192
193 def share(ui, source, dest=None, update=True, bookmarks=True):
193 def share(ui, source, dest=None, update=True, bookmarks=True):
194 '''create a shared repository'''
194 '''create a shared repository'''
195
195
196 if not islocal(source):
196 if not islocal(source):
197 raise util.Abort(_('can only share local repositories'))
197 raise util.Abort(_('can only share local repositories'))
198
198
199 if not dest:
199 if not dest:
200 dest = defaultdest(source)
200 dest = defaultdest(source)
201 else:
201 else:
202 dest = ui.expandpath(dest)
202 dest = ui.expandpath(dest)
203
203
204 if isinstance(source, str):
204 if isinstance(source, str):
205 origsource = ui.expandpath(source)
205 origsource = ui.expandpath(source)
206 source, branches = parseurl(origsource)
206 source, branches = parseurl(origsource)
207 srcrepo = repository(ui, source)
207 srcrepo = repository(ui, source)
208 rev, checkout = addbranchrevs(srcrepo, srcrepo, branches, None)
208 rev, checkout = addbranchrevs(srcrepo, srcrepo, branches, None)
209 else:
209 else:
210 srcrepo = source.local()
210 srcrepo = source.local()
211 origsource = source = srcrepo.url()
211 origsource = source = srcrepo.url()
212 checkout = None
212 checkout = None
213
213
214 sharedpath = srcrepo.sharedpath # if our source is already sharing
214 sharedpath = srcrepo.sharedpath # if our source is already sharing
215
215
216 destwvfs = scmutil.vfs(dest, realpath=True)
216 destwvfs = scmutil.vfs(dest, realpath=True)
217 destvfs = scmutil.vfs(os.path.join(destwvfs.base, '.hg'), realpath=True)
217 destvfs = scmutil.vfs(os.path.join(destwvfs.base, '.hg'), realpath=True)
218
218
219 if destvfs.lexists():
219 if destvfs.lexists():
220 raise util.Abort(_('destination already exists'))
220 raise util.Abort(_('destination already exists'))
221
221
222 if not destwvfs.isdir():
222 if not destwvfs.isdir():
223 destwvfs.mkdir()
223 destwvfs.mkdir()
224 destvfs.makedir()
224 destvfs.makedir()
225
225
226 requirements = ''
226 requirements = ''
227 try:
227 try:
228 requirements = srcrepo.vfs.read('requires')
228 requirements = srcrepo.vfs.read('requires')
229 except IOError as inst:
229 except IOError as inst:
230 if inst.errno != errno.ENOENT:
230 if inst.errno != errno.ENOENT:
231 raise
231 raise
232
232
233 requirements += 'shared\n'
233 requirements += 'shared\n'
234 destvfs.write('requires', requirements)
234 destvfs.write('requires', requirements)
235 destvfs.write('sharedpath', sharedpath)
235 destvfs.write('sharedpath', sharedpath)
236
236
237 r = repository(ui, destwvfs.base)
237 r = repository(ui, destwvfs.base)
238
238
239 default = srcrepo.ui.config('paths', 'default')
239 default = srcrepo.ui.config('paths', 'default')
240 if default:
240 if default:
241 fp = r.vfs("hgrc", "w", text=True)
241 fp = r.vfs("hgrc", "w", text=True)
242 fp.write("[paths]\n")
242 fp.write("[paths]\n")
243 fp.write("default = %s\n" % default)
243 fp.write("default = %s\n" % default)
244 fp.close()
244 fp.close()
245
245
246 if update:
246 if update:
247 r.ui.status(_("updating working directory\n"))
247 r.ui.status(_("updating working directory\n"))
248 if update is not True:
248 if update is not True:
249 checkout = update
249 checkout = update
250 for test in (checkout, 'default', 'tip'):
250 for test in (checkout, 'default', 'tip'):
251 if test is None:
251 if test is None:
252 continue
252 continue
253 try:
253 try:
254 uprev = r.lookup(test)
254 uprev = r.lookup(test)
255 break
255 break
256 except error.RepoLookupError:
256 except error.RepoLookupError:
257 continue
257 continue
258 _update(r, uprev)
258 _update(r, uprev)
259
259
260 if bookmarks:
260 if bookmarks:
261 fp = r.vfs('shared', 'w')
261 fp = r.vfs('shared', 'w')
262 fp.write('bookmarks\n')
262 fp.write('bookmarks\n')
263 fp.close()
263 fp.close()
264
264
265 def copystore(ui, srcrepo, destpath):
265 def copystore(ui, srcrepo, destpath):
266 '''copy files from store of srcrepo in destpath
266 '''copy files from store of srcrepo in destpath
267
267
268 returns destlock
268 returns destlock
269 '''
269 '''
270 destlock = None
270 destlock = None
271 try:
271 try:
272 hardlink = None
272 hardlink = None
273 num = 0
273 num = 0
274 closetopic = [None]
274 closetopic = [None]
275 def prog(topic, pos):
275 def prog(topic, pos):
276 if pos is None:
276 if pos is None:
277 closetopic[0] = topic
277 closetopic[0] = topic
278 else:
278 else:
279 ui.progress(topic, pos + num)
279 ui.progress(topic, pos + num)
280 srcpublishing = srcrepo.publishing()
280 srcpublishing = srcrepo.publishing()
281 srcvfs = scmutil.vfs(srcrepo.sharedpath)
281 srcvfs = scmutil.vfs(srcrepo.sharedpath)
282 dstvfs = scmutil.vfs(destpath)
282 dstvfs = scmutil.vfs(destpath)
283 for f in srcrepo.store.copylist():
283 for f in srcrepo.store.copylist():
284 if srcpublishing and f.endswith('phaseroots'):
284 if srcpublishing and f.endswith('phaseroots'):
285 continue
285 continue
286 dstbase = os.path.dirname(f)
286 dstbase = os.path.dirname(f)
287 if dstbase and not dstvfs.exists(dstbase):
287 if dstbase and not dstvfs.exists(dstbase):
288 dstvfs.mkdir(dstbase)
288 dstvfs.mkdir(dstbase)
289 if srcvfs.exists(f):
289 if srcvfs.exists(f):
290 if f.endswith('data'):
290 if f.endswith('data'):
291 # 'dstbase' may be empty (e.g. revlog format 0)
291 # 'dstbase' may be empty (e.g. revlog format 0)
292 lockfile = os.path.join(dstbase, "lock")
292 lockfile = os.path.join(dstbase, "lock")
293 # lock to avoid premature writing to the target
293 # lock to avoid premature writing to the target
294 destlock = lock.lock(dstvfs, lockfile)
294 destlock = lock.lock(dstvfs, lockfile)
295 hardlink, n = util.copyfiles(srcvfs.join(f), dstvfs.join(f),
295 hardlink, n = util.copyfiles(srcvfs.join(f), dstvfs.join(f),
296 hardlink, progress=prog)
296 hardlink, progress=prog)
297 num += n
297 num += n
298 if hardlink:
298 if hardlink:
299 ui.debug("linked %d files\n" % num)
299 ui.debug("linked %d files\n" % num)
300 if closetopic[0]:
300 if closetopic[0]:
301 ui.progress(closetopic[0], None)
301 ui.progress(closetopic[0], None)
302 else:
302 else:
303 ui.debug("copied %d files\n" % num)
303 ui.debug("copied %d files\n" % num)
304 if closetopic[0]:
304 if closetopic[0]:
305 ui.progress(closetopic[0], None)
305 ui.progress(closetopic[0], None)
306 return destlock
306 return destlock
307 except: # re-raises
307 except: # re-raises
308 release(destlock)
308 release(destlock)
309 raise
309 raise
310
310
311 def clonewithshare(ui, peeropts, sharepath, source, srcpeer, dest, pull=False,
311 def clonewithshare(ui, peeropts, sharepath, source, srcpeer, dest, pull=False,
312 rev=None, update=True, stream=False):
312 rev=None, update=True, stream=False):
313 """Perform a clone using a shared repo.
313 """Perform a clone using a shared repo.
314
314
315 The store for the repository will be located at <sharepath>/.hg. The
315 The store for the repository will be located at <sharepath>/.hg. The
316 specified revisions will be cloned or pulled from "source". A shared repo
316 specified revisions will be cloned or pulled from "source". A shared repo
317 will be created at "dest" and a working copy will be created if "update" is
317 will be created at "dest" and a working copy will be created if "update" is
318 True.
318 True.
319 """
319 """
320 revs = None
320 revs = None
321 if rev:
321 if rev:
322 if not srcpeer.capable('lookup'):
322 if not srcpeer.capable('lookup'):
323 raise util.Abort(_("src repository does not support "
323 raise util.Abort(_("src repository does not support "
324 "revision lookup and so doesn't "
324 "revision lookup and so doesn't "
325 "support clone by revision"))
325 "support clone by revision"))
326 revs = [srcpeer.lookup(r) for r in rev]
326 revs = [srcpeer.lookup(r) for r in rev]
327
327
328 basename = os.path.basename(sharepath)
328 basename = os.path.basename(sharepath)
329
329
330 if os.path.exists(sharepath):
330 if os.path.exists(sharepath):
331 ui.status(_('(sharing from existing pooled repository %s)\n') %
331 ui.status(_('(sharing from existing pooled repository %s)\n') %
332 basename)
332 basename)
333 else:
333 else:
334 ui.status(_('(sharing from new pooled repository %s)\n') % basename)
334 ui.status(_('(sharing from new pooled repository %s)\n') % basename)
335 # Always use pull mode because hardlinks in share mode don't work well.
335 # Always use pull mode because hardlinks in share mode don't work well.
336 # Never update because working copies aren't necessary in share mode.
336 # Never update because working copies aren't necessary in share mode.
337 clone(ui, peeropts, source, dest=sharepath, pull=True,
337 clone(ui, peeropts, source, dest=sharepath, pull=True,
338 rev=rev, update=False, stream=stream)
338 rev=rev, update=False, stream=stream)
339
339
340 sharerepo = repository(ui, path=sharepath)
340 sharerepo = repository(ui, path=sharepath)
341 share(ui, sharerepo, dest=dest, update=update, bookmarks=False)
341 share(ui, sharerepo, dest=dest, update=update, bookmarks=False)
342
342
343 # We need to perform a pull against the dest repo to fetch bookmarks
343 # We need to perform a pull against the dest repo to fetch bookmarks
344 # and other non-store data that isn't shared by default. In the case of
344 # and other non-store data that isn't shared by default. In the case of
345 # non-existing shared repo, this means we pull from the remote twice. This
345 # non-existing shared repo, this means we pull from the remote twice. This
346 # is a bit weird. But at the time it was implemented, there wasn't an easy
346 # is a bit weird. But at the time it was implemented, there wasn't an easy
347 # way to pull just non-changegroup data.
347 # way to pull just non-changegroup data.
348 destrepo = repository(ui, path=dest)
348 destrepo = repository(ui, path=dest)
349 exchange.pull(destrepo, srcpeer, heads=revs)
349 exchange.pull(destrepo, srcpeer, heads=revs)
350
350
351 return srcpeer, peer(ui, peeropts, dest)
351 return srcpeer, peer(ui, peeropts, dest)
352
352
353 def clone(ui, peeropts, source, dest=None, pull=False, rev=None,
353 def clone(ui, peeropts, source, dest=None, pull=False, rev=None,
354 update=True, stream=False, branch=None, shareopts=None):
354 update=True, stream=False, branch=None, shareopts=None):
355 """Make a copy of an existing repository.
355 """Make a copy of an existing repository.
356
356
357 Create a copy of an existing repository in a new directory. The
357 Create a copy of an existing repository in a new directory. The
358 source and destination are URLs, as passed to the repository
358 source and destination are URLs, as passed to the repository
359 function. Returns a pair of repository peers, the source and
359 function. Returns a pair of repository peers, the source and
360 newly created destination.
360 newly created destination.
361
361
362 The location of the source is added to the new repository's
362 The location of the source is added to the new repository's
363 .hg/hgrc file, as the default to be used for future pulls and
363 .hg/hgrc file, as the default to be used for future pulls and
364 pushes.
364 pushes.
365
365
366 If an exception is raised, the partly cloned/updated destination
366 If an exception is raised, the partly cloned/updated destination
367 repository will be deleted.
367 repository will be deleted.
368
368
369 Arguments:
369 Arguments:
370
370
371 source: repository object or URL
371 source: repository object or URL
372
372
373 dest: URL of destination repository to create (defaults to base
373 dest: URL of destination repository to create (defaults to base
374 name of source repository)
374 name of source repository)
375
375
376 pull: always pull from source repository, even in local case or if the
376 pull: always pull from source repository, even in local case or if the
377 server prefers streaming
377 server prefers streaming
378
378
379 stream: stream raw data uncompressed from repository (fast over
379 stream: stream raw data uncompressed from repository (fast over
380 LAN, slow over WAN)
380 LAN, slow over WAN)
381
381
382 rev: revision to clone up to (implies pull=True)
382 rev: revision to clone up to (implies pull=True)
383
383
384 update: update working directory after clone completes, if
384 update: update working directory after clone completes, if
385 destination is local repository (True means update to default rev,
385 destination is local repository (True means update to default rev,
386 anything else is treated as a revision)
386 anything else is treated as a revision)
387
387
388 branch: branches to clone
388 branch: branches to clone
389
389
390 shareopts: dict of options to control auto sharing behavior. The "pool" key
390 shareopts: dict of options to control auto sharing behavior. The "pool" key
391 activates auto sharing mode and defines the directory for stores. The
391 activates auto sharing mode and defines the directory for stores. The
392 "mode" key determines how to construct the directory name of the shared
392 "mode" key determines how to construct the directory name of the shared
393 repository. "identity" means the name is derived from the node of the first
393 repository. "identity" means the name is derived from the node of the first
394 changeset in the repository. "remote" means the name is derived from the
394 changeset in the repository. "remote" means the name is derived from the
395 remote's path/URL. Defaults to "identity."
395 remote's path/URL. Defaults to "identity."
396 """
396 """
397
397
398 if isinstance(source, str):
398 if isinstance(source, str):
399 origsource = ui.expandpath(source)
399 origsource = ui.expandpath(source)
400 source, branch = parseurl(origsource, branch)
400 source, branch = parseurl(origsource, branch)
401 srcpeer = peer(ui, peeropts, source)
401 srcpeer = peer(ui, peeropts, source)
402 else:
402 else:
403 srcpeer = source.peer() # in case we were called with a localrepo
403 srcpeer = source.peer() # in case we were called with a localrepo
404 branch = (None, branch or [])
404 branch = (None, branch or [])
405 origsource = source = srcpeer.url()
405 origsource = source = srcpeer.url()
406 rev, checkout = addbranchrevs(srcpeer, srcpeer, branch, rev)
406 rev, checkout = addbranchrevs(srcpeer, srcpeer, branch, rev)
407
407
408 if dest is None:
408 if dest is None:
409 dest = defaultdest(source)
409 dest = defaultdest(source)
410 if dest:
410 if dest:
411 ui.status(_("destination directory: %s\n") % dest)
411 ui.status(_("destination directory: %s\n") % dest)
412 else:
412 else:
413 dest = ui.expandpath(dest)
413 dest = ui.expandpath(dest)
414
414
415 dest = util.urllocalpath(dest)
415 dest = util.urllocalpath(dest)
416 source = util.urllocalpath(source)
416 source = util.urllocalpath(source)
417
417
418 if not dest:
418 if not dest:
419 raise util.Abort(_("empty destination path is not valid"))
419 raise util.Abort(_("empty destination path is not valid"))
420
420
421 destvfs = scmutil.vfs(dest, expandpath=True)
421 destvfs = scmutil.vfs(dest, expandpath=True)
422 if destvfs.lexists():
422 if destvfs.lexists():
423 if not destvfs.isdir():
423 if not destvfs.isdir():
424 raise util.Abort(_("destination '%s' already exists") % dest)
424 raise util.Abort(_("destination '%s' already exists") % dest)
425 elif destvfs.listdir():
425 elif destvfs.listdir():
426 raise util.Abort(_("destination '%s' is not empty") % dest)
426 raise util.Abort(_("destination '%s' is not empty") % dest)
427
427
428 shareopts = shareopts or {}
428 shareopts = shareopts or {}
429 sharepool = shareopts.get('pool')
429 sharepool = shareopts.get('pool')
430 sharenamemode = shareopts.get('mode')
430 sharenamemode = shareopts.get('mode')
431 if sharepool and islocal(dest):
431 if sharepool and islocal(dest):
432 sharepath = None
432 sharepath = None
433 if sharenamemode == 'identity':
433 if sharenamemode == 'identity':
434 # Resolve the name from the initial changeset in the remote
434 # Resolve the name from the initial changeset in the remote
435 # repository. This returns nullid when the remote is empty. It
435 # repository. This returns nullid when the remote is empty. It
436 # raises RepoLookupError if revision 0 is filtered or otherwise
436 # raises RepoLookupError if revision 0 is filtered or otherwise
437 # not available. If we fail to resolve, sharing is not enabled.
437 # not available. If we fail to resolve, sharing is not enabled.
438 try:
438 try:
439 rootnode = srcpeer.lookup('0')
439 rootnode = srcpeer.lookup('0')
440 if rootnode != node.nullid:
440 if rootnode != node.nullid:
441 sharepath = os.path.join(sharepool, node.hex(rootnode))
441 sharepath = os.path.join(sharepool, node.hex(rootnode))
442 else:
442 else:
443 ui.status(_('(not using pooled storage: '
443 ui.status(_('(not using pooled storage: '
444 'remote appears to be empty)\n'))
444 'remote appears to be empty)\n'))
445 except error.RepoLookupError:
445 except error.RepoLookupError:
446 ui.status(_('(not using pooled storage: '
446 ui.status(_('(not using pooled storage: '
447 'unable to resolve identity of remote)\n'))
447 'unable to resolve identity of remote)\n'))
448 elif sharenamemode == 'remote':
448 elif sharenamemode == 'remote':
449 sharepath = os.path.join(sharepool, util.sha1(source).hexdigest())
449 sharepath = os.path.join(sharepool, util.sha1(source).hexdigest())
450 else:
450 else:
451 raise util.Abort('unknown share naming mode: %s' % sharenamemode)
451 raise util.Abort('unknown share naming mode: %s' % sharenamemode)
452
452
453 if sharepath:
453 if sharepath:
454 return clonewithshare(ui, peeropts, sharepath, source, srcpeer,
454 return clonewithshare(ui, peeropts, sharepath, source, srcpeer,
455 dest, pull=pull, rev=rev, update=update,
455 dest, pull=pull, rev=rev, update=update,
456 stream=stream)
456 stream=stream)
457
457
458 srclock = destlock = cleandir = None
458 srclock = destlock = cleandir = None
459 srcrepo = srcpeer.local()
459 srcrepo = srcpeer.local()
460 try:
460 try:
461 abspath = origsource
461 abspath = origsource
462 if islocal(origsource):
462 if islocal(origsource):
463 abspath = os.path.abspath(util.urllocalpath(origsource))
463 abspath = os.path.abspath(util.urllocalpath(origsource))
464
464
465 if islocal(dest):
465 if islocal(dest):
466 cleandir = dest
466 cleandir = dest
467
467
468 copy = False
468 copy = False
469 if (srcrepo and srcrepo.cancopy() and islocal(dest)
469 if (srcrepo and srcrepo.cancopy() and islocal(dest)
470 and not phases.hassecret(srcrepo)):
470 and not phases.hassecret(srcrepo)):
471 copy = not pull and not rev
471 copy = not pull and not rev
472
472
473 if copy:
473 if copy:
474 try:
474 try:
475 # we use a lock here because if we race with commit, we
475 # we use a lock here because if we race with commit, we
476 # can end up with extra data in the cloned revlogs that's
476 # can end up with extra data in the cloned revlogs that's
477 # not pointed to by changesets, thus causing verify to
477 # not pointed to by changesets, thus causing verify to
478 # fail
478 # fail
479 srclock = srcrepo.lock(wait=False)
479 srclock = srcrepo.lock(wait=False)
480 except error.LockError:
480 except error.LockError:
481 copy = False
481 copy = False
482
482
483 if copy:
483 if copy:
484 srcrepo.hook('preoutgoing', throw=True, source='clone')
484 srcrepo.hook('preoutgoing', throw=True, source='clone')
485 hgdir = os.path.realpath(os.path.join(dest, ".hg"))
485 hgdir = os.path.realpath(os.path.join(dest, ".hg"))
486 if not os.path.exists(dest):
486 if not os.path.exists(dest):
487 os.mkdir(dest)
487 os.mkdir(dest)
488 else:
488 else:
489 # only clean up directories we create ourselves
489 # only clean up directories we create ourselves
490 cleandir = hgdir
490 cleandir = hgdir
491 try:
491 try:
492 destpath = hgdir
492 destpath = hgdir
493 util.makedir(destpath, notindexed=True)
493 util.makedir(destpath, notindexed=True)
494 except OSError as inst:
494 except OSError as inst:
495 if inst.errno == errno.EEXIST:
495 if inst.errno == errno.EEXIST:
496 cleandir = None
496 cleandir = None
497 raise util.Abort(_("destination '%s' already exists")
497 raise util.Abort(_("destination '%s' already exists")
498 % dest)
498 % dest)
499 raise
499 raise
500
500
501 destlock = copystore(ui, srcrepo, destpath)
501 destlock = copystore(ui, srcrepo, destpath)
502 # copy bookmarks over
502 # copy bookmarks over
503 srcbookmarks = srcrepo.join('bookmarks')
503 srcbookmarks = srcrepo.join('bookmarks')
504 dstbookmarks = os.path.join(destpath, 'bookmarks')
504 dstbookmarks = os.path.join(destpath, 'bookmarks')
505 if os.path.exists(srcbookmarks):
505 if os.path.exists(srcbookmarks):
506 util.copyfile(srcbookmarks, dstbookmarks)
506 util.copyfile(srcbookmarks, dstbookmarks)
507
507
508 # Recomputing branch cache might be slow on big repos,
508 # Recomputing branch cache might be slow on big repos,
509 # so just copy it
509 # so just copy it
510 def copybranchcache(fname):
510 def copybranchcache(fname):
511 srcbranchcache = srcrepo.join('cache/%s' % fname)
511 srcbranchcache = srcrepo.join('cache/%s' % fname)
512 dstbranchcache = os.path.join(dstcachedir, fname)
512 dstbranchcache = os.path.join(dstcachedir, fname)
513 if os.path.exists(srcbranchcache):
513 if os.path.exists(srcbranchcache):
514 if not os.path.exists(dstcachedir):
514 if not os.path.exists(dstcachedir):
515 os.mkdir(dstcachedir)
515 os.mkdir(dstcachedir)
516 util.copyfile(srcbranchcache, dstbranchcache)
516 util.copyfile(srcbranchcache, dstbranchcache)
517
517
518 dstcachedir = os.path.join(destpath, 'cache')
518 dstcachedir = os.path.join(destpath, 'cache')
519 # In local clones we're copying all nodes, not just served
519 # In local clones we're copying all nodes, not just served
520 # ones. Therefore copy all branch caches over.
520 # ones. Therefore copy all branch caches over.
521 copybranchcache('branch2')
521 copybranchcache('branch2')
522 for cachename in repoview.filtertable:
522 for cachename in repoview.filtertable:
523 copybranchcache('branch2-%s' % cachename)
523 copybranchcache('branch2-%s' % cachename)
524
524
525 # we need to re-init the repo after manually copying the data
525 # we need to re-init the repo after manually copying the data
526 # into it
526 # into it
527 destpeer = peer(srcrepo, peeropts, dest)
527 destpeer = peer(srcrepo, peeropts, dest)
528 srcrepo.hook('outgoing', source='clone',
528 srcrepo.hook('outgoing', source='clone',
529 node=node.hex(node.nullid))
529 node=node.hex(node.nullid))
530 else:
530 else:
531 try:
531 try:
532 destpeer = peer(srcrepo or ui, peeropts, dest, create=True)
532 destpeer = peer(srcrepo or ui, peeropts, dest, create=True)
533 # only pass ui when no srcrepo
533 # only pass ui when no srcrepo
534 except OSError as inst:
534 except OSError as inst:
535 if inst.errno == errno.EEXIST:
535 if inst.errno == errno.EEXIST:
536 cleandir = None
536 cleandir = None
537 raise util.Abort(_("destination '%s' already exists")
537 raise util.Abort(_("destination '%s' already exists")
538 % dest)
538 % dest)
539 raise
539 raise
540
540
541 revs = None
541 revs = None
542 if rev:
542 if rev:
543 if not srcpeer.capable('lookup'):
543 if not srcpeer.capable('lookup'):
544 raise util.Abort(_("src repository does not support "
544 raise util.Abort(_("src repository does not support "
545 "revision lookup and so doesn't "
545 "revision lookup and so doesn't "
546 "support clone by revision"))
546 "support clone by revision"))
547 revs = [srcpeer.lookup(r) for r in rev]
547 revs = [srcpeer.lookup(r) for r in rev]
548 checkout = revs[0]
548 checkout = revs[0]
549 if destpeer.local():
549 if destpeer.local():
550 if not stream:
550 if not stream:
551 if pull:
551 if pull:
552 stream = False
552 stream = False
553 else:
553 else:
554 stream = None
554 stream = None
555 destpeer.local().clone(srcpeer, heads=revs, stream=stream)
555 destpeer.local().clone(srcpeer, heads=revs, stream=stream)
556 elif srcrepo:
556 elif srcrepo:
557 exchange.push(srcrepo, destpeer, revs=revs,
557 exchange.push(srcrepo, destpeer, revs=revs,
558 bookmarks=srcrepo._bookmarks.keys())
558 bookmarks=srcrepo._bookmarks.keys())
559 else:
559 else:
560 raise util.Abort(_("clone from remote to remote not supported"))
560 raise util.Abort(_("clone from remote to remote not supported"))
561
561
562 cleandir = None
562 cleandir = None
563
563
564 destrepo = destpeer.local()
564 destrepo = destpeer.local()
565 if destrepo:
565 if destrepo:
566 template = uimod.samplehgrcs['cloned']
566 template = uimod.samplehgrcs['cloned']
567 fp = destrepo.vfs("hgrc", "w", text=True)
567 fp = destrepo.vfs("hgrc", "w", text=True)
568 u = util.url(abspath)
568 u = util.url(abspath)
569 u.passwd = None
569 u.passwd = None
570 defaulturl = str(u)
570 defaulturl = str(u)
571 fp.write(template % defaulturl)
571 fp.write(template % defaulturl)
572 fp.close()
572 fp.close()
573
573
574 destrepo.ui.setconfig('paths', 'default', defaulturl, 'clone')
574 destrepo.ui.setconfig('paths', 'default', defaulturl, 'clone')
575
575
576 if update:
576 if update:
577 if update is not True:
577 if update is not True:
578 checkout = srcpeer.lookup(update)
578 checkout = srcpeer.lookup(update)
579 uprev = None
579 uprev = None
580 status = None
580 status = None
581 if checkout is not None:
581 if checkout is not None:
582 try:
582 try:
583 uprev = destrepo.lookup(checkout)
583 uprev = destrepo.lookup(checkout)
584 except error.RepoLookupError:
584 except error.RepoLookupError:
585 try:
585 if update is not True:
586 uprev = destrepo.lookup(update)
586 try:
587 except error.RepoLookupError:
587 uprev = destrepo.lookup(update)
588 pass
588 except error.RepoLookupError:
589 pass
589 if uprev is None:
590 if uprev is None:
590 try:
591 try:
591 uprev = destrepo._bookmarks['@']
592 uprev = destrepo._bookmarks['@']
592 update = '@'
593 update = '@'
593 bn = destrepo[uprev].branch()
594 bn = destrepo[uprev].branch()
594 if bn == 'default':
595 if bn == 'default':
595 status = _("updating to bookmark @\n")
596 status = _("updating to bookmark @\n")
596 else:
597 else:
597 status = (_("updating to bookmark @ on branch %s\n")
598 status = (_("updating to bookmark @ on branch %s\n")
598 % bn)
599 % bn)
599 except KeyError:
600 except KeyError:
600 try:
601 try:
601 uprev = destrepo.branchtip('default')
602 uprev = destrepo.branchtip('default')
602 except error.RepoLookupError:
603 except error.RepoLookupError:
603 uprev = destrepo.lookup('tip')
604 uprev = destrepo.lookup('tip')
604 if not status:
605 if not status:
605 bn = destrepo[uprev].branch()
606 bn = destrepo[uprev].branch()
606 status = _("updating to branch %s\n") % bn
607 status = _("updating to branch %s\n") % bn
607 destrepo.ui.status(status)
608 destrepo.ui.status(status)
608 _update(destrepo, uprev)
609 _update(destrepo, uprev)
609 if update in destrepo._bookmarks:
610 if update in destrepo._bookmarks:
610 bookmarks.activate(destrepo, update)
611 bookmarks.activate(destrepo, update)
611 finally:
612 finally:
612 release(srclock, destlock)
613 release(srclock, destlock)
613 if cleandir is not None:
614 if cleandir is not None:
614 shutil.rmtree(cleandir, True)
615 shutil.rmtree(cleandir, True)
615 if srcpeer is not None:
616 if srcpeer is not None:
616 srcpeer.close()
617 srcpeer.close()
617 return srcpeer, destpeer
618 return srcpeer, destpeer
618
619
619 def _showstats(repo, stats):
620 def _showstats(repo, stats):
620 repo.ui.status(_("%d files updated, %d files merged, "
621 repo.ui.status(_("%d files updated, %d files merged, "
621 "%d files removed, %d files unresolved\n") % stats)
622 "%d files removed, %d files unresolved\n") % stats)
622
623
623 def updaterepo(repo, node, overwrite):
624 def updaterepo(repo, node, overwrite):
624 """Update the working directory to node.
625 """Update the working directory to node.
625
626
626 When overwrite is set, changes are clobbered, merged else
627 When overwrite is set, changes are clobbered, merged else
627
628
628 returns stats (see pydoc mercurial.merge.applyupdates)"""
629 returns stats (see pydoc mercurial.merge.applyupdates)"""
629 return mergemod.update(repo, node, False, overwrite, None,
630 return mergemod.update(repo, node, False, overwrite, None,
630 labels=['working copy', 'destination'])
631 labels=['working copy', 'destination'])
631
632
632 def update(repo, node):
633 def update(repo, node):
633 """update the working directory to node, merging linear changes"""
634 """update the working directory to node, merging linear changes"""
634 stats = updaterepo(repo, node, False)
635 stats = updaterepo(repo, node, False)
635 _showstats(repo, stats)
636 _showstats(repo, stats)
636 if stats[3]:
637 if stats[3]:
637 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n"))
638 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n"))
638 return stats[3] > 0
639 return stats[3] > 0
639
640
640 # naming conflict in clone()
641 # naming conflict in clone()
641 _update = update
642 _update = update
642
643
643 def clean(repo, node, show_stats=True):
644 def clean(repo, node, show_stats=True):
644 """forcibly switch the working directory to node, clobbering changes"""
645 """forcibly switch the working directory to node, clobbering changes"""
645 stats = updaterepo(repo, node, True)
646 stats = updaterepo(repo, node, True)
646 util.unlinkpath(repo.join('graftstate'), ignoremissing=True)
647 util.unlinkpath(repo.join('graftstate'), ignoremissing=True)
647 if show_stats:
648 if show_stats:
648 _showstats(repo, stats)
649 _showstats(repo, stats)
649 return stats[3] > 0
650 return stats[3] > 0
650
651
651 def merge(repo, node, force=None, remind=True):
652 def merge(repo, node, force=None, remind=True):
652 """Branch merge with node, resolving changes. Return true if any
653 """Branch merge with node, resolving changes. Return true if any
653 unresolved conflicts."""
654 unresolved conflicts."""
654 stats = mergemod.update(repo, node, True, force, False)
655 stats = mergemod.update(repo, node, True, force, False)
655 _showstats(repo, stats)
656 _showstats(repo, stats)
656 if stats[3]:
657 if stats[3]:
657 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
658 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
658 "or 'hg update -C .' to abandon\n"))
659 "or 'hg update -C .' to abandon\n"))
659 elif remind:
660 elif remind:
660 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
661 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
661 return stats[3] > 0
662 return stats[3] > 0
662
663
663 def _incoming(displaychlist, subreporecurse, ui, repo, source,
664 def _incoming(displaychlist, subreporecurse, ui, repo, source,
664 opts, buffered=False):
665 opts, buffered=False):
665 """
666 """
666 Helper for incoming / gincoming.
667 Helper for incoming / gincoming.
667 displaychlist gets called with
668 displaychlist gets called with
668 (remoterepo, incomingchangesetlist, displayer) parameters,
669 (remoterepo, incomingchangesetlist, displayer) parameters,
669 and is supposed to contain only code that can't be unified.
670 and is supposed to contain only code that can't be unified.
670 """
671 """
671 source, branches = parseurl(ui.expandpath(source), opts.get('branch'))
672 source, branches = parseurl(ui.expandpath(source), opts.get('branch'))
672 other = peer(repo, opts, source)
673 other = peer(repo, opts, source)
673 ui.status(_('comparing with %s\n') % util.hidepassword(source))
674 ui.status(_('comparing with %s\n') % util.hidepassword(source))
674 revs, checkout = addbranchrevs(repo, other, branches, opts.get('rev'))
675 revs, checkout = addbranchrevs(repo, other, branches, opts.get('rev'))
675
676
676 if revs:
677 if revs:
677 revs = [other.lookup(rev) for rev in revs]
678 revs = [other.lookup(rev) for rev in revs]
678 other, chlist, cleanupfn = bundlerepo.getremotechanges(ui, repo, other,
679 other, chlist, cleanupfn = bundlerepo.getremotechanges(ui, repo, other,
679 revs, opts["bundle"], opts["force"])
680 revs, opts["bundle"], opts["force"])
680 try:
681 try:
681 if not chlist:
682 if not chlist:
682 ui.status(_("no changes found\n"))
683 ui.status(_("no changes found\n"))
683 return subreporecurse()
684 return subreporecurse()
684
685
685 displayer = cmdutil.show_changeset(ui, other, opts, buffered)
686 displayer = cmdutil.show_changeset(ui, other, opts, buffered)
686 displaychlist(other, chlist, displayer)
687 displaychlist(other, chlist, displayer)
687 displayer.close()
688 displayer.close()
688 finally:
689 finally:
689 cleanupfn()
690 cleanupfn()
690 subreporecurse()
691 subreporecurse()
691 return 0 # exit code is zero since we found incoming changes
692 return 0 # exit code is zero since we found incoming changes
692
693
693 def incoming(ui, repo, source, opts):
694 def incoming(ui, repo, source, opts):
694 def subreporecurse():
695 def subreporecurse():
695 ret = 1
696 ret = 1
696 if opts.get('subrepos'):
697 if opts.get('subrepos'):
697 ctx = repo[None]
698 ctx = repo[None]
698 for subpath in sorted(ctx.substate):
699 for subpath in sorted(ctx.substate):
699 sub = ctx.sub(subpath)
700 sub = ctx.sub(subpath)
700 ret = min(ret, sub.incoming(ui, source, opts))
701 ret = min(ret, sub.incoming(ui, source, opts))
701 return ret
702 return ret
702
703
703 def display(other, chlist, displayer):
704 def display(other, chlist, displayer):
704 limit = cmdutil.loglimit(opts)
705 limit = cmdutil.loglimit(opts)
705 if opts.get('newest_first'):
706 if opts.get('newest_first'):
706 chlist.reverse()
707 chlist.reverse()
707 count = 0
708 count = 0
708 for n in chlist:
709 for n in chlist:
709 if limit is not None and count >= limit:
710 if limit is not None and count >= limit:
710 break
711 break
711 parents = [p for p in other.changelog.parents(n) if p != nullid]
712 parents = [p for p in other.changelog.parents(n) if p != nullid]
712 if opts.get('no_merges') and len(parents) == 2:
713 if opts.get('no_merges') and len(parents) == 2:
713 continue
714 continue
714 count += 1
715 count += 1
715 displayer.show(other[n])
716 displayer.show(other[n])
716 return _incoming(display, subreporecurse, ui, repo, source, opts)
717 return _incoming(display, subreporecurse, ui, repo, source, opts)
717
718
718 def _outgoing(ui, repo, dest, opts):
719 def _outgoing(ui, repo, dest, opts):
719 dest = ui.expandpath(dest or 'default-push', dest or 'default')
720 dest = ui.expandpath(dest or 'default-push', dest or 'default')
720 dest, branches = parseurl(dest, opts.get('branch'))
721 dest, branches = parseurl(dest, opts.get('branch'))
721 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
722 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
722 revs, checkout = addbranchrevs(repo, repo, branches, opts.get('rev'))
723 revs, checkout = addbranchrevs(repo, repo, branches, opts.get('rev'))
723 if revs:
724 if revs:
724 revs = [repo.lookup(rev) for rev in scmutil.revrange(repo, revs)]
725 revs = [repo.lookup(rev) for rev in scmutil.revrange(repo, revs)]
725
726
726 other = peer(repo, opts, dest)
727 other = peer(repo, opts, dest)
727 outgoing = discovery.findcommonoutgoing(repo.unfiltered(), other, revs,
728 outgoing = discovery.findcommonoutgoing(repo.unfiltered(), other, revs,
728 force=opts.get('force'))
729 force=opts.get('force'))
729 o = outgoing.missing
730 o = outgoing.missing
730 if not o:
731 if not o:
731 scmutil.nochangesfound(repo.ui, repo, outgoing.excluded)
732 scmutil.nochangesfound(repo.ui, repo, outgoing.excluded)
732 return o, other
733 return o, other
733
734
734 def outgoing(ui, repo, dest, opts):
735 def outgoing(ui, repo, dest, opts):
735 def recurse():
736 def recurse():
736 ret = 1
737 ret = 1
737 if opts.get('subrepos'):
738 if opts.get('subrepos'):
738 ctx = repo[None]
739 ctx = repo[None]
739 for subpath in sorted(ctx.substate):
740 for subpath in sorted(ctx.substate):
740 sub = ctx.sub(subpath)
741 sub = ctx.sub(subpath)
741 ret = min(ret, sub.outgoing(ui, dest, opts))
742 ret = min(ret, sub.outgoing(ui, dest, opts))
742 return ret
743 return ret
743
744
744 limit = cmdutil.loglimit(opts)
745 limit = cmdutil.loglimit(opts)
745 o, other = _outgoing(ui, repo, dest, opts)
746 o, other = _outgoing(ui, repo, dest, opts)
746 if not o:
747 if not o:
747 cmdutil.outgoinghooks(ui, repo, other, opts, o)
748 cmdutil.outgoinghooks(ui, repo, other, opts, o)
748 return recurse()
749 return recurse()
749
750
750 if opts.get('newest_first'):
751 if opts.get('newest_first'):
751 o.reverse()
752 o.reverse()
752 displayer = cmdutil.show_changeset(ui, repo, opts)
753 displayer = cmdutil.show_changeset(ui, repo, opts)
753 count = 0
754 count = 0
754 for n in o:
755 for n in o:
755 if limit is not None and count >= limit:
756 if limit is not None and count >= limit:
756 break
757 break
757 parents = [p for p in repo.changelog.parents(n) if p != nullid]
758 parents = [p for p in repo.changelog.parents(n) if p != nullid]
758 if opts.get('no_merges') and len(parents) == 2:
759 if opts.get('no_merges') and len(parents) == 2:
759 continue
760 continue
760 count += 1
761 count += 1
761 displayer.show(repo[n])
762 displayer.show(repo[n])
762 displayer.close()
763 displayer.close()
763 cmdutil.outgoinghooks(ui, repo, other, opts, o)
764 cmdutil.outgoinghooks(ui, repo, other, opts, o)
764 recurse()
765 recurse()
765 return 0 # exit code is zero since we found outgoing changes
766 return 0 # exit code is zero since we found outgoing changes
766
767
767 def revert(repo, node, choose):
768 def revert(repo, node, choose):
768 """revert changes to revision in node without updating dirstate"""
769 """revert changes to revision in node without updating dirstate"""
769 return mergemod.update(repo, node, False, True, choose)[3] > 0
770 return mergemod.update(repo, node, False, True, choose)[3] > 0
770
771
771 def verify(repo):
772 def verify(repo):
772 """verify the consistency of a repository"""
773 """verify the consistency of a repository"""
773 ret = verifymod.verify(repo)
774 ret = verifymod.verify(repo)
774
775
775 # Broken subrepo references in hidden csets don't seem worth worrying about,
776 # Broken subrepo references in hidden csets don't seem worth worrying about,
776 # since they can't be pushed/pulled, and --hidden can be used if they are a
777 # since they can't be pushed/pulled, and --hidden can be used if they are a
777 # concern.
778 # concern.
778
779
779 # pathto() is needed for -R case
780 # pathto() is needed for -R case
780 revs = repo.revs("filelog(%s)",
781 revs = repo.revs("filelog(%s)",
781 util.pathto(repo.root, repo.getcwd(), '.hgsubstate'))
782 util.pathto(repo.root, repo.getcwd(), '.hgsubstate'))
782
783
783 if revs:
784 if revs:
784 repo.ui.status(_('checking subrepo links\n'))
785 repo.ui.status(_('checking subrepo links\n'))
785 for rev in revs:
786 for rev in revs:
786 ctx = repo[rev]
787 ctx = repo[rev]
787 try:
788 try:
788 for subpath in ctx.substate:
789 for subpath in ctx.substate:
789 ret = ctx.sub(subpath).verify() or ret
790 ret = ctx.sub(subpath).verify() or ret
790 except Exception:
791 except Exception:
791 repo.ui.warn(_('.hgsubstate is corrupt in revision %s\n') %
792 repo.ui.warn(_('.hgsubstate is corrupt in revision %s\n') %
792 node.short(ctx.node()))
793 node.short(ctx.node()))
793
794
794 return ret
795 return ret
795
796
796 def remoteui(src, opts):
797 def remoteui(src, opts):
797 'build a remote ui from ui or repo and opts'
798 'build a remote ui from ui or repo and opts'
798 if util.safehasattr(src, 'baseui'): # looks like a repository
799 if util.safehasattr(src, 'baseui'): # looks like a repository
799 dst = src.baseui.copy() # drop repo-specific config
800 dst = src.baseui.copy() # drop repo-specific config
800 src = src.ui # copy target options from repo
801 src = src.ui # copy target options from repo
801 else: # assume it's a global ui object
802 else: # assume it's a global ui object
802 dst = src.copy() # keep all global options
803 dst = src.copy() # keep all global options
803
804
804 # copy ssh-specific options
805 # copy ssh-specific options
805 for o in 'ssh', 'remotecmd':
806 for o in 'ssh', 'remotecmd':
806 v = opts.get(o) or src.config('ui', o)
807 v = opts.get(o) or src.config('ui', o)
807 if v:
808 if v:
808 dst.setconfig("ui", o, v, 'copied')
809 dst.setconfig("ui", o, v, 'copied')
809
810
810 # copy bundle-specific options
811 # copy bundle-specific options
811 r = src.config('bundle', 'mainreporoot')
812 r = src.config('bundle', 'mainreporoot')
812 if r:
813 if r:
813 dst.setconfig('bundle', 'mainreporoot', r, 'copied')
814 dst.setconfig('bundle', 'mainreporoot', r, 'copied')
814
815
815 # copy selected local settings to the remote ui
816 # copy selected local settings to the remote ui
816 for sect in ('auth', 'hostfingerprints', 'http_proxy'):
817 for sect in ('auth', 'hostfingerprints', 'http_proxy'):
817 for key, val in src.configitems(sect):
818 for key, val in src.configitems(sect):
818 dst.setconfig(sect, key, val, 'copied')
819 dst.setconfig(sect, key, val, 'copied')
819 v = src.config('web', 'cacerts')
820 v = src.config('web', 'cacerts')
820 if v == '!':
821 if v == '!':
821 dst.setconfig('web', 'cacerts', v, 'copied')
822 dst.setconfig('web', 'cacerts', v, 'copied')
822 elif v:
823 elif v:
823 dst.setconfig('web', 'cacerts', util.expandpath(v), 'copied')
824 dst.setconfig('web', 'cacerts', util.expandpath(v), 'copied')
824
825
825 return dst
826 return dst
826
827
827 # Files of interest
828 # Files of interest
828 # Used to check if the repository has changed looking at mtime and size of
829 # Used to check if the repository has changed looking at mtime and size of
829 # theses files.
830 # theses files.
830 foi = [('spath', '00changelog.i'),
831 foi = [('spath', '00changelog.i'),
831 ('spath', 'phaseroots'), # ! phase can change content at the same size
832 ('spath', 'phaseroots'), # ! phase can change content at the same size
832 ('spath', 'obsstore'),
833 ('spath', 'obsstore'),
833 ('path', 'bookmarks'), # ! bookmark can change content at the same size
834 ('path', 'bookmarks'), # ! bookmark can change content at the same size
834 ]
835 ]
835
836
836 class cachedlocalrepo(object):
837 class cachedlocalrepo(object):
837 """Holds a localrepository that can be cached and reused."""
838 """Holds a localrepository that can be cached and reused."""
838
839
839 def __init__(self, repo):
840 def __init__(self, repo):
840 """Create a new cached repo from an existing repo.
841 """Create a new cached repo from an existing repo.
841
842
842 We assume the passed in repo was recently created. If the
843 We assume the passed in repo was recently created. If the
843 repo has changed between when it was created and when it was
844 repo has changed between when it was created and when it was
844 turned into a cache, it may not refresh properly.
845 turned into a cache, it may not refresh properly.
845 """
846 """
846 assert isinstance(repo, localrepo.localrepository)
847 assert isinstance(repo, localrepo.localrepository)
847 self._repo = repo
848 self._repo = repo
848 self._state, self.mtime = self._repostate()
849 self._state, self.mtime = self._repostate()
849
850
850 def fetch(self):
851 def fetch(self):
851 """Refresh (if necessary) and return a repository.
852 """Refresh (if necessary) and return a repository.
852
853
853 If the cached instance is out of date, it will be recreated
854 If the cached instance is out of date, it will be recreated
854 automatically and returned.
855 automatically and returned.
855
856
856 Returns a tuple of the repo and a boolean indicating whether a new
857 Returns a tuple of the repo and a boolean indicating whether a new
857 repo instance was created.
858 repo instance was created.
858 """
859 """
859 # We compare the mtimes and sizes of some well-known files to
860 # We compare the mtimes and sizes of some well-known files to
860 # determine if the repo changed. This is not precise, as mtimes
861 # determine if the repo changed. This is not precise, as mtimes
861 # are susceptible to clock skew and imprecise filesystems and
862 # are susceptible to clock skew and imprecise filesystems and
862 # file content can change while maintaining the same size.
863 # file content can change while maintaining the same size.
863
864
864 state, mtime = self._repostate()
865 state, mtime = self._repostate()
865 if state == self._state:
866 if state == self._state:
866 return self._repo, False
867 return self._repo, False
867
868
868 self._repo = repository(self._repo.baseui, self._repo.url())
869 self._repo = repository(self._repo.baseui, self._repo.url())
869 self._state = state
870 self._state = state
870 self.mtime = mtime
871 self.mtime = mtime
871
872
872 return self._repo, True
873 return self._repo, True
873
874
874 def _repostate(self):
875 def _repostate(self):
875 state = []
876 state = []
876 maxmtime = -1
877 maxmtime = -1
877 for attr, fname in foi:
878 for attr, fname in foi:
878 prefix = getattr(self._repo, attr)
879 prefix = getattr(self._repo, attr)
879 p = os.path.join(prefix, fname)
880 p = os.path.join(prefix, fname)
880 try:
881 try:
881 st = os.stat(p)
882 st = os.stat(p)
882 except OSError:
883 except OSError:
883 st = os.stat(prefix)
884 st = os.stat(prefix)
884 state.append((st.st_mtime, st.st_size))
885 state.append((st.st_mtime, st.st_size))
885 maxmtime = max(maxmtime, st.st_mtime)
886 maxmtime = max(maxmtime, st.st_mtime)
886
887
887 return tuple(state), maxmtime
888 return tuple(state), maxmtime
888
889
889 def copy(self):
890 def copy(self):
890 """Obtain a copy of this class instance.
891 """Obtain a copy of this class instance.
891
892
892 A new localrepository instance is obtained. The new instance should be
893 A new localrepository instance is obtained. The new instance should be
893 completely independent of the original.
894 completely independent of the original.
894 """
895 """
895 repo = repository(self._repo.baseui, self._repo.origroot)
896 repo = repository(self._repo.baseui, self._repo.origroot)
896 c = cachedlocalrepo(repo)
897 c = cachedlocalrepo(repo)
897 c._state = self._state
898 c._state = self._state
898 c.mtime = self.mtime
899 c.mtime = self.mtime
899 return c
900 return c
General Comments 0
You need to be logged in to leave comments. Login now