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