##// END OF EJS Templates
bookmarks: document getbkfile method...
Augie Fackler -
r27185:d5276583 default
parent child Browse files
Show More
@@ -1,585 +1,591 b''
1 1 # Mercurial bookmark support code
2 2 #
3 3 # Copyright 2008 David Soria Parra <dsp@php.net>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import os
12 12
13 13 from .i18n import _
14 14 from .node import (
15 15 bin,
16 16 hex,
17 17 )
18 18 from . import (
19 19 encoding,
20 20 lock as lockmod,
21 21 obsolete,
22 22 util,
23 23 )
24 24
25 25 class bmstore(dict):
26 26 """Storage for bookmarks.
27 27
28 28 This object should do all bookmark reads and writes, so that it's
29 29 fairly simple to replace the storage underlying bookmarks without
30 30 having to clone the logic surrounding bookmarks.
31 31
32 32 This particular bmstore implementation stores bookmarks as
33 33 {hash}\s{name}\n (the same format as localtags) in
34 34 .hg/bookmarks. The mapping is stored as {name: nodeid}.
35 35
36 36 This class does NOT handle the "active" bookmark state at this
37 37 time.
38 38 """
39 39
40 40 def __init__(self, repo):
41 41 dict.__init__(self)
42 42 self._repo = repo
43 43 try:
44 44 bkfile = self.getbkfile(repo)
45 45 for line in bkfile:
46 46 line = line.strip()
47 47 if not line:
48 48 continue
49 49 if ' ' not in line:
50 50 repo.ui.warn(_('malformed line in .hg/bookmarks: %r\n')
51 51 % line)
52 52 continue
53 53 sha, refspec = line.split(' ', 1)
54 54 refspec = encoding.tolocal(refspec)
55 55 try:
56 56 self[refspec] = repo.changelog.lookup(sha)
57 57 except LookupError:
58 58 pass
59 59 except IOError as inst:
60 60 if inst.errno != errno.ENOENT:
61 61 raise
62 62
63 63 def getbkfile(self, repo):
64 """Hook so that extensions that mess with the store can hook bm storage.
65
66 For core, this just handles wether we should see pending
67 bookmarks or the committed ones. Other extensions (like share)
68 may need to tweak this behavior further.
69 """
64 70 bkfile = None
65 71 if 'HG_PENDING' in os.environ:
66 72 try:
67 73 bkfile = repo.vfs('bookmarks.pending')
68 74 except IOError as inst:
69 75 if inst.errno != errno.ENOENT:
70 76 raise
71 77 if bkfile is None:
72 78 bkfile = repo.vfs('bookmarks')
73 79 return bkfile
74 80
75 81 def recordchange(self, tr):
76 82 """record that bookmarks have been changed in a transaction
77 83
78 84 The transaction is then responsible for updating the file content."""
79 85 tr.addfilegenerator('bookmarks', ('bookmarks',), self._write,
80 86 location='plain')
81 87 tr.hookargs['bookmark_moved'] = '1'
82 88
83 89 def write(self):
84 90 '''Write bookmarks
85 91
86 92 Write the given bookmark => hash dictionary to the .hg/bookmarks file
87 93 in a format equal to those of localtags.
88 94
89 95 We also store a backup of the previous state in undo.bookmarks that
90 96 can be copied back on rollback.
91 97 '''
92 98 repo = self._repo
93 99 if (repo.ui.configbool('devel', 'all-warnings')
94 100 or repo.ui.configbool('devel', 'check-locks')):
95 101 l = repo._wlockref and repo._wlockref()
96 102 if l is None or not l.held:
97 103 repo.ui.develwarn('bookmarks write with no wlock')
98 104
99 105 tr = repo.currenttransaction()
100 106 if tr:
101 107 self.recordchange(tr)
102 108 # invalidatevolatilesets() is omitted because this doesn't
103 109 # write changes out actually
104 110 return
105 111
106 112 self._writerepo(repo)
107 113 repo.invalidatevolatilesets()
108 114
109 115 def _writerepo(self, repo):
110 116 """Factored out for extensibility"""
111 117 if repo._activebookmark not in self:
112 118 deactivate(repo)
113 119
114 120 wlock = repo.wlock()
115 121 try:
116 122
117 123 file = repo.vfs('bookmarks', 'w', atomictemp=True)
118 124 self._write(file)
119 125 file.close()
120 126
121 127 finally:
122 128 wlock.release()
123 129
124 130 def _write(self, fp):
125 131 for name, node in self.iteritems():
126 132 fp.write("%s %s\n" % (hex(node), encoding.fromlocal(name)))
127 133
128 134 def readactive(repo):
129 135 """
130 136 Get the active bookmark. We can have an active bookmark that updates
131 137 itself as we commit. This function returns the name of that bookmark.
132 138 It is stored in .hg/bookmarks.current
133 139 """
134 140 mark = None
135 141 try:
136 142 file = repo.vfs('bookmarks.current')
137 143 except IOError as inst:
138 144 if inst.errno != errno.ENOENT:
139 145 raise
140 146 return None
141 147 try:
142 148 # No readline() in osutil.posixfile, reading everything is cheap
143 149 mark = encoding.tolocal((file.readlines() or [''])[0])
144 150 if mark == '' or mark not in repo._bookmarks:
145 151 mark = None
146 152 finally:
147 153 file.close()
148 154 return mark
149 155
150 156 def activate(repo, mark):
151 157 """
152 158 Set the given bookmark to be 'active', meaning that this bookmark will
153 159 follow new commits that are made.
154 160 The name is recorded in .hg/bookmarks.current
155 161 """
156 162 if mark not in repo._bookmarks:
157 163 raise AssertionError('bookmark %s does not exist!' % mark)
158 164
159 165 active = repo._activebookmark
160 166 if active == mark:
161 167 return
162 168
163 169 wlock = repo.wlock()
164 170 try:
165 171 file = repo.vfs('bookmarks.current', 'w', atomictemp=True)
166 172 file.write(encoding.fromlocal(mark))
167 173 file.close()
168 174 finally:
169 175 wlock.release()
170 176 repo._activebookmark = mark
171 177
172 178 def deactivate(repo):
173 179 """
174 180 Unset the active bookmark in this repository.
175 181 """
176 182 wlock = repo.wlock()
177 183 try:
178 184 repo.vfs.unlink('bookmarks.current')
179 185 repo._activebookmark = None
180 186 except OSError as inst:
181 187 if inst.errno != errno.ENOENT:
182 188 raise
183 189 finally:
184 190 wlock.release()
185 191
186 192 def isactivewdirparent(repo):
187 193 """
188 194 Tell whether the 'active' bookmark (the one that follows new commits)
189 195 points to one of the parents of the current working directory (wdir).
190 196
191 197 While this is normally the case, it can on occasion be false; for example,
192 198 immediately after a pull, the active bookmark can be moved to point
193 199 to a place different than the wdir. This is solved by running `hg update`.
194 200 """
195 201 mark = repo._activebookmark
196 202 marks = repo._bookmarks
197 203 parents = [p.node() for p in repo[None].parents()]
198 204 return (mark in marks and marks[mark] in parents)
199 205
200 206 def deletedivergent(repo, deletefrom, bm):
201 207 '''Delete divergent versions of bm on nodes in deletefrom.
202 208
203 209 Return True if at least one bookmark was deleted, False otherwise.'''
204 210 deleted = False
205 211 marks = repo._bookmarks
206 212 divergent = [b for b in marks if b.split('@', 1)[0] == bm.split('@', 1)[0]]
207 213 for mark in divergent:
208 214 if mark == '@' or '@' not in mark:
209 215 # can't be divergent by definition
210 216 continue
211 217 if mark and marks[mark] in deletefrom:
212 218 if mark != bm:
213 219 del marks[mark]
214 220 deleted = True
215 221 return deleted
216 222
217 223 def calculateupdate(ui, repo, checkout):
218 224 '''Return a tuple (targetrev, movemarkfrom) indicating the rev to
219 225 check out and where to move the active bookmark from, if needed.'''
220 226 movemarkfrom = None
221 227 if checkout is None:
222 228 activemark = repo._activebookmark
223 229 if isactivewdirparent(repo):
224 230 movemarkfrom = repo['.'].node()
225 231 elif activemark:
226 232 ui.status(_("updating to active bookmark %s\n") % activemark)
227 233 checkout = activemark
228 234 return (checkout, movemarkfrom)
229 235
230 236 def update(repo, parents, node):
231 237 deletefrom = parents
232 238 marks = repo._bookmarks
233 239 update = False
234 240 active = repo._activebookmark
235 241 if not active:
236 242 return False
237 243
238 244 if marks[active] in parents:
239 245 new = repo[node]
240 246 divs = [repo[b] for b in marks
241 247 if b.split('@', 1)[0] == active.split('@', 1)[0]]
242 248 anc = repo.changelog.ancestors([new.rev()])
243 249 deletefrom = [b.node() for b in divs if b.rev() in anc or b == new]
244 250 if validdest(repo, repo[marks[active]], new):
245 251 marks[active] = new.node()
246 252 update = True
247 253
248 254 if deletedivergent(repo, deletefrom, active):
249 255 update = True
250 256
251 257 if update:
252 258 lock = tr = None
253 259 try:
254 260 lock = repo.lock()
255 261 tr = repo.transaction('bookmark')
256 262 marks.recordchange(tr)
257 263 tr.close()
258 264 finally:
259 265 lockmod.release(tr, lock)
260 266 return update
261 267
262 268 def listbookmarks(repo):
263 269 # We may try to list bookmarks on a repo type that does not
264 270 # support it (e.g., statichttprepository).
265 271 marks = getattr(repo, '_bookmarks', {})
266 272
267 273 d = {}
268 274 hasnode = repo.changelog.hasnode
269 275 for k, v in marks.iteritems():
270 276 # don't expose local divergent bookmarks
271 277 if hasnode(v) and ('@' not in k or k.endswith('@')):
272 278 d[k] = hex(v)
273 279 return d
274 280
275 281 def pushbookmark(repo, key, old, new):
276 282 w = l = tr = None
277 283 try:
278 284 w = repo.wlock()
279 285 l = repo.lock()
280 286 tr = repo.transaction('bookmarks')
281 287 marks = repo._bookmarks
282 288 existing = hex(marks.get(key, ''))
283 289 if existing != old and existing != new:
284 290 return False
285 291 if new == '':
286 292 del marks[key]
287 293 else:
288 294 if new not in repo:
289 295 return False
290 296 marks[key] = repo[new].node()
291 297 marks.recordchange(tr)
292 298 tr.close()
293 299 return True
294 300 finally:
295 301 lockmod.release(tr, l, w)
296 302
297 303 def compare(repo, srcmarks, dstmarks,
298 304 srchex=None, dsthex=None, targets=None):
299 305 '''Compare bookmarks between srcmarks and dstmarks
300 306
301 307 This returns tuple "(addsrc, adddst, advsrc, advdst, diverge,
302 308 differ, invalid)", each are list of bookmarks below:
303 309
304 310 :addsrc: added on src side (removed on dst side, perhaps)
305 311 :adddst: added on dst side (removed on src side, perhaps)
306 312 :advsrc: advanced on src side
307 313 :advdst: advanced on dst side
308 314 :diverge: diverge
309 315 :differ: changed, but changeset referred on src is unknown on dst
310 316 :invalid: unknown on both side
311 317 :same: same on both side
312 318
313 319 Each elements of lists in result tuple is tuple "(bookmark name,
314 320 changeset ID on source side, changeset ID on destination
315 321 side)". Each changeset IDs are 40 hexadecimal digit string or
316 322 None.
317 323
318 324 Changeset IDs of tuples in "addsrc", "adddst", "differ" or
319 325 "invalid" list may be unknown for repo.
320 326
321 327 This function expects that "srcmarks" and "dstmarks" return
322 328 changeset ID in 40 hexadecimal digit string for specified
323 329 bookmark. If not so (e.g. bmstore "repo._bookmarks" returning
324 330 binary value), "srchex" or "dsthex" should be specified to convert
325 331 into such form.
326 332
327 333 If "targets" is specified, only bookmarks listed in it are
328 334 examined.
329 335 '''
330 336 if not srchex:
331 337 srchex = lambda x: x
332 338 if not dsthex:
333 339 dsthex = lambda x: x
334 340
335 341 if targets:
336 342 bset = set(targets)
337 343 else:
338 344 srcmarkset = set(srcmarks)
339 345 dstmarkset = set(dstmarks)
340 346 bset = srcmarkset | dstmarkset
341 347
342 348 results = ([], [], [], [], [], [], [], [])
343 349 addsrc = results[0].append
344 350 adddst = results[1].append
345 351 advsrc = results[2].append
346 352 advdst = results[3].append
347 353 diverge = results[4].append
348 354 differ = results[5].append
349 355 invalid = results[6].append
350 356 same = results[7].append
351 357
352 358 for b in sorted(bset):
353 359 if b not in srcmarks:
354 360 if b in dstmarks:
355 361 adddst((b, None, dsthex(dstmarks[b])))
356 362 else:
357 363 invalid((b, None, None))
358 364 elif b not in dstmarks:
359 365 addsrc((b, srchex(srcmarks[b]), None))
360 366 else:
361 367 scid = srchex(srcmarks[b])
362 368 dcid = dsthex(dstmarks[b])
363 369 if scid == dcid:
364 370 same((b, scid, dcid))
365 371 elif scid in repo and dcid in repo:
366 372 sctx = repo[scid]
367 373 dctx = repo[dcid]
368 374 if sctx.rev() < dctx.rev():
369 375 if validdest(repo, sctx, dctx):
370 376 advdst((b, scid, dcid))
371 377 else:
372 378 diverge((b, scid, dcid))
373 379 else:
374 380 if validdest(repo, dctx, sctx):
375 381 advsrc((b, scid, dcid))
376 382 else:
377 383 diverge((b, scid, dcid))
378 384 else:
379 385 # it is too expensive to examine in detail, in this case
380 386 differ((b, scid, dcid))
381 387
382 388 return results
383 389
384 390 def _diverge(ui, b, path, localmarks, remotenode):
385 391 '''Return appropriate diverged bookmark for specified ``path``
386 392
387 393 This returns None, if it is failed to assign any divergent
388 394 bookmark name.
389 395
390 396 This reuses already existing one with "@number" suffix, if it
391 397 refers ``remotenode``.
392 398 '''
393 399 if b == '@':
394 400 b = ''
395 401 # try to use an @pathalias suffix
396 402 # if an @pathalias already exists, we overwrite (update) it
397 403 if path.startswith("file:"):
398 404 path = util.url(path).path
399 405 for p, u in ui.configitems("paths"):
400 406 if u.startswith("file:"):
401 407 u = util.url(u).path
402 408 if path == u:
403 409 return '%s@%s' % (b, p)
404 410
405 411 # assign a unique "@number" suffix newly
406 412 for x in range(1, 100):
407 413 n = '%s@%d' % (b, x)
408 414 if n not in localmarks or localmarks[n] == remotenode:
409 415 return n
410 416
411 417 return None
412 418
413 419 def updatefromremote(ui, repo, remotemarks, path, trfunc, explicit=()):
414 420 ui.debug("checking for updated bookmarks\n")
415 421 localmarks = repo._bookmarks
416 422 (addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same
417 423 ) = compare(repo, remotemarks, localmarks, dsthex=hex)
418 424
419 425 status = ui.status
420 426 warn = ui.warn
421 427 if ui.configbool('ui', 'quietbookmarkmove', False):
422 428 status = warn = ui.debug
423 429
424 430 explicit = set(explicit)
425 431 changed = []
426 432 for b, scid, dcid in addsrc:
427 433 if scid in repo: # add remote bookmarks for changes we already have
428 434 changed.append((b, bin(scid), status,
429 435 _("adding remote bookmark %s\n") % (b)))
430 436 elif b in explicit:
431 437 explicit.remove(b)
432 438 ui.warn(_("remote bookmark %s points to locally missing %s\n")
433 439 % (b, scid[:12]))
434 440
435 441 for b, scid, dcid in advsrc:
436 442 changed.append((b, bin(scid), status,
437 443 _("updating bookmark %s\n") % (b)))
438 444 # remove normal movement from explicit set
439 445 explicit.difference_update(d[0] for d in changed)
440 446
441 447 for b, scid, dcid in diverge:
442 448 if b in explicit:
443 449 explicit.discard(b)
444 450 changed.append((b, bin(scid), status,
445 451 _("importing bookmark %s\n") % (b)))
446 452 else:
447 453 snode = bin(scid)
448 454 db = _diverge(ui, b, path, localmarks, snode)
449 455 if db:
450 456 changed.append((db, snode, warn,
451 457 _("divergent bookmark %s stored as %s\n") %
452 458 (b, db)))
453 459 else:
454 460 warn(_("warning: failed to assign numbered name "
455 461 "to divergent bookmark %s\n") % (b))
456 462 for b, scid, dcid in adddst + advdst:
457 463 if b in explicit:
458 464 explicit.discard(b)
459 465 changed.append((b, bin(scid), status,
460 466 _("importing bookmark %s\n") % (b)))
461 467 for b, scid, dcid in differ:
462 468 if b in explicit:
463 469 explicit.remove(b)
464 470 ui.warn(_("remote bookmark %s points to locally missing %s\n")
465 471 % (b, scid[:12]))
466 472
467 473 if changed:
468 474 tr = trfunc()
469 475 for b, node, writer, msg in sorted(changed):
470 476 localmarks[b] = node
471 477 writer(msg)
472 478 localmarks.recordchange(tr)
473 479
474 480 def incoming(ui, repo, other):
475 481 '''Show bookmarks incoming from other to repo
476 482 '''
477 483 ui.status(_("searching for changed bookmarks\n"))
478 484
479 485 r = compare(repo, other.listkeys('bookmarks'), repo._bookmarks,
480 486 dsthex=hex)
481 487 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
482 488
483 489 incomings = []
484 490 if ui.debugflag:
485 491 getid = lambda id: id
486 492 else:
487 493 getid = lambda id: id[:12]
488 494 if ui.verbose:
489 495 def add(b, id, st):
490 496 incomings.append(" %-25s %s %s\n" % (b, getid(id), st))
491 497 else:
492 498 def add(b, id, st):
493 499 incomings.append(" %-25s %s\n" % (b, getid(id)))
494 500 for b, scid, dcid in addsrc:
495 501 # i18n: "added" refers to a bookmark
496 502 add(b, scid, _('added'))
497 503 for b, scid, dcid in advsrc:
498 504 # i18n: "advanced" refers to a bookmark
499 505 add(b, scid, _('advanced'))
500 506 for b, scid, dcid in diverge:
501 507 # i18n: "diverged" refers to a bookmark
502 508 add(b, scid, _('diverged'))
503 509 for b, scid, dcid in differ:
504 510 # i18n: "changed" refers to a bookmark
505 511 add(b, scid, _('changed'))
506 512
507 513 if not incomings:
508 514 ui.status(_("no changed bookmarks found\n"))
509 515 return 1
510 516
511 517 for s in sorted(incomings):
512 518 ui.write(s)
513 519
514 520 return 0
515 521
516 522 def outgoing(ui, repo, other):
517 523 '''Show bookmarks outgoing from repo to other
518 524 '''
519 525 ui.status(_("searching for changed bookmarks\n"))
520 526
521 527 r = compare(repo, repo._bookmarks, other.listkeys('bookmarks'),
522 528 srchex=hex)
523 529 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
524 530
525 531 outgoings = []
526 532 if ui.debugflag:
527 533 getid = lambda id: id
528 534 else:
529 535 getid = lambda id: id[:12]
530 536 if ui.verbose:
531 537 def add(b, id, st):
532 538 outgoings.append(" %-25s %s %s\n" % (b, getid(id), st))
533 539 else:
534 540 def add(b, id, st):
535 541 outgoings.append(" %-25s %s\n" % (b, getid(id)))
536 542 for b, scid, dcid in addsrc:
537 543 # i18n: "added refers to a bookmark
538 544 add(b, scid, _('added'))
539 545 for b, scid, dcid in adddst:
540 546 # i18n: "deleted" refers to a bookmark
541 547 add(b, ' ' * 40, _('deleted'))
542 548 for b, scid, dcid in advsrc:
543 549 # i18n: "advanced" refers to a bookmark
544 550 add(b, scid, _('advanced'))
545 551 for b, scid, dcid in diverge:
546 552 # i18n: "diverged" refers to a bookmark
547 553 add(b, scid, _('diverged'))
548 554 for b, scid, dcid in differ:
549 555 # i18n: "changed" refers to a bookmark
550 556 add(b, scid, _('changed'))
551 557
552 558 if not outgoings:
553 559 ui.status(_("no changed bookmarks found\n"))
554 560 return 1
555 561
556 562 for s in sorted(outgoings):
557 563 ui.write(s)
558 564
559 565 return 0
560 566
561 567 def summary(repo, other):
562 568 '''Compare bookmarks between repo and other for "hg summary" output
563 569
564 570 This returns "(# of incoming, # of outgoing)" tuple.
565 571 '''
566 572 r = compare(repo, other.listkeys('bookmarks'), repo._bookmarks,
567 573 dsthex=hex)
568 574 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
569 575 return (len(addsrc), len(adddst))
570 576
571 577 def validdest(repo, old, new):
572 578 """Is the new bookmark destination a valid update from the old one"""
573 579 repo = repo.unfiltered()
574 580 if old == new:
575 581 # Old == new -> nothing to update.
576 582 return False
577 583 elif not old:
578 584 # old is nullrev, anything is valid.
579 585 # (new != nullrev has been excluded by the previous check)
580 586 return True
581 587 elif repo.obsstore:
582 588 return new.node() in obsolete.foreground(repo, [old.node()])
583 589 else:
584 590 # still an independent clause as it is lazier (and therefore faster)
585 591 return old.descendant(new)
General Comments 0
You need to be logged in to leave comments. Login now