diff options
Diffstat (limited to 'mercurial/bookmarks.py')
-rw-r--r-- | mercurial/bookmarks.py | 254 |
1 files changed, 254 insertions, 0 deletions
diff --git a/mercurial/bookmarks.py b/mercurial/bookmarks.py new file mode 100644 index 0000000..a7cd854 --- /dev/null +++ b/mercurial/bookmarks.py @@ -0,0 +1,254 @@ +# Mercurial bookmark support code +# +# Copyright 2008 David Soria Parra <dsp@php.net> +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +from mercurial.i18n import _ +from mercurial.node import hex +from mercurial import encoding, error, util +import errno, os + +def valid(mark): + for c in (':', '\0', '\n', '\r'): + if c in mark: + return False + return True + +def read(repo): + '''Parse .hg/bookmarks file and return a dictionary + + Bookmarks are stored as {HASH}\\s{NAME}\\n (localtags format) values + in the .hg/bookmarks file. + Read the file and return a (name=>nodeid) dictionary + ''' + bookmarks = {} + try: + for line in repo.opener('bookmarks'): + line = line.strip() + if not line: + continue + if ' ' not in line: + repo.ui.warn(_('malformed line in .hg/bookmarks: %r\n') % line) + continue + sha, refspec = line.split(' ', 1) + refspec = encoding.tolocal(refspec) + try: + bookmarks[refspec] = repo.changelog.lookup(sha) + except LookupError: + pass + except IOError, inst: + if inst.errno != errno.ENOENT: + raise + return bookmarks + +def readcurrent(repo): + '''Get the current bookmark + + If we use gittishsh branches we have a current bookmark that + we are on. This function returns the name of the bookmark. It + is stored in .hg/bookmarks.current + ''' + mark = None + try: + file = repo.opener('bookmarks.current') + except IOError, inst: + if inst.errno != errno.ENOENT: + raise + return None + try: + # No readline() in posixfile_nt, reading everything is cheap + mark = encoding.tolocal((file.readlines() or [''])[0]) + if mark == '' or mark not in repo._bookmarks: + mark = None + finally: + file.close() + return mark + +def write(repo): + '''Write bookmarks + + Write the given bookmark => hash dictionary to the .hg/bookmarks file + in a format equal to those of localtags. + + We also store a backup of the previous state in undo.bookmarks that + can be copied back on rollback. + ''' + refs = repo._bookmarks + + if repo._bookmarkcurrent not in refs: + setcurrent(repo, None) + for mark in refs.keys(): + if not valid(mark): + raise util.Abort(_("bookmark '%s' contains illegal " + "character" % mark)) + + wlock = repo.wlock() + try: + + file = repo.opener('bookmarks', 'w', atomictemp=True) + for refspec, node in refs.iteritems(): + file.write("%s %s\n" % (hex(node), encoding.fromlocal(refspec))) + file.close() + + # touch 00changelog.i so hgweb reloads bookmarks (no lock needed) + try: + os.utime(repo.sjoin('00changelog.i'), None) + except OSError: + pass + + finally: + wlock.release() + +def setcurrent(repo, mark): + '''Set the name of the bookmark that we are currently on + + Set the name of the bookmark that we are on (hg update <bookmark>). + The name is recorded in .hg/bookmarks.current + ''' + current = repo._bookmarkcurrent + if current == mark: + return + + if mark not in repo._bookmarks: + mark = '' + if not valid(mark): + raise util.Abort(_("bookmark '%s' contains illegal " + "character" % mark)) + + wlock = repo.wlock() + try: + file = repo.opener('bookmarks.current', 'w', atomictemp=True) + file.write(encoding.fromlocal(mark)) + file.close() + finally: + wlock.release() + repo._bookmarkcurrent = mark + +def unsetcurrent(repo): + wlock = repo.wlock() + try: + try: + util.unlink(repo.join('bookmarks.current')) + repo._bookmarkcurrent = None + except OSError, inst: + if inst.errno != errno.ENOENT: + raise + finally: + wlock.release() + +def updatecurrentbookmark(repo, oldnode, curbranch): + try: + return update(repo, oldnode, repo.branchtip(curbranch)) + except error.RepoLookupError: + if curbranch == "default": # no default branch! + return update(repo, oldnode, repo.lookup("tip")) + else: + raise util.Abort(_("branch %s not found") % curbranch) + +def update(repo, parents, node): + marks = repo._bookmarks + update = False + cur = repo._bookmarkcurrent + if not cur: + return False + + toupdate = [b for b in marks if b.split('@', 1)[0] == cur.split('@', 1)[0]] + for mark in toupdate: + if mark and marks[mark] in parents: + old = repo[marks[mark]] + new = repo[node] + if new in old.descendants() and mark == cur: + marks[cur] = new.node() + update = True + if mark != cur: + del marks[mark] + if update: + repo._writebookmarks(marks) + return update + +def listbookmarks(repo): + # We may try to list bookmarks on a repo type that does not + # support it (e.g., statichttprepository). + marks = getattr(repo, '_bookmarks', {}) + + d = {} + for k, v in marks.iteritems(): + # don't expose local divergent bookmarks + if '@' not in k or k.endswith('@'): + d[k] = hex(v) + return d + +def pushbookmark(repo, key, old, new): + w = repo.wlock() + try: + marks = repo._bookmarks + if hex(marks.get(key, '')) != old: + return False + if new == '': + del marks[key] + else: + if new not in repo: + return False + marks[key] = repo[new].node() + write(repo) + return True + finally: + w.release() + +def updatefromremote(ui, repo, remote, path): + ui.debug("checking for updated bookmarks\n") + rb = remote.listkeys('bookmarks') + changed = False + for k in rb.keys(): + if k in repo._bookmarks: + nr, nl = rb[k], repo._bookmarks[k] + if nr in repo: + cr = repo[nr] + cl = repo[nl] + if cl.rev() >= cr.rev(): + continue + if cr in cl.descendants(): + repo._bookmarks[k] = cr.node() + changed = True + ui.status(_("updating bookmark %s\n") % k) + else: + # find a unique @ suffix + for x in range(1, 100): + n = '%s@%d' % (k, x) + if n not in repo._bookmarks: + break + # try to use an @pathalias suffix + # if an @pathalias already exists, we overwrite (update) it + for p, u in ui.configitems("paths"): + if path == u: + n = '%s@%s' % (k, p) + + repo._bookmarks[n] = cr.node() + changed = True + ui.warn(_("divergent bookmark %s stored as %s\n") % (k, n)) + elif rb[k] in repo: + # add remote bookmarks for changes we already have + repo._bookmarks[k] = repo[rb[k]].node() + changed = True + ui.status(_("adding remote bookmark %s\n") % k) + + if changed: + write(repo) + +def diff(ui, repo, remote): + ui.status(_("searching for changed bookmarks\n")) + + lmarks = repo.listkeys('bookmarks') + rmarks = remote.listkeys('bookmarks') + + diff = sorted(set(rmarks) - set(lmarks)) + for k in diff: + mark = ui.debugflag and rmarks[k] or rmarks[k][:12] + ui.write(" %-25s %s\n" % (k, mark)) + + if len(diff) <= 0: + ui.status(_("no changed bookmarks found\n")) + return 1 + return 0 |