##// END OF EJS Templates
i18n, bookmarks: add comments for translators
Martin Geisler -
r12881:161fe487 stable
parent child Browse files
Show More
@@ -1,571 +1,573 b''
1 1 # Mercurial extension to provide the 'hg bookmark' command
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 '''track a line of development with movable markers
9 9
10 10 Bookmarks are local movable markers to changesets. Every bookmark
11 11 points to a changeset identified by its hash. If you commit a
12 12 changeset that is based on a changeset that has a bookmark on it, the
13 13 bookmark shifts to the new changeset.
14 14
15 15 It is possible to use bookmark names in every revision lookup (e.g.
16 16 :hg:`merge`, :hg:`update`).
17 17
18 18 By default, when several bookmarks point to the same changeset, they
19 19 will all move forward together. It is possible to obtain a more
20 20 git-like experience by adding the following configuration option to
21 21 your configuration file::
22 22
23 23 [bookmarks]
24 24 track.current = True
25 25
26 26 This will cause Mercurial to track the bookmark that you are currently
27 27 using, and only update it. This is similar to git's approach to
28 28 branching.
29 29 '''
30 30
31 31 from mercurial.i18n import _
32 32 from mercurial.node import nullid, nullrev, bin, hex, short
33 33 from mercurial import util, commands, repair, extensions, pushkey, hg, url
34 34 from mercurial import revset
35 35 import os
36 36
37 37 def write(repo):
38 38 '''Write bookmarks
39 39
40 40 Write the given bookmark => hash dictionary to the .hg/bookmarks file
41 41 in a format equal to those of localtags.
42 42
43 43 We also store a backup of the previous state in undo.bookmarks that
44 44 can be copied back on rollback.
45 45 '''
46 46 refs = repo._bookmarks
47 47 if os.path.exists(repo.join('bookmarks')):
48 48 util.copyfile(repo.join('bookmarks'), repo.join('undo.bookmarks'))
49 49 if repo._bookmarkcurrent not in refs:
50 50 setcurrent(repo, None)
51 51 wlock = repo.wlock()
52 52 try:
53 53 file = repo.opener('bookmarks', 'w', atomictemp=True)
54 54 for refspec, node in refs.iteritems():
55 55 file.write("%s %s\n" % (hex(node), refspec))
56 56 file.rename()
57 57
58 58 # touch 00changelog.i so hgweb reloads bookmarks (no lock needed)
59 59 try:
60 60 os.utime(repo.sjoin('00changelog.i'), None)
61 61 except OSError:
62 62 pass
63 63
64 64 finally:
65 65 wlock.release()
66 66
67 67 def setcurrent(repo, mark):
68 68 '''Set the name of the bookmark that we are currently on
69 69
70 70 Set the name of the bookmark that we are on (hg update <bookmark>).
71 71 The name is recorded in .hg/bookmarks.current
72 72 '''
73 73 current = repo._bookmarkcurrent
74 74 if current == mark:
75 75 return
76 76
77 77 refs = repo._bookmarks
78 78
79 79 # do not update if we do update to a rev equal to the current bookmark
80 80 if (mark and mark not in refs and
81 81 current and refs[current] == repo.changectx('.').node()):
82 82 return
83 83 if mark not in refs:
84 84 mark = ''
85 85 wlock = repo.wlock()
86 86 try:
87 87 file = repo.opener('bookmarks.current', 'w', atomictemp=True)
88 88 file.write(mark)
89 89 file.rename()
90 90 finally:
91 91 wlock.release()
92 92 repo._bookmarkcurrent = mark
93 93
94 94 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False, rename=None):
95 95 '''track a line of development with movable markers
96 96
97 97 Bookmarks are pointers to certain commits that move when
98 98 committing. Bookmarks are local. They can be renamed, copied and
99 99 deleted. It is possible to use bookmark names in :hg:`merge` and
100 100 :hg:`update` to merge and update respectively to a given bookmark.
101 101
102 102 You can use :hg:`bookmark NAME` to set a bookmark on the working
103 103 directory's parent revision with the given name. If you specify
104 104 a revision using -r REV (where REV may be an existing bookmark),
105 105 the bookmark is assigned to that revision.
106 106
107 107 Bookmarks can be pushed and pulled between repositories (see :hg:`help
108 108 push` and :hg:`help pull`). This requires the bookmark extension to be
109 109 enabled for both the local and remote repositories.
110 110 '''
111 111 hexfn = ui.debugflag and hex or short
112 112 marks = repo._bookmarks
113 113 cur = repo.changectx('.').node()
114 114
115 115 if rename:
116 116 if rename not in marks:
117 117 raise util.Abort(_("a bookmark of this name does not exist"))
118 118 if mark in marks and not force:
119 119 raise util.Abort(_("a bookmark of the same name already exists"))
120 120 if mark is None:
121 121 raise util.Abort(_("new bookmark name required"))
122 122 marks[mark] = marks[rename]
123 123 del marks[rename]
124 124 if repo._bookmarkcurrent == rename:
125 125 setcurrent(repo, mark)
126 126 write(repo)
127 127 return
128 128
129 129 if delete:
130 130 if mark is None:
131 131 raise util.Abort(_("bookmark name required"))
132 132 if mark not in marks:
133 133 raise util.Abort(_("a bookmark of this name does not exist"))
134 134 if mark == repo._bookmarkcurrent:
135 135 setcurrent(repo, None)
136 136 del marks[mark]
137 137 write(repo)
138 138 return
139 139
140 140 if mark != None:
141 141 if "\n" in mark:
142 142 raise util.Abort(_("bookmark name cannot contain newlines"))
143 143 mark = mark.strip()
144 144 if not mark:
145 145 raise util.Abort(_("bookmark names cannot consist entirely of "
146 146 "whitespace"))
147 147 if mark in marks and not force:
148 148 raise util.Abort(_("a bookmark of the same name already exists"))
149 149 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
150 150 and not force):
151 151 raise util.Abort(
152 152 _("a bookmark cannot have the name of an existing branch"))
153 153 if rev:
154 154 marks[mark] = repo.lookup(rev)
155 155 else:
156 156 marks[mark] = repo.changectx('.').node()
157 157 setcurrent(repo, mark)
158 158 write(repo)
159 159 return
160 160
161 161 if mark is None:
162 162 if rev:
163 163 raise util.Abort(_("bookmark name required"))
164 164 if len(marks) == 0:
165 165 ui.status(_("no bookmarks set\n"))
166 166 else:
167 167 for bmark, n in marks.iteritems():
168 168 if ui.configbool('bookmarks', 'track.current'):
169 169 current = repo._bookmarkcurrent
170 170 if bmark == current and n == cur:
171 171 prefix, label = '*', 'bookmarks.current'
172 172 else:
173 173 prefix, label = ' ', ''
174 174 else:
175 175 if n == cur:
176 176 prefix, label = '*', 'bookmarks.current'
177 177 else:
178 178 prefix, label = ' ', ''
179 179
180 180 if ui.quiet:
181 181 ui.write("%s\n" % bmark, label=label)
182 182 else:
183 183 ui.write(" %s %-25s %d:%s\n" % (
184 184 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
185 185 label=label)
186 186 return
187 187
188 188 def _revstostrip(changelog, node):
189 189 srev = changelog.rev(node)
190 190 tostrip = [srev]
191 191 saveheads = []
192 192 for r in xrange(srev, len(changelog)):
193 193 parents = changelog.parentrevs(r)
194 194 if parents[0] in tostrip or parents[1] in tostrip:
195 195 tostrip.append(r)
196 196 if parents[1] != nullrev:
197 197 for p in parents:
198 198 if p not in tostrip and p > srev:
199 199 saveheads.append(p)
200 200 return [r for r in tostrip if r not in saveheads]
201 201
202 202 def strip(oldstrip, ui, repo, node, backup="all"):
203 203 """Strip bookmarks if revisions are stripped using
204 204 the mercurial.strip method. This usually happens during
205 205 qpush and qpop"""
206 206 revisions = _revstostrip(repo.changelog, node)
207 207 marks = repo._bookmarks
208 208 update = []
209 209 for mark, n in marks.iteritems():
210 210 if repo.changelog.rev(n) in revisions:
211 211 update.append(mark)
212 212 oldstrip(ui, repo, node, backup)
213 213 if len(update) > 0:
214 214 for m in update:
215 215 marks[m] = repo.changectx('.').node()
216 216 write(repo)
217 217
218 218 def reposetup(ui, repo):
219 219 if not repo.local():
220 220 return
221 221
222 222 class bookmark_repo(repo.__class__):
223 223
224 224 @util.propertycache
225 225 def _bookmarks(self):
226 226 '''Parse .hg/bookmarks file and return a dictionary
227 227
228 228 Bookmarks are stored as {HASH}\\s{NAME}\\n (localtags format) values
229 229 in the .hg/bookmarks file.
230 230 Read the file and return a (name=>nodeid) dictionary
231 231 '''
232 232 try:
233 233 bookmarks = {}
234 234 for line in self.opener('bookmarks'):
235 235 sha, refspec = line.strip().split(' ', 1)
236 236 bookmarks[refspec] = self.changelog.lookup(sha)
237 237 except:
238 238 pass
239 239 return bookmarks
240 240
241 241 @util.propertycache
242 242 def _bookmarkcurrent(self):
243 243 '''Get the current bookmark
244 244
245 245 If we use gittishsh branches we have a current bookmark that
246 246 we are on. This function returns the name of the bookmark. It
247 247 is stored in .hg/bookmarks.current
248 248 '''
249 249 mark = None
250 250 if os.path.exists(self.join('bookmarks.current')):
251 251 file = self.opener('bookmarks.current')
252 252 # No readline() in posixfile_nt, reading everything is cheap
253 253 mark = (file.readlines() or [''])[0]
254 254 if mark == '':
255 255 mark = None
256 256 file.close()
257 257 return mark
258 258
259 259 def rollback(self, *args):
260 260 if os.path.exists(self.join('undo.bookmarks')):
261 261 util.rename(self.join('undo.bookmarks'), self.join('bookmarks'))
262 262 return super(bookmark_repo, self).rollback(*args)
263 263
264 264 def lookup(self, key):
265 265 if key in self._bookmarks:
266 266 key = self._bookmarks[key]
267 267 return super(bookmark_repo, self).lookup(key)
268 268
269 269 def _bookmarksupdate(self, parents, node):
270 270 marks = self._bookmarks
271 271 update = False
272 272 if ui.configbool('bookmarks', 'track.current'):
273 273 mark = self._bookmarkcurrent
274 274 if mark and marks[mark] in parents:
275 275 marks[mark] = node
276 276 update = True
277 277 else:
278 278 for mark, n in marks.items():
279 279 if n in parents:
280 280 marks[mark] = node
281 281 update = True
282 282 if update:
283 283 write(self)
284 284
285 285 def commitctx(self, ctx, error=False):
286 286 """Add a revision to the repository and
287 287 move the bookmark"""
288 288 wlock = self.wlock() # do both commit and bookmark with lock held
289 289 try:
290 290 node = super(bookmark_repo, self).commitctx(ctx, error)
291 291 if node is None:
292 292 return None
293 293 parents = self.changelog.parents(node)
294 294 if parents[1] == nullid:
295 295 parents = (parents[0],)
296 296
297 297 self._bookmarksupdate(parents, node)
298 298 return node
299 299 finally:
300 300 wlock.release()
301 301
302 302 def pull(self, remote, heads=None, force=False):
303 303 result = super(bookmark_repo, self).pull(remote, heads, force)
304 304
305 305 self.ui.debug("checking for updated bookmarks\n")
306 306 rb = remote.listkeys('bookmarks')
307 307 changed = False
308 308 for k in rb.keys():
309 309 if k in self._bookmarks:
310 310 nr, nl = rb[k], self._bookmarks[k]
311 311 if nr in self:
312 312 cr = self[nr]
313 313 cl = self[nl]
314 314 if cl.rev() >= cr.rev():
315 315 continue
316 316 if cr in cl.descendants():
317 317 self._bookmarks[k] = cr.node()
318 318 changed = True
319 319 self.ui.status(_("updating bookmark %s\n") % k)
320 320 else:
321 321 self.ui.warn(_("not updating divergent"
322 322 " bookmark %s\n") % k)
323 323 if changed:
324 324 write(repo)
325 325
326 326 return result
327 327
328 328 def push(self, remote, force=False, revs=None, newbranch=False):
329 329 result = super(bookmark_repo, self).push(remote, force, revs,
330 330 newbranch)
331 331
332 332 self.ui.debug("checking for updated bookmarks\n")
333 333 rb = remote.listkeys('bookmarks')
334 334 for k in rb.keys():
335 335 if k in self._bookmarks:
336 336 nr, nl = rb[k], self._bookmarks[k]
337 337 if nr in self:
338 338 cr = self[nr]
339 339 cl = self[nl]
340 340 if cl in cr.descendants():
341 341 r = remote.pushkey('bookmarks', k, nr, nl)
342 342 if r:
343 343 self.ui.status(_("updating bookmark %s\n") % k)
344 344 else:
345 345 self.ui.warn(_('updating bookmark %s'
346 346 ' failed!\n') % k)
347 347
348 348 return result
349 349
350 350 def addchangegroup(self, *args, **kwargs):
351 351 parents = self.dirstate.parents()
352 352
353 353 result = super(bookmark_repo, self).addchangegroup(*args, **kwargs)
354 354 if result > 1:
355 355 # We have more heads than before
356 356 return result
357 357 node = self.changelog.tip()
358 358
359 359 self._bookmarksupdate(parents, node)
360 360 return result
361 361
362 362 def _findtags(self):
363 363 """Merge bookmarks with normal tags"""
364 364 (tags, tagtypes) = super(bookmark_repo, self)._findtags()
365 365 tags.update(self._bookmarks)
366 366 return (tags, tagtypes)
367 367
368 368 if hasattr(repo, 'invalidate'):
369 369 def invalidate(self):
370 370 super(bookmark_repo, self).invalidate()
371 371 for attr in ('_bookmarks', '_bookmarkcurrent'):
372 372 if attr in self.__dict__:
373 373 delattr(self, attr)
374 374
375 375 repo.__class__ = bookmark_repo
376 376
377 377 def listbookmarks(repo):
378 378 # We may try to list bookmarks on a repo type that does not
379 379 # support it (e.g., statichttprepository).
380 380 if not hasattr(repo, '_bookmarks'):
381 381 return {}
382 382
383 383 d = {}
384 384 for k, v in repo._bookmarks.iteritems():
385 385 d[k] = hex(v)
386 386 return d
387 387
388 388 def pushbookmark(repo, key, old, new):
389 389 w = repo.wlock()
390 390 try:
391 391 marks = repo._bookmarks
392 392 if hex(marks.get(key, '')) != old:
393 393 return False
394 394 if new == '':
395 395 del marks[key]
396 396 else:
397 397 if new not in repo:
398 398 return False
399 399 marks[key] = repo[new].node()
400 400 write(repo)
401 401 return True
402 402 finally:
403 403 w.release()
404 404
405 405 def pull(oldpull, ui, repo, source="default", **opts):
406 406 # translate bookmark args to rev args for actual pull
407 407 if opts.get('bookmark'):
408 408 # this is an unpleasant hack as pull will do this internally
409 409 source, branches = hg.parseurl(ui.expandpath(source),
410 410 opts.get('branch'))
411 411 other = hg.repository(hg.remoteui(repo, opts), source)
412 412 rb = other.listkeys('bookmarks')
413 413
414 414 for b in opts['bookmark']:
415 415 if b not in rb:
416 416 raise util.Abort(_('remote bookmark %s not found!') % b)
417 417 opts.setdefault('rev', []).append(b)
418 418
419 419 result = oldpull(ui, repo, source, **opts)
420 420
421 421 # update specified bookmarks
422 422 if opts.get('bookmark'):
423 423 for b in opts['bookmark']:
424 424 # explicit pull overrides local bookmark if any
425 425 ui.status(_("importing bookmark %s\n") % b)
426 426 repo._bookmarks[b] = repo[rb[b]].node()
427 427 write(repo)
428 428
429 429 return result
430 430
431 431 def push(oldpush, ui, repo, dest=None, **opts):
432 432 dopush = True
433 433 if opts.get('bookmark'):
434 434 dopush = False
435 435 for b in opts['bookmark']:
436 436 if b in repo._bookmarks:
437 437 dopush = True
438 438 opts.setdefault('rev', []).append(b)
439 439
440 440 result = 0
441 441 if dopush:
442 442 result = oldpush(ui, repo, dest, **opts)
443 443
444 444 if opts.get('bookmark'):
445 445 # this is an unpleasant hack as push will do this internally
446 446 dest = ui.expandpath(dest or 'default-push', dest or 'default')
447 447 dest, branches = hg.parseurl(dest, opts.get('branch'))
448 448 other = hg.repository(hg.remoteui(repo, opts), dest)
449 449 rb = other.listkeys('bookmarks')
450 450 for b in opts['bookmark']:
451 451 # explicit push overrides remote bookmark if any
452 452 if b in repo._bookmarks:
453 453 ui.status(_("exporting bookmark %s\n") % b)
454 454 new = repo[b].hex()
455 455 elif b in rb:
456 456 ui.status(_("deleting remote bookmark %s\n") % b)
457 457 new = '' # delete
458 458 else:
459 459 ui.warn(_('bookmark %s does not exist on the local '
460 460 'or remote repository!\n') % b)
461 461 return 2
462 462 old = rb.get(b, '')
463 463 r = other.pushkey('bookmarks', b, old, new)
464 464 if not r:
465 465 ui.warn(_('updating bookmark %s failed!\n') % b)
466 466 if not result:
467 467 result = 2
468 468
469 469 return result
470 470
471 471 def diffbookmarks(ui, repo, remote):
472 472 ui.status(_("searching for changed bookmarks\n"))
473 473
474 474 lmarks = repo.listkeys('bookmarks')
475 475 rmarks = remote.listkeys('bookmarks')
476 476
477 477 diff = sorted(set(rmarks) - set(lmarks))
478 478 for k in diff:
479 479 ui.write(" %-25s %s\n" % (k, rmarks[k][:12]))
480 480
481 481 if len(diff) <= 0:
482 482 ui.status(_("no changed bookmarks found\n"))
483 483 return 1
484 484 return 0
485 485
486 486 def incoming(oldincoming, ui, repo, source="default", **opts):
487 487 if opts.get('bookmarks'):
488 488 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
489 489 other = hg.repository(hg.remoteui(repo, opts), source)
490 490 ui.status(_('comparing with %s\n') % url.hidepassword(source))
491 491 return diffbookmarks(ui, repo, other)
492 492 else:
493 493 return oldincoming(ui, repo, source, **opts)
494 494
495 495 def outgoing(oldoutgoing, ui, repo, dest=None, **opts):
496 496 if opts.get('bookmarks'):
497 497 dest = ui.expandpath(dest or 'default-push', dest or 'default')
498 498 dest, branches = hg.parseurl(dest, opts.get('branch'))
499 499 other = hg.repository(hg.remoteui(repo, opts), dest)
500 500 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
501 501 return diffbookmarks(ui, other, repo)
502 502 else:
503 503 return oldoutgoing(ui, repo, dest, **opts)
504 504
505 505 def uisetup(ui):
506 506 extensions.wrapfunction(repair, "strip", strip)
507 507 if ui.configbool('bookmarks', 'track.current'):
508 508 extensions.wrapcommand(commands.table, 'update', updatecurbookmark)
509 509
510 510 entry = extensions.wrapcommand(commands.table, 'pull', pull)
511 511 entry[1].append(('B', 'bookmark', [],
512 512 _("bookmark to import"),
513 513 _('BOOKMARK')))
514 514 entry = extensions.wrapcommand(commands.table, 'push', push)
515 515 entry[1].append(('B', 'bookmark', [],
516 516 _("bookmark to export"),
517 517 _('BOOKMARK')))
518 518 entry = extensions.wrapcommand(commands.table, 'incoming', incoming)
519 519 entry[1].append(('B', 'bookmarks', False,
520 520 _("compare bookmark")))
521 521 entry = extensions.wrapcommand(commands.table, 'outgoing', outgoing)
522 522 entry[1].append(('B', 'bookmarks', False,
523 523 _("compare bookmark")))
524 524
525 525 pushkey.register('bookmarks', pushbookmark, listbookmarks)
526 526
527 527 def updatecurbookmark(orig, ui, repo, *args, **opts):
528 528 '''Set the current bookmark
529 529
530 530 If the user updates to a bookmark we update the .hg/bookmarks.current
531 531 file.
532 532 '''
533 533 res = orig(ui, repo, *args, **opts)
534 534 rev = opts['rev']
535 535 if not rev and len(args) > 0:
536 536 rev = args[0]
537 537 setcurrent(repo, rev)
538 538 return res
539 539
540 540 def bmrevset(repo, subset, x):
541 541 """``bookmark([name])``
542 542 The named bookmark or all bookmarks.
543 543 """
544 # i18n: "bookmark" is a keyword
544 545 args = revset.getargs(x, 0, 1, _('bookmark takes one or no arguments'))
545 546 if args:
546 547 bm = revset.getstring(args[0],
548 # i18n: "bookmark" is a keyword
547 549 _('the argument to bookmark must be a string'))
548 550 bmrev = listbookmarks(repo).get(bm, None)
549 551 if bmrev:
550 552 bmrev = repo.changelog.rev(bin(bmrev))
551 553 return [r for r in subset if r == bmrev]
552 554 bms = set([repo.changelog.rev(bin(r)) for r in listbookmarks(repo).values()])
553 555 return [r for r in subset if r in bms]
554 556
555 557 def extsetup(ui):
556 558 revset.symbols['bookmark'] = bmrevset
557 559
558 560 cmdtable = {
559 561 "bookmarks":
560 562 (bookmark,
561 563 [('f', 'force', False, _('force')),
562 564 ('r', 'rev', '', _('revision'), _('REV')),
563 565 ('d', 'delete', False, _('delete a given bookmark')),
564 566 ('m', 'rename', '', _('rename a given bookmark'), _('NAME'))],
565 567 _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
566 568 }
567 569
568 570 colortable = {'bookmarks.current': 'green'}
569 571
570 572 # tell hggettext to extract docstrings from these functions:
571 573 i18nfunctions = [bmrevset]
General Comments 0
You need to be logged in to leave comments. Login now