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