##// END OF EJS Templates
share: add option to share bookmarks...
Ryan McElroy -
r23614:cd79fb4d default
parent child Browse files
Show More
@@ -1,129 +1,130 b''
1 1 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
2 2 #
3 3 # This software may be used and distributed according to the terms of the
4 4 # GNU General Public License version 2 or any later version.
5 5
6 6 '''share a common history between several working directories'''
7 7
8 8 from mercurial.i18n import _
9 9 from mercurial import cmdutil, hg, util, extensions, bookmarks
10 10 from mercurial.hg import repository, parseurl
11 11 import errno
12 12
13 13 cmdtable = {}
14 14 command = cmdutil.command(cmdtable)
15 15 testedwith = 'internal'
16 16
17 17 @command('share',
18 [('U', 'noupdate', None, _('do not create a working copy'))],
19 _('[-U] SOURCE [DEST]'),
18 [('U', 'noupdate', None, _('do not create a working copy')),
19 ('B', 'bookmarks', None, _('also share bookmarks'))],
20 _('[-U] [-B] SOURCE [DEST]'),
20 21 norepo=True)
21 def share(ui, source, dest=None, noupdate=False):
22 def share(ui, source, dest=None, noupdate=False, bookmarks=False):
22 23 """create a new shared repository
23 24
24 25 Initialize a new repository and working directory that shares its
25 history with another repository.
26 history (and optionally bookmarks) with another repository.
26 27
27 28 .. note::
28 29
29 30 using rollback or extensions that destroy/modify history (mq,
30 31 rebase, etc.) can cause considerable confusion with shared
31 32 clones. In particular, if two shared clones are both updated to
32 33 the same changeset, and one of them destroys that changeset
33 34 with rollback, the other clone will suddenly stop working: all
34 35 operations will fail with "abort: working directory has unknown
35 36 parent". The only known workaround is to use debugsetparents on
36 37 the broken clone to reset it to a changeset that still exists.
37 38 """
38 39
39 return hg.share(ui, source, dest, not noupdate)
40 return hg.share(ui, source, dest, not noupdate, bookmarks)
40 41
41 42 @command('unshare', [], '')
42 43 def unshare(ui, repo):
43 44 """convert a shared repository to a normal one
44 45
45 46 Copy the store data to the repo and remove the sharedpath data.
46 47 """
47 48
48 49 if repo.sharedpath == repo.path:
49 50 raise util.Abort(_("this is not a shared repo"))
50 51
51 52 destlock = lock = None
52 53 lock = repo.lock()
53 54 try:
54 55 # we use locks here because if we race with commit, we
55 56 # can end up with extra data in the cloned revlogs that's
56 57 # not pointed to by changesets, thus causing verify to
57 58 # fail
58 59
59 60 destlock = hg.copystore(ui, repo, repo.path)
60 61
61 62 sharefile = repo.join('sharedpath')
62 63 util.rename(sharefile, sharefile + '.old')
63 64
64 65 repo.requirements.discard('sharedpath')
65 66 repo._writerequirements()
66 67 finally:
67 68 destlock and destlock.release()
68 69 lock and lock.release()
69 70
70 71 # update store, spath, sopener and sjoin of repo
71 72 repo.unfiltered().__init__(repo.baseui, repo.root)
72 73
73 74 def extsetup(ui):
74 75 extensions.wrapfunction(bookmarks.bmstore, 'getbkfile', getbkfile)
75 76 extensions.wrapfunction(bookmarks.bmstore, 'recordchange', recordchange)
76 77 extensions.wrapfunction(bookmarks.bmstore, 'write', write)
77 78
78 79 def _hassharedbookmarks(repo):
79 80 """Returns whether this repo has shared bookmarks"""
80 81 try:
81 82 repo.vfs.read('bookmarks.shared')
82 83 return True
83 84 except IOError, inst:
84 85 if inst.errno != errno.ENOENT:
85 86 raise
86 87 return False
87 88
88 89 def _getsrcrepo(repo):
89 90 """
90 91 Returns the source repository object for a given shared repository.
91 92 If repo is not a shared repository, return None.
92 93 """
93 94 srcrepo = None
94 95 try:
95 96 # strip because some tools write with newline after
96 97 sharedpath = repo.vfs.read('sharedpath').strip()
97 98 # the sharedpath always ends in the .hg; we want the path to the repo
98 99 source = sharedpath.rsplit('/.hg', 1)[0]
99 100 srcurl, branches = parseurl(source)
100 101 srcrepo = repository(repo.ui, srcurl)
101 102 except IOError, inst:
102 103 if inst.errno != errno.ENOENT:
103 104 raise
104 105 return srcrepo
105 106
106 107 def getbkfile(orig, self, repo):
107 108 if _hassharedbookmarks(repo):
108 109 srcrepo = _getsrcrepo(repo)
109 110 if srcrepo is not None:
110 111 repo = srcrepo
111 112 return orig(self, repo)
112 113
113 114 def recordchange(orig, self, tr):
114 115 # Continue with write to local bookmarks file as usual
115 116 orig(self, tr)
116 117
117 118 if _hassharedbookmarks(self._repo):
118 119 srcrepo = _getsrcrepo(self._repo)
119 120 if srcrepo is not None:
120 121 category = 'share-bookmarks'
121 122 tr.addpostclose(category, lambda tr: self._writerepo(srcrepo))
122 123
123 124 def write(orig, self):
124 125 # First write local bookmarks file in case we ever unshare
125 126 orig(self)
126 127 if _hassharedbookmarks(self._repo):
127 128 srcrepo = _getsrcrepo(self._repo)
128 129 if srcrepo is not None:
129 130 self._writerepo(srcrepo)
@@ -1,673 +1,676 b''
1 1 # hg.py - repository classes for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 from i18n import _
10 10 from lock import release
11 11 from node import nullid
12 12
13 13 import localrepo, bundlerepo, unionrepo, httppeer, sshpeer, statichttprepo
14 14 import bookmarks, lock, util, extensions, error, node, scmutil, phases, url
15 15 import cmdutil, discovery, repoview, exchange
16 16 import ui as uimod
17 17 import merge as mergemod
18 18 import verify as verifymod
19 19 import errno, os, shutil
20 20
21 21 def _local(path):
22 22 path = util.expandpath(util.urllocalpath(path))
23 23 return (os.path.isfile(path) and bundlerepo or localrepo)
24 24
25 25 def addbranchrevs(lrepo, other, branches, revs):
26 26 peer = other.peer() # a courtesy to callers using a localrepo for other
27 27 hashbranch, branches = branches
28 28 if not hashbranch and not branches:
29 29 x = revs or None
30 30 if util.safehasattr(revs, 'first'):
31 31 y = revs.first()
32 32 elif revs:
33 33 y = revs[0]
34 34 else:
35 35 y = None
36 36 return x, y
37 37 revs = revs and list(revs) or []
38 38 if not peer.capable('branchmap'):
39 39 if branches:
40 40 raise util.Abort(_("remote branch lookup not supported"))
41 41 revs.append(hashbranch)
42 42 return revs, revs[0]
43 43 branchmap = peer.branchmap()
44 44
45 45 def primary(branch):
46 46 if branch == '.':
47 47 if not lrepo:
48 48 raise util.Abort(_("dirstate branch not accessible"))
49 49 branch = lrepo.dirstate.branch()
50 50 if branch in branchmap:
51 51 revs.extend(node.hex(r) for r in reversed(branchmap[branch]))
52 52 return True
53 53 else:
54 54 return False
55 55
56 56 for branch in branches:
57 57 if not primary(branch):
58 58 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
59 59 if hashbranch:
60 60 if not primary(hashbranch):
61 61 revs.append(hashbranch)
62 62 return revs, revs[0]
63 63
64 64 def parseurl(path, branches=None):
65 65 '''parse url#branch, returning (url, (branch, branches))'''
66 66
67 67 u = util.url(path)
68 68 branch = None
69 69 if u.fragment:
70 70 branch = u.fragment
71 71 u.fragment = None
72 72 return str(u), (branch, branches or [])
73 73
74 74 schemes = {
75 75 'bundle': bundlerepo,
76 76 'union': unionrepo,
77 77 'file': _local,
78 78 'http': httppeer,
79 79 'https': httppeer,
80 80 'ssh': sshpeer,
81 81 'static-http': statichttprepo,
82 82 }
83 83
84 84 def _peerlookup(path):
85 85 u = util.url(path)
86 86 scheme = u.scheme or 'file'
87 87 thing = schemes.get(scheme) or schemes['file']
88 88 try:
89 89 return thing(path)
90 90 except TypeError:
91 91 return thing
92 92
93 93 def islocal(repo):
94 94 '''return true if repo (or path pointing to repo) is local'''
95 95 if isinstance(repo, str):
96 96 try:
97 97 return _peerlookup(repo).islocal(repo)
98 98 except AttributeError:
99 99 return False
100 100 return repo.local()
101 101
102 102 def openpath(ui, path):
103 103 '''open path with open if local, url.open if remote'''
104 104 pathurl = util.url(path, parsequery=False, parsefragment=False)
105 105 if pathurl.islocal():
106 106 return util.posixfile(pathurl.localpath(), 'rb')
107 107 else:
108 108 return url.open(ui, path)
109 109
110 110 # a list of (ui, repo) functions called for wire peer initialization
111 111 wirepeersetupfuncs = []
112 112
113 113 def _peerorrepo(ui, path, create=False):
114 114 """return a repository object for the specified path"""
115 115 obj = _peerlookup(path).instance(ui, path, create)
116 116 ui = getattr(obj, "ui", ui)
117 117 for name, module in extensions.extensions(ui):
118 118 hook = getattr(module, 'reposetup', None)
119 119 if hook:
120 120 hook(ui, obj)
121 121 if not obj.local():
122 122 for f in wirepeersetupfuncs:
123 123 f(ui, obj)
124 124 return obj
125 125
126 126 def repository(ui, path='', create=False):
127 127 """return a repository object for the specified path"""
128 128 peer = _peerorrepo(ui, path, create)
129 129 repo = peer.local()
130 130 if not repo:
131 131 raise util.Abort(_("repository '%s' is not local") %
132 132 (path or peer.url()))
133 133 return repo.filtered('visible')
134 134
135 135 def peer(uiorrepo, opts, path, create=False):
136 136 '''return a repository peer for the specified path'''
137 137 rui = remoteui(uiorrepo, opts)
138 138 return _peerorrepo(rui, path, create).peer()
139 139
140 140 def defaultdest(source):
141 141 '''return default destination of clone if none is given
142 142
143 143 >>> defaultdest('foo')
144 144 'foo'
145 145 >>> defaultdest('/foo/bar')
146 146 'bar'
147 147 >>> defaultdest('/')
148 148 ''
149 149 >>> defaultdest('')
150 150 ''
151 151 >>> defaultdest('http://example.org/')
152 152 ''
153 153 >>> defaultdest('http://example.org/foo/')
154 154 'foo'
155 155 '''
156 156 path = util.url(source).path
157 157 if not path:
158 158 return ''
159 159 return os.path.basename(os.path.normpath(path))
160 160
161 def share(ui, source, dest=None, update=True):
161 def share(ui, source, dest=None, update=True, bookmarks=True):
162 162 '''create a shared repository'''
163 163
164 164 if not islocal(source):
165 165 raise util.Abort(_('can only share local repositories'))
166 166
167 167 if not dest:
168 168 dest = defaultdest(source)
169 169 else:
170 170 dest = ui.expandpath(dest)
171 171
172 172 if isinstance(source, str):
173 173 origsource = ui.expandpath(source)
174 174 source, branches = parseurl(origsource)
175 175 srcrepo = repository(ui, source)
176 176 rev, checkout = addbranchrevs(srcrepo, srcrepo, branches, None)
177 177 else:
178 178 srcrepo = source.local()
179 179 origsource = source = srcrepo.url()
180 180 checkout = None
181 181
182 182 sharedpath = srcrepo.sharedpath # if our source is already sharing
183 183
184 184 destwvfs = scmutil.vfs(dest, realpath=True)
185 185 destvfs = scmutil.vfs(os.path.join(destwvfs.base, '.hg'), realpath=True)
186 186
187 187 if destvfs.lexists():
188 188 raise util.Abort(_('destination already exists'))
189 189
190 190 if not destwvfs.isdir():
191 191 destwvfs.mkdir()
192 192 destvfs.makedir()
193 193
194 194 requirements = ''
195 195 try:
196 196 requirements = srcrepo.opener.read('requires')
197 197 except IOError, inst:
198 198 if inst.errno != errno.ENOENT:
199 199 raise
200 200
201 201 requirements += 'shared\n'
202 202 destvfs.write('requires', requirements)
203 203 destvfs.write('sharedpath', sharedpath)
204 204
205 205 r = repository(ui, destwvfs.base)
206 206
207 207 default = srcrepo.ui.config('paths', 'default')
208 208 if default:
209 209 fp = r.opener("hgrc", "w", text=True)
210 210 fp.write("[paths]\n")
211 211 fp.write("default = %s\n" % default)
212 212 fp.close()
213 213
214 214 if update:
215 215 r.ui.status(_("updating working directory\n"))
216 216 if update is not True:
217 217 checkout = update
218 218 for test in (checkout, 'default', 'tip'):
219 219 if test is None:
220 220 continue
221 221 try:
222 222 uprev = r.lookup(test)
223 223 break
224 224 except error.RepoLookupError:
225 225 continue
226 226 _update(r, uprev)
227 227
228 if bookmarks:
229 r.opener('bookmarks.shared', 'w').close()
230
228 231 def copystore(ui, srcrepo, destpath):
229 232 '''copy files from store of srcrepo in destpath
230 233
231 234 returns destlock
232 235 '''
233 236 destlock = None
234 237 try:
235 238 hardlink = None
236 239 num = 0
237 240 srcpublishing = srcrepo.ui.configbool('phases', 'publish', True)
238 241 srcvfs = scmutil.vfs(srcrepo.sharedpath)
239 242 dstvfs = scmutil.vfs(destpath)
240 243 for f in srcrepo.store.copylist():
241 244 if srcpublishing and f.endswith('phaseroots'):
242 245 continue
243 246 dstbase = os.path.dirname(f)
244 247 if dstbase and not dstvfs.exists(dstbase):
245 248 dstvfs.mkdir(dstbase)
246 249 if srcvfs.exists(f):
247 250 if f.endswith('data'):
248 251 # 'dstbase' may be empty (e.g. revlog format 0)
249 252 lockfile = os.path.join(dstbase, "lock")
250 253 # lock to avoid premature writing to the target
251 254 destlock = lock.lock(dstvfs, lockfile)
252 255 hardlink, n = util.copyfiles(srcvfs.join(f), dstvfs.join(f),
253 256 hardlink)
254 257 num += n
255 258 if hardlink:
256 259 ui.debug("linked %d files\n" % num)
257 260 else:
258 261 ui.debug("copied %d files\n" % num)
259 262 return destlock
260 263 except: # re-raises
261 264 release(destlock)
262 265 raise
263 266
264 267 def clone(ui, peeropts, source, dest=None, pull=False, rev=None,
265 268 update=True, stream=False, branch=None):
266 269 """Make a copy of an existing repository.
267 270
268 271 Create a copy of an existing repository in a new directory. The
269 272 source and destination are URLs, as passed to the repository
270 273 function. Returns a pair of repository peers, the source and
271 274 newly created destination.
272 275
273 276 The location of the source is added to the new repository's
274 277 .hg/hgrc file, as the default to be used for future pulls and
275 278 pushes.
276 279
277 280 If an exception is raised, the partly cloned/updated destination
278 281 repository will be deleted.
279 282
280 283 Arguments:
281 284
282 285 source: repository object or URL
283 286
284 287 dest: URL of destination repository to create (defaults to base
285 288 name of source repository)
286 289
287 290 pull: always pull from source repository, even in local case or if the
288 291 server prefers streaming
289 292
290 293 stream: stream raw data uncompressed from repository (fast over
291 294 LAN, slow over WAN)
292 295
293 296 rev: revision to clone up to (implies pull=True)
294 297
295 298 update: update working directory after clone completes, if
296 299 destination is local repository (True means update to default rev,
297 300 anything else is treated as a revision)
298 301
299 302 branch: branches to clone
300 303 """
301 304
302 305 if isinstance(source, str):
303 306 origsource = ui.expandpath(source)
304 307 source, branch = parseurl(origsource, branch)
305 308 srcpeer = peer(ui, peeropts, source)
306 309 else:
307 310 srcpeer = source.peer() # in case we were called with a localrepo
308 311 branch = (None, branch or [])
309 312 origsource = source = srcpeer.url()
310 313 rev, checkout = addbranchrevs(srcpeer, srcpeer, branch, rev)
311 314
312 315 if dest is None:
313 316 dest = defaultdest(source)
314 317 if dest:
315 318 ui.status(_("destination directory: %s\n") % dest)
316 319 else:
317 320 dest = ui.expandpath(dest)
318 321
319 322 dest = util.urllocalpath(dest)
320 323 source = util.urllocalpath(source)
321 324
322 325 if not dest:
323 326 raise util.Abort(_("empty destination path is not valid"))
324 327
325 328 destvfs = scmutil.vfs(dest, expandpath=True)
326 329 if destvfs.lexists():
327 330 if not destvfs.isdir():
328 331 raise util.Abort(_("destination '%s' already exists") % dest)
329 332 elif destvfs.listdir():
330 333 raise util.Abort(_("destination '%s' is not empty") % dest)
331 334
332 335 srclock = destlock = cleandir = None
333 336 srcrepo = srcpeer.local()
334 337 try:
335 338 abspath = origsource
336 339 if islocal(origsource):
337 340 abspath = os.path.abspath(util.urllocalpath(origsource))
338 341
339 342 if islocal(dest):
340 343 cleandir = dest
341 344
342 345 copy = False
343 346 if (srcrepo and srcrepo.cancopy() and islocal(dest)
344 347 and not phases.hassecret(srcrepo)):
345 348 copy = not pull and not rev
346 349
347 350 if copy:
348 351 try:
349 352 # we use a lock here because if we race with commit, we
350 353 # can end up with extra data in the cloned revlogs that's
351 354 # not pointed to by changesets, thus causing verify to
352 355 # fail
353 356 srclock = srcrepo.lock(wait=False)
354 357 except error.LockError:
355 358 copy = False
356 359
357 360 if copy:
358 361 srcrepo.hook('preoutgoing', throw=True, source='clone')
359 362 hgdir = os.path.realpath(os.path.join(dest, ".hg"))
360 363 if not os.path.exists(dest):
361 364 os.mkdir(dest)
362 365 else:
363 366 # only clean up directories we create ourselves
364 367 cleandir = hgdir
365 368 try:
366 369 destpath = hgdir
367 370 util.makedir(destpath, notindexed=True)
368 371 except OSError, inst:
369 372 if inst.errno == errno.EEXIST:
370 373 cleandir = None
371 374 raise util.Abort(_("destination '%s' already exists")
372 375 % dest)
373 376 raise
374 377
375 378 destlock = copystore(ui, srcrepo, destpath)
376 379 # copy bookmarks over
377 380 srcbookmarks = srcrepo.join('bookmarks')
378 381 dstbookmarks = os.path.join(destpath, 'bookmarks')
379 382 if os.path.exists(srcbookmarks):
380 383 util.copyfile(srcbookmarks, dstbookmarks)
381 384
382 385 # Recomputing branch cache might be slow on big repos,
383 386 # so just copy it
384 387 def copybranchcache(fname):
385 388 srcbranchcache = srcrepo.join('cache/%s' % fname)
386 389 dstbranchcache = os.path.join(dstcachedir, fname)
387 390 if os.path.exists(srcbranchcache):
388 391 if not os.path.exists(dstcachedir):
389 392 os.mkdir(dstcachedir)
390 393 util.copyfile(srcbranchcache, dstbranchcache)
391 394
392 395 dstcachedir = os.path.join(destpath, 'cache')
393 396 # In local clones we're copying all nodes, not just served
394 397 # ones. Therefore copy all branch caches over.
395 398 copybranchcache('branch2')
396 399 for cachename in repoview.filtertable:
397 400 copybranchcache('branch2-%s' % cachename)
398 401
399 402 # we need to re-init the repo after manually copying the data
400 403 # into it
401 404 destpeer = peer(srcrepo, peeropts, dest)
402 405 srcrepo.hook('outgoing', source='clone',
403 406 node=node.hex(node.nullid))
404 407 else:
405 408 try:
406 409 destpeer = peer(srcrepo or ui, peeropts, dest, create=True)
407 410 # only pass ui when no srcrepo
408 411 except OSError, inst:
409 412 if inst.errno == errno.EEXIST:
410 413 cleandir = None
411 414 raise util.Abort(_("destination '%s' already exists")
412 415 % dest)
413 416 raise
414 417
415 418 revs = None
416 419 if rev:
417 420 if not srcpeer.capable('lookup'):
418 421 raise util.Abort(_("src repository does not support "
419 422 "revision lookup and so doesn't "
420 423 "support clone by revision"))
421 424 revs = [srcpeer.lookup(r) for r in rev]
422 425 checkout = revs[0]
423 426 if destpeer.local():
424 427 if not stream:
425 428 if pull:
426 429 stream = False
427 430 else:
428 431 stream = None
429 432 destpeer.local().clone(srcpeer, heads=revs, stream=stream)
430 433 elif srcrepo:
431 434 exchange.push(srcrepo, destpeer, revs=revs,
432 435 bookmarks=srcrepo._bookmarks.keys())
433 436 else:
434 437 raise util.Abort(_("clone from remote to remote not supported"))
435 438
436 439 cleandir = None
437 440
438 441 destrepo = destpeer.local()
439 442 if destrepo:
440 443 template = uimod.samplehgrcs['cloned']
441 444 fp = destrepo.opener("hgrc", "w", text=True)
442 445 u = util.url(abspath)
443 446 u.passwd = None
444 447 defaulturl = str(u)
445 448 fp.write(template % defaulturl)
446 449 fp.close()
447 450
448 451 destrepo.ui.setconfig('paths', 'default', defaulturl, 'clone')
449 452
450 453 if update:
451 454 if update is not True:
452 455 checkout = srcpeer.lookup(update)
453 456 uprev = None
454 457 status = None
455 458 if checkout is not None:
456 459 try:
457 460 uprev = destrepo.lookup(checkout)
458 461 except error.RepoLookupError:
459 462 pass
460 463 if uprev is None:
461 464 try:
462 465 uprev = destrepo._bookmarks['@']
463 466 update = '@'
464 467 bn = destrepo[uprev].branch()
465 468 if bn == 'default':
466 469 status = _("updating to bookmark @\n")
467 470 else:
468 471 status = (_("updating to bookmark @ on branch %s\n")
469 472 % bn)
470 473 except KeyError:
471 474 try:
472 475 uprev = destrepo.branchtip('default')
473 476 except error.RepoLookupError:
474 477 uprev = destrepo.lookup('tip')
475 478 if not status:
476 479 bn = destrepo[uprev].branch()
477 480 status = _("updating to branch %s\n") % bn
478 481 destrepo.ui.status(status)
479 482 _update(destrepo, uprev)
480 483 if update in destrepo._bookmarks:
481 484 bookmarks.setcurrent(destrepo, update)
482 485 finally:
483 486 release(srclock, destlock)
484 487 if cleandir is not None:
485 488 shutil.rmtree(cleandir, True)
486 489 if srcpeer is not None:
487 490 srcpeer.close()
488 491 return srcpeer, destpeer
489 492
490 493 def _showstats(repo, stats):
491 494 repo.ui.status(_("%d files updated, %d files merged, "
492 495 "%d files removed, %d files unresolved\n") % stats)
493 496
494 497 def updaterepo(repo, node, overwrite):
495 498 """Update the working directory to node.
496 499
497 500 When overwrite is set, changes are clobbered, merged else
498 501
499 502 returns stats (see pydoc mercurial.merge.applyupdates)"""
500 503 return mergemod.update(repo, node, False, overwrite, None,
501 504 labels=['working copy', 'destination'])
502 505
503 506 def update(repo, node):
504 507 """update the working directory to node, merging linear changes"""
505 508 stats = updaterepo(repo, node, False)
506 509 _showstats(repo, stats)
507 510 if stats[3]:
508 511 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n"))
509 512 return stats[3] > 0
510 513
511 514 # naming conflict in clone()
512 515 _update = update
513 516
514 517 def clean(repo, node, show_stats=True):
515 518 """forcibly switch the working directory to node, clobbering changes"""
516 519 stats = updaterepo(repo, node, True)
517 520 util.unlinkpath(repo.join('graftstate'), ignoremissing=True)
518 521 if show_stats:
519 522 _showstats(repo, stats)
520 523 return stats[3] > 0
521 524
522 525 def merge(repo, node, force=None, remind=True):
523 526 """Branch merge with node, resolving changes. Return true if any
524 527 unresolved conflicts."""
525 528 stats = mergemod.update(repo, node, True, force, False)
526 529 _showstats(repo, stats)
527 530 if stats[3]:
528 531 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
529 532 "or 'hg update -C .' to abandon\n"))
530 533 elif remind:
531 534 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
532 535 return stats[3] > 0
533 536
534 537 def _incoming(displaychlist, subreporecurse, ui, repo, source,
535 538 opts, buffered=False):
536 539 """
537 540 Helper for incoming / gincoming.
538 541 displaychlist gets called with
539 542 (remoterepo, incomingchangesetlist, displayer) parameters,
540 543 and is supposed to contain only code that can't be unified.
541 544 """
542 545 source, branches = parseurl(ui.expandpath(source), opts.get('branch'))
543 546 other = peer(repo, opts, source)
544 547 ui.status(_('comparing with %s\n') % util.hidepassword(source))
545 548 revs, checkout = addbranchrevs(repo, other, branches, opts.get('rev'))
546 549
547 550 if revs:
548 551 revs = [other.lookup(rev) for rev in revs]
549 552 other, chlist, cleanupfn = bundlerepo.getremotechanges(ui, repo, other,
550 553 revs, opts["bundle"], opts["force"])
551 554 try:
552 555 if not chlist:
553 556 ui.status(_("no changes found\n"))
554 557 return subreporecurse()
555 558
556 559 displayer = cmdutil.show_changeset(ui, other, opts, buffered)
557 560 displaychlist(other, chlist, displayer)
558 561 displayer.close()
559 562 finally:
560 563 cleanupfn()
561 564 subreporecurse()
562 565 return 0 # exit code is zero since we found incoming changes
563 566
564 567 def incoming(ui, repo, source, opts):
565 568 def subreporecurse():
566 569 ret = 1
567 570 if opts.get('subrepos'):
568 571 ctx = repo[None]
569 572 for subpath in sorted(ctx.substate):
570 573 sub = ctx.sub(subpath)
571 574 ret = min(ret, sub.incoming(ui, source, opts))
572 575 return ret
573 576
574 577 def display(other, chlist, displayer):
575 578 limit = cmdutil.loglimit(opts)
576 579 if opts.get('newest_first'):
577 580 chlist.reverse()
578 581 count = 0
579 582 for n in chlist:
580 583 if limit is not None and count >= limit:
581 584 break
582 585 parents = [p for p in other.changelog.parents(n) if p != nullid]
583 586 if opts.get('no_merges') and len(parents) == 2:
584 587 continue
585 588 count += 1
586 589 displayer.show(other[n])
587 590 return _incoming(display, subreporecurse, ui, repo, source, opts)
588 591
589 592 def _outgoing(ui, repo, dest, opts):
590 593 dest = ui.expandpath(dest or 'default-push', dest or 'default')
591 594 dest, branches = parseurl(dest, opts.get('branch'))
592 595 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
593 596 revs, checkout = addbranchrevs(repo, repo, branches, opts.get('rev'))
594 597 if revs:
595 598 revs = [repo.lookup(rev) for rev in scmutil.revrange(repo, revs)]
596 599
597 600 other = peer(repo, opts, dest)
598 601 outgoing = discovery.findcommonoutgoing(repo.unfiltered(), other, revs,
599 602 force=opts.get('force'))
600 603 o = outgoing.missing
601 604 if not o:
602 605 scmutil.nochangesfound(repo.ui, repo, outgoing.excluded)
603 606 return o, other
604 607
605 608 def outgoing(ui, repo, dest, opts):
606 609 def recurse():
607 610 ret = 1
608 611 if opts.get('subrepos'):
609 612 ctx = repo[None]
610 613 for subpath in sorted(ctx.substate):
611 614 sub = ctx.sub(subpath)
612 615 ret = min(ret, sub.outgoing(ui, dest, opts))
613 616 return ret
614 617
615 618 limit = cmdutil.loglimit(opts)
616 619 o, other = _outgoing(ui, repo, dest, opts)
617 620 if not o:
618 621 cmdutil.outgoinghooks(ui, repo, other, opts, o)
619 622 return recurse()
620 623
621 624 if opts.get('newest_first'):
622 625 o.reverse()
623 626 displayer = cmdutil.show_changeset(ui, repo, opts)
624 627 count = 0
625 628 for n in o:
626 629 if limit is not None and count >= limit:
627 630 break
628 631 parents = [p for p in repo.changelog.parents(n) if p != nullid]
629 632 if opts.get('no_merges') and len(parents) == 2:
630 633 continue
631 634 count += 1
632 635 displayer.show(repo[n])
633 636 displayer.close()
634 637 cmdutil.outgoinghooks(ui, repo, other, opts, o)
635 638 recurse()
636 639 return 0 # exit code is zero since we found outgoing changes
637 640
638 641 def revert(repo, node, choose):
639 642 """revert changes to revision in node without updating dirstate"""
640 643 return mergemod.update(repo, node, False, True, choose)[3] > 0
641 644
642 645 def verify(repo):
643 646 """verify the consistency of a repository"""
644 647 return verifymod.verify(repo)
645 648
646 649 def remoteui(src, opts):
647 650 'build a remote ui from ui or repo and opts'
648 651 if util.safehasattr(src, 'baseui'): # looks like a repository
649 652 dst = src.baseui.copy() # drop repo-specific config
650 653 src = src.ui # copy target options from repo
651 654 else: # assume it's a global ui object
652 655 dst = src.copy() # keep all global options
653 656
654 657 # copy ssh-specific options
655 658 for o in 'ssh', 'remotecmd':
656 659 v = opts.get(o) or src.config('ui', o)
657 660 if v:
658 661 dst.setconfig("ui", o, v, 'copied')
659 662
660 663 # copy bundle-specific options
661 664 r = src.config('bundle', 'mainreporoot')
662 665 if r:
663 666 dst.setconfig('bundle', 'mainreporoot', r, 'copied')
664 667
665 668 # copy selected local settings to the remote ui
666 669 for sect in ('auth', 'hostfingerprints', 'http_proxy'):
667 670 for key, val in src.configitems(sect):
668 671 dst.setconfig(sect, key, val, 'copied')
669 672 v = src.config('web', 'cacerts')
670 673 if v:
671 674 dst.setconfig('web', 'cacerts', util.expandpath(v), 'copied')
672 675
673 676 return dst
@@ -1,303 +1,303 b''
1 1 #require killdaemons
2 2
3 3 $ echo "[extensions]" >> $HGRCPATH
4 4 $ echo "share = " >> $HGRCPATH
5 5
6 6 prepare repo1
7 7
8 8 $ hg init repo1
9 9 $ cd repo1
10 10 $ echo a > a
11 11 $ hg commit -A -m'init'
12 12 adding a
13 13
14 14 share it
15 15
16 16 $ cd ..
17 17 $ hg share repo1 repo2
18 18 updating working directory
19 19 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
20 20
21 21 share shouldn't have a store dir
22 22
23 23 $ cd repo2
24 24 $ test -d .hg/store
25 25 [1]
26 26
27 27 Some sed versions appends newline, some don't, and some just fails
28 28
29 29 $ cat .hg/sharedpath; echo
30 30 $TESTTMP/repo1/.hg (glob)
31 31
32 32 trailing newline on .hg/sharedpath is ok
33 33 $ hg tip -q
34 34 0:d3873e73d99e
35 35 $ echo '' >> .hg/sharedpath
36 36 $ cat .hg/sharedpath
37 37 $TESTTMP/repo1/.hg (glob)
38 38 $ hg tip -q
39 39 0:d3873e73d99e
40 40
41 41 commit in shared clone
42 42
43 43 $ echo a >> a
44 44 $ hg commit -m'change in shared clone'
45 45
46 46 check original
47 47
48 48 $ cd ../repo1
49 49 $ hg log
50 50 changeset: 1:8af4dc49db9e
51 51 tag: tip
52 52 user: test
53 53 date: Thu Jan 01 00:00:00 1970 +0000
54 54 summary: change in shared clone
55 55
56 56 changeset: 0:d3873e73d99e
57 57 user: test
58 58 date: Thu Jan 01 00:00:00 1970 +0000
59 59 summary: init
60 60
61 61 $ hg update
62 62 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
63 63 $ cat a # should be two lines of "a"
64 64 a
65 65 a
66 66
67 67 commit in original
68 68
69 69 $ echo b > b
70 70 $ hg commit -A -m'another file'
71 71 adding b
72 72
73 73 check in shared clone
74 74
75 75 $ cd ../repo2
76 76 $ hg log
77 77 changeset: 2:c2e0ac586386
78 78 tag: tip
79 79 user: test
80 80 date: Thu Jan 01 00:00:00 1970 +0000
81 81 summary: another file
82 82
83 83 changeset: 1:8af4dc49db9e
84 84 user: test
85 85 date: Thu Jan 01 00:00:00 1970 +0000
86 86 summary: change in shared clone
87 87
88 88 changeset: 0:d3873e73d99e
89 89 user: test
90 90 date: Thu Jan 01 00:00:00 1970 +0000
91 91 summary: init
92 92
93 93 $ hg update
94 94 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
95 95 $ cat b # should exist with one "b"
96 96 b
97 97
98 98 hg serve shared clone
99 99
100 100 $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid
101 101 $ cat hg.pid >> $DAEMON_PIDS
102 102 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'raw-file/'
103 103 200 Script output follows
104 104
105 105
106 106 -rw-r--r-- 4 a
107 107 -rw-r--r-- 2 b
108 108
109 109
110 110
111 111 test unshare command
112 112
113 113 $ hg unshare
114 114 $ test -d .hg/store
115 115 $ test -f .hg/sharedpath
116 116 [1]
117 117 $ hg unshare
118 118 abort: this is not a shared repo
119 119 [255]
120 120
121 121 check that a change does not propagate
122 122
123 123 $ echo b >> b
124 124 $ hg commit -m'change in unshared'
125 125 $ cd ../repo1
126 126 $ hg id -r tip
127 127 c2e0ac586386 tip
128 128
129 129 $ cd ..
130 130
131 131
132 test sharing bookmarks (manually add bookmarks.shared file for now)
132 test sharing bookmarks
133 133
134 $ hg share repo1 repo3 && touch repo3/.hg/bookmarks.shared
134 $ hg share -B repo1 repo3
135 135 updating working directory
136 136 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
137 137 $ cd repo1
138 138 $ hg bookmark bm1
139 139 $ hg bookmarks
140 140 * bm1 2:c2e0ac586386
141 141 $ cd ../repo2
142 142 $ hg book bm2
143 143 $ hg bookmarks
144 144 * bm2 3:0e6e70d1d5f1
145 145 $ cd ../repo3
146 146 $ hg bookmarks
147 147 bm1 2:c2e0ac586386
148 148 $ hg book bm3
149 149 $ hg bookmarks
150 150 bm1 2:c2e0ac586386
151 151 * bm3 2:c2e0ac586386
152 152 $ cd ../repo1
153 153 $ hg bookmarks
154 154 * bm1 2:c2e0ac586386
155 155 bm3 2:c2e0ac586386
156 156
157 157 test that commits work
158 158
159 159 $ echo 'shared bookmarks' > a
160 160 $ hg commit -m 'testing shared bookmarks'
161 161 $ hg bookmarks
162 162 * bm1 3:b87954705719
163 163 bm3 2:c2e0ac586386
164 164 $ cd ../repo3
165 165 $ hg bookmarks
166 166 bm1 3:b87954705719
167 167 * bm3 2:c2e0ac586386
168 168 $ echo 'more shared bookmarks' > a
169 169 $ hg commit -m 'testing shared bookmarks'
170 170 created new head
171 171 $ hg bookmarks
172 172 bm1 3:b87954705719
173 173 * bm3 4:62f4ded848e4
174 174 $ cd ../repo1
175 175 $ hg bookmarks
176 176 * bm1 3:b87954705719
177 177 bm3 4:62f4ded848e4
178 178 $ cd ..
179 179
180 180 test pushing bookmarks works
181 181
182 182 $ hg clone repo3 repo4
183 183 updating to branch default
184 184 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
185 185 $ cd repo4
186 186 $ hg boo bm4
187 187 $ echo foo > b
188 188 $ hg commit -m 'foo in b'
189 189 $ hg boo
190 190 bm1 3:b87954705719
191 191 bm3 4:62f4ded848e4
192 192 * bm4 5:92793bfc8cad
193 193 $ hg push -B bm4
194 194 pushing to $TESTTMP/repo3 (glob)
195 195 searching for changes
196 196 adding changesets
197 197 adding manifests
198 198 adding file changes
199 199 added 1 changesets with 1 changes to 1 files
200 200 exporting bookmark bm4
201 201 $ cd ../repo1
202 202 $ hg bookmarks
203 203 * bm1 3:b87954705719
204 204 bm3 4:62f4ded848e4
205 205 bm4 5:92793bfc8cad
206 206 $ cd ../repo3
207 207 $ hg bookmarks
208 208 bm1 3:b87954705719
209 209 * bm3 4:62f4ded848e4
210 210 bm4 5:92793bfc8cad
211 211 $ cd ..
212 212
213 213 test behavior when sharing a shared repo
214 214
215 $ hg share repo3 repo5 && touch repo5/.hg/bookmarks.shared
215 $ hg share -B repo3 repo5
216 216 updating working directory
217 217 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
218 218 $ cd repo5
219 219 $ hg book
220 220 bm1 3:b87954705719
221 221 bm3 4:62f4ded848e4
222 222 bm4 5:92793bfc8cad
223 223 $ cd ..
224 224
225 225 test what happens when an active bookmark is deleted
226 226
227 227 $ cd repo1
228 228 $ hg boo -d bm3
229 229 $ hg boo
230 230 * bm1 3:b87954705719
231 231 bm4 5:92793bfc8cad
232 232 $ cd ../repo3
233 233 $ hg boo
234 234 bm1 3:b87954705719
235 235 bm4 5:92793bfc8cad
236 236 $ cd ..
237 237
238 238 verify that bookmarks are not written on failed transaction
239 239
240 240 $ cat > failpullbookmarks.py << EOF
241 241 > """A small extension that makes bookmark pulls fail, for testing"""
242 242 > from mercurial import extensions, exchange, error
243 243 > def _pullbookmarks(orig, pullop):
244 244 > orig(pullop)
245 245 > raise error.HookAbort('forced failure by extension')
246 246 > def extsetup(ui):
247 247 > extensions.wrapfunction(exchange, '_pullbookmarks', _pullbookmarks)
248 248 > EOF
249 249 $ cd repo4
250 250 $ hg boo
251 251 bm1 3:b87954705719
252 252 bm3 4:62f4ded848e4
253 253 * bm4 5:92793bfc8cad
254 254 $ cd ../repo3
255 255 $ hg boo
256 256 bm1 3:b87954705719
257 257 bm4 5:92793bfc8cad
258 258 $ hg --config "extensions.failpullbookmarks=$TESTTMP/failpullbookmarks.py" pull $TESTTMP/repo4
259 259 pulling from $TESTTMP/repo4 (glob)
260 260 searching for changes
261 261 no changes found
262 262 adding remote bookmark bm3
263 263 abort: forced failure by extension
264 264 [255]
265 265 $ hg boo
266 266 bm1 3:b87954705719
267 267 bm4 5:92793bfc8cad
268 268 $ hg pull $TESTTMP/repo4
269 269 pulling from $TESTTMP/repo4 (glob)
270 270 searching for changes
271 271 no changes found
272 272 adding remote bookmark bm3
273 273 $ hg boo
274 274 bm1 3:b87954705719
275 275 * bm3 4:62f4ded848e4
276 276 bm4 5:92793bfc8cad
277 277 $ cd ..
278 278
279 279 verify bookmark behavior after unshare
280 280
281 281 $ cd repo3
282 282 $ hg unshare
283 283 $ hg boo
284 284 bm1 3:b87954705719
285 285 * bm3 4:62f4ded848e4
286 286 bm4 5:92793bfc8cad
287 287 $ hg boo -d bm4
288 288 $ hg boo bm5
289 289 $ hg boo
290 290 bm1 3:b87954705719
291 291 bm3 4:62f4ded848e4
292 292 * bm5 4:62f4ded848e4
293 293 $ cd ../repo1
294 294 $ hg boo
295 295 * bm1 3:b87954705719
296 296 bm3 4:62f4ded848e4
297 297 bm4 5:92793bfc8cad
298 298 $ cd ..
299 299
300 300 Explicitly kill daemons to let the test exit on Windows
301 301
302 302 $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
303 303
General Comments 0
You need to be logged in to leave comments. Login now