summaryrefslogtreecommitdiff
path: root/mercurial/bookmarks.py
diff options
context:
space:
mode:
Diffstat (limited to 'mercurial/bookmarks.py')
-rw-r--r--mercurial/bookmarks.py254
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