hg site extension
 
(arne)
2012-06-04: Added fork info for every clone in path.

Added fork info for every clone in path.

diff --git a/site.py b/site.py
--- a/site.py
+++ b/site.py
@@ -25,7 +25,7 @@ import mercurial
 import ftplib
 import socket
 import datetime
-from mercurial import cmdutil
+from mercurial import cmdutil, util
 from mercurial import commands
 from mercurial.i18n import _
 from mercurial import hg, discovery
@@ -54,6 +54,17 @@ templates = {
 </head>
 <body>
 """,
+    "forkhead": """<!DOCTYPE html>
+<html><head>
+    <meta charset="utf-8" />
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <!--duplicate for older browsers-->
+    <link rel="stylesheet" href="style.css" type="text/css" media="screen" />
+    <link rel="stylesheet" href="print.css" type="text/css" media="print" />
+    <title>{forkname}</title>
+</head>
+<body>
+<h1>{forkname} <small>(fork of <a href="../../">{reponame}</a>, found at {forkuri})</small></h1>
+""",
     "foot": "</body></html>\n",
     "screenstyle": """ """,
     "printstyle": """ """,
@@ -120,7 +131,20 @@ def writeoverview(ui, repo, target, name
             overview += "</div>"
             break
     # now the links to the log and the files.
-    overview += "\n<p id='nav'><a href='commits'>changelog</a> | <a href='src/" + repo["tip"].hex() + "/'>files</a></p>"
+    overview += "\n<p id='nav'><a href='commits'>changelog</a> | <a href='src/" + repo["tip"].hex() + "/'>files</a>"
+    # and the forks
+    forks = getforkinfo(ui, target)
+    if forks:
+        overview += " | " + _("forks: ")
+        for forkname, forkuri in forks.items():
+            overview += "<a href='" + getforkdir(target, forkname) + "'>" + forkname + "</a> "
+            incoming, fn = getincoming(ui, repo, forkuri)
+            overview += "<small>(" + str(len(incoming))
+            outgoing, fn = getoutgoing(ui, repo, forkuri)
+            overview += "<small>↓↑</small>" + str(len(outgoing)) + ")</small> "
+
+    overview += "</p>"
+
     # now add the 5 most recent log entries
     # divert all following ui output to a string, so we can just use standard functions
     overview += "\n<div id='shortlog'><h2>Changes (<a href='commits'>full changelog</a>)</h2>\n"
@@ -231,6 +255,105 @@ def writelog(ui, repo, target, name):
             with open(filepath, "w") as f: 
                 f.write(data)
 
+def getincoming(ui, repo, otheruri, other=None):
+    if not other:
+        other = hg.peer(repo, {}, otheruri)
+    ui.pushbuffer() # ignore ui events
+    source, branches = hg.parseurl(otheruri, None)
+    revs, checkout = hg.addbranchrevs(repo, other, branches, None)
+    if revs:
+        revs = [other.lookup(rev) for rev in revs]
+    other, chlist, cleanupfn = hg.bundlerepo.getremotechanges(ui, repo, other,
+                                                               revs, False, False)
+    ui.popbuffer()
+    return chlist, cleanupfn
+
+def getoutgoing(ui, repo, otheruri, other=None):
+    if not other: 
+        other = hg.peer(repo, {}, otheruri)
+    other.ui.pushbuffer() # ignore ui events
+    source, branches = hg.parseurl(repo.root, None)
+    revs, checkout = hg.addbranchrevs(other, repo, branches, None)
+    if revs:
+        revs = [repo.lookup(rev) for rev in revs]
+    other, chlist, cleanupfn = hg.bundlerepo.getremotechanges(ui, other, repo,
+                                                               revs, False, False)
+    return chlist, cleanupfn
+
+
+def getforkinfo(ui, target):
+    """Name and Uri of all forks."""
+    forks = dict(ui.configitems("paths"))
+    forkinfo = {}
+    for forkname, forkuri in forks.items():
+        # ignore the static repo
+        if os.path.abspath(forkuri) == os.path.abspath(target):
+            continue
+        forkinfo[forkname] = forkuri
+    return forkinfo
+
+def getforkdata(ui, repo, target, name, forkname, forkuri):
+    """Write the site for a single fork."""
+    # make sure the forkdir exists.
+    other = hg.peer(repo, {}, forkuri)
+
+    # incrementally build the html
+    html = templates["forkhead"].replace(
+            "{forkname}", forkname).replace(
+        "{reponame}", name).replace(
+                "{forkuri}", util.hidepassword(forkuri))
+
+    # prepare the log templater
+    t = cmdutil.changeset_templater(ui, repo, patch=False, diffopts=None, mapfile=None, buffered=False)
+    t.use_template(templates["commitlog"].replace(
+            "{relativepath}", "../"))
+
+    # Add incoming commits
+    html += "<div id='incoming'><h2>Incoming commits</h2>"
+    chlist, cleanupfn = getincoming(ui, repo, forkuri, other=other)
+    
+    ui.pushbuffer()
+    for ch in chlist:
+        ctx = other.changectx(ch)
+        t.show(ctx)
+    html += ui.popbuffer()
+    cleanupfn()
+
+    # add outgoing commits
+    html += "<div id='outgoing'><h2>Outgoing commits</h2>"
+    chlist, cleanupfn = getoutgoing(ui, repo, forkuri, other=other)
+
+    ui.pushbuffer()
+    for ch in chlist:
+        ctx = repo.changectx(ch)
+        t.show(ctx)
+    html += ui.popbuffer()
+    cleanupfn()
+    
+    html += "</div>"
+    html += templates["foot"]
+    return html
+
+def getforkdir(target, forkname):
+    return join("forks", forkname)
+
+def writeforks(ui, repo, target, name):
+    """Write an info-page for each fork, defined in hg paths.
+
+    relevant data: incoming commits, outgoing commits, branches and bookmarks not in fork or not in repo. Short: incoming (commits, branches, bookmarks), outgoing (incoming first means, we consider this repo to be the main repo).
+    """
+    forkinfo = getforkinfo(ui, target)
+    for forkname, forkuri in forkinfo.items():
+        # ignore the static repo itself
+        if os.path.abspath(forkuri) == os.path.abspath(target):
+            continue
+        forkdir = getforkdir(target, forkname)
+        if not isdir(join(target, forkdir)):
+            os.makedirs(join(target, forkdir))
+        with open(join(target, forkdir, "index.html"), "w") as f:
+            f.write(
+                getforkdata(ui, repo, target, name, forkname, forkuri))
+
 
 def writecommits(ui, repo, target, name, force=False):
     """Write all not yet existing commit files."""
@@ -391,6 +514,9 @@ def parsesite(ui, repo, target, **opts):
     # and all file data
     writesourcetree(ui, repo, target, name, force=opts["force"])
 
+    # and all forks
+    writeforks(ui, repo, target, name)
+
 
 def addrepo(ui, repo, target, bookmarks):
     """Add the repo to the target and make sure it is up to date."""
@@ -527,7 +653,6 @@ def staticsite(ui, repo, target=None, **
         upload(ui, repo, target, opts["upload"], opts["force"])
 
 
-
 cmdtable = {
     # "command-name": (function-call, options-list, help-string)
     "site": (staticsite,