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