hg site extension
 
(Arne Babenhauserheide)
2012-10-30: merged default into stable for release. releases 0.2

merged default into stable for release.

diff --git a/.bugs/bugs b/.bugs/bugs
--- a/.bugs/bugs
+++ b/.bugs/bugs
@@ -8,14 +8,16 @@ get mtimes from the repo instead of quer
 FIX: revision 0 is omitted: change that :)                   | owner:Arne Babenhauserheide <bab@draketo.de>, open:False, id:29396f1753e45b5a37ffa0ce04d96c876d6b6722, time:1319209563.68
 parse the pushed repo, not the local one.                    | owner:Arne Babenhauserheide <bab@draketo.de>, open:True, id:2c26d62b62e3656ebcce43e7a24f627594911fb5, time:1322115065.37
 revisions more structured, as list or similar.               | owner:Arne Babenhauserheide <bab@draketo.de>, open:True, id:39cacfc83da6f6beecafdb745823d7f01018206b, time:1322159357.88
+tags do not have a heading on the overview anymore           | owner:, open:True, id:485d0b451f18b5eae517b34466622b0d52340d8e, time:1351620233.63
 only write .statichgrepo print.css style.css and index.html when their data changed \(or \-\-force\) → read them and compare the contents. | owner:Arne Babenhauserheide <bab@draketo.de>, open:False, id:4f02149269a60fca85aa040116b2789d98c906f2, time:1319212903.98
 sort tags in reverse order                                   | owner:Arne Babenhauserheide <bab@draketo.de>, open:True, id:61d256ab154e64597be604d6298daa545d4a96c7, time:1322159250.01
 add proper caching of every ftp directory listing.           | owner:Arne Babenhauserheide <bab@draketo.de>, open:False, id:750692931106d78ffc38c1ed63013c4dac4099dd, time:1319175393.07
-fork-/clone-info for each entry in [paths] with its incoming data (if it has some): | owner:, open:True, id:8621575e4016752e8987c8b294dfa9166f77eff3, time:1319147671.39
+fork-/clone-info for each entry in [paths] with its incoming data (if it has some): | owner:, open:False, id:8621575e4016752e8987c8b294dfa9166f77eff3, time:1319147671.39
+partial localization of strings - files to Dateien - is worse than none. | owner:Arne Babenhauserheide <bab@draketo.de>, open:True, id:921fb80eb2a2fbf57e0f6db0a949ead872624f8f, time:1351635128.79
 More complex Readme parsing.                                 | owner:Arne Babenhauserheide <bab@draketo.de>, open:True, id:94fbade896adbf6f696cfdb331021437dff3f30e, time:1319147671.39
-make the link from the /commit/*.html pages to the /src/*/[index.html] pages more obvious | owner:Arne Babenhauserheide <bab@draketo.de>, open:True, id:ad936eaaba1693f7c44bd59916a19e6f3b3db27e, time:1319209748.93
+make the link from the /commit/*.html pages to the /src/*/[index.html] pages more obvious | owner:Arne Babenhauserheide <bab@draketo.de>, open:False, id:ad936eaaba1693f7c44bd59916a19e6f3b3db27e, time:1319209748.93
 cache FTP directory listings for much faster upload of already existing sites. | owner:Arne Babenhauserheide <bab@draketo.de>, open:False, id:b1f6dfdaccc0346adaf0d42d361bbc2de00ee176, time:1319208814.12
-Idea: hg clone/push ftp://host.tld/path/to/repo → hg site --upload | owner:Arne Babenhauserheide <bab@draketo.de>, open:True, id:b4693d2677e0a2b4ef9ee5dfbbe8c4742924604c, time:1319147779.76
+Idea: hg clone/push ftp://host.tld/path/to/repo → hg site --upload | owner:Arne Babenhauserheide <bab@draketo.de>, open:False, id:b4693d2677e0a2b4ef9ee5dfbbe8c4742924604c, time:1319147779.76
 add linenumbers to the src files.                            | owner:Arne Babenhauserheide <bab@draketo.de>, open:True, id:b7bab6f46da7d224f97d0dac55a617d3a464d301, time:1319147678.56
 commits as commit/<rev>/ for long term viability. .html as suffix is not as long lived as a simple dirname. | owner:Arne Babenhauserheide <bab@draketo.de>, open:True, id:baaad4bdc13d7155048ce6a9dde92dc857b6a1ac, time:1319148414.16
 clone/<pathname>/ → incoming log (commits) + possibly an associated issue in b. | owner:Arne Babenhauserheide <bab@draketo.de>, open:True, id:c58557260a47597ac5057703e26a94df190a2a5d, time:1319147661.8
@@ -24,4 +26,5 @@ check the hgweb templating for parsing t
 maybe more advanced bookmarks pushing.                       | owner:Arne Babenhauserheide <bab@draketo.de>, open:True, id:ef8c12bfcc99686efc1f685a9be0be0c78922ca5, time:1322115049.48
 crashes on missing readme.                                   | owner:Arne Babenhauserheide <bab@draketo.de>, open:True, id:f4232c8a52fff730a4c63525ad597c063135e576, time:1332936115.69
 Treat branch heads specially: link on the main page.         | owner:Arne Babenhauserheide <bab@draketo.de>, open:True, id:f531c27b38f9ea1749ded312f4f468c9ac33b930, time:1319147696.96
+do not show closed branches                                  | owner:Arne Babenhauserheide <bab@draketo.de>, open:True, id:fbae7e95ed65dc314770126b614fe40d52e61f89, time:1351633799.8
 allow setting user, password, server and path vie .hg/hgrc.  | owner:Arne Babenhauserheide <bab@draketo.de>, open:True, id:fc575156316d56b70fda64725984b66cc8a2cfde, time:1322118134.69
diff --git a/README.txt b/README.txt
--- a/README.txt
+++ b/README.txt
@@ -4,13 +4,13 @@ Create and/or upload a static copy of th
 
 You can get it via `hg clone http://draketo.de/proj/hgsite/`
 
-The main goal is sharing Mercurial on servers with only FTP access and
-statically served files, while providing the same information as hg
-serve and full solutions like bitbucket and gitorious (naturally
-without the interactivity).
+The main goal is sharing code with Mercurial on servers who only offer
+FTP access and statically served files, while providing the same
+information as hg serve and full solutions like bitbucket and
+gitorious (naturally without the interactivity).
 
-Only changed files are uploaded, based on the time they were last
-modified, so uploads can be reasonably fast.
+On upload, only changed files are uploaded, based on the time they
+were last modified, so uploads can be reasonably fast.
 
 Install:
 
@@ -22,10 +22,13 @@ Install:
 
 Usage:
 
-$ hg site [-n sitename] -u user:password@ftp.host.tld/path/to/dir
-→ that’s how this site gets created.
+$ hg push [-f] --sitename "sitename" ftp://user:password@ftp.host.tld/path/to/dir
+→ that’s how this site gets created. 
+  With -f it is reuploaded completely, 
+  otherwise only the changes get uploaded.
+  defining the ftp://… in [paths] in .hg/hgrc works.
 
-To upload the site when you push, you can use a hook in .hg/hgrc. This is what I use: 
+To upload the site when you push it anywhere, you can use a hook in .hg/hgrc. This is what I use: 
 
     [hooks]
     post-push = hg site -n site -u user:password@ftp.host.tld/path/to/dir
diff --git a/staticsite.py b/staticsite.py
--- a/staticsite.py
+++ b/staticsite.py
@@ -11,7 +11,7 @@ serve and full solutions like bitbucket 
 without the interactivity).
 """
 
-__copyright__ = """Copyright 2011 Arne Babenhauserheide
+__copyright__ = """Copyright 2012 Arne Babenhauserheide
 
 This software may be used and distributed according to the terms of the
 GNU General Public License version 2 or any later version.
@@ -65,7 +65,20 @@ templates = {
 <h1>{forkname} <small>(fork of <a href="../../">{reponame}</a>, found at {forkuri})</small></h1>
 """,
     "foot": "</body></html>\n",
-    "screenstyle": """ """,
+    "screenstyle": """ 
+.bugnumbers {
+    font-size: x-small;
+    vertical-align: super;
+}
+.openbugnumber, .openbugnumber a {
+    color: #f00;
+    text-decoration: none;
+}
+.resolvedbugnumber, .resolvedbugnumber a {
+    color: #00f;
+    text-decoration: none;
+}
+""",
     "printstyle": """ """,
     "manifesthead": """<h2>""" + _("Commit (click to see the diff)")+""": <a href='../../commit/{hex}.html'>{hex}</a></h2>
 <p>{desc}</p><p>{user}</p>
@@ -95,6 +108,76 @@ def contentequals(filepath, content):
     except IOError: return False # file does not exist. Empty != not existing.
     # TODO: check: return True if content is None?
 
+def bisenabled():
+    """Check if the b extension is enabled to decide if we want to add
+    a bug listing."""
+    enabled = extensions.enabled()
+    if "b" in enabled:
+        return True
+
+def splitbugline(line):
+    """Split a b extension bug line into the ID and the description."""
+    try:
+        bugid = line.split("-")[0].strip()
+    except IndexError:
+        return "", line
+    description = "".join(line.split("-")[1:]).lstrip()
+    return bugid, description
+
+def getbugdetails(ui, repo, bugid):
+    """Get the details for a bug."""
+    # first get the details
+    ui.pushbuffer()
+    req = dispatch.request(["b", "details", bugid], ui=ui, repo=repo)
+    dispatch.dispatch(req)
+    return ui.popbuffer()
+
+def getbugfullid(details, bugid):
+    """Get the real ID of a bug from its detailed info. If it’s not available, just give the short bugid"""
+    try:
+        idline = [i for i in details.splitlines() if i.startswith("ID: ")][0]
+    except IndexError: # no id line
+        return bugid
+    realid = idline[4:].strip()
+    return realid
+
+class BBug(object):
+    """A b-extension bug."""
+    def __init__(self, shortid, fullid, description, state, details=""):
+        self.shortid, self.fullid, self.description, self.state, self.details = shortid, fullid, description, state, details
+
+def getbuginfo(ui, repo, bugline):
+    """Get information about a bug from its bugline."""
+    shortid, description = splitbugline(bugline)
+    details = getbugdetails(ui, repo, shortid)
+    fullid = getbugfullid(details, shortid)
+    return shortid, fullid, description, details
+
+def getbugs(ui, repo):
+    """Get all bugs."""
+    if not bisenabled():
+        return [], []
+    # run the b command to get all open bugs
+    ui.pushbuffer()
+    req = dispatch.request(["b"], ui=ui, repo=repo)
+    dispatch.dispatch(req)
+    openbuglines = [line for line in ui.popbuffer().splitlines() if "-" in line]
+    # similarly get all resolved bugs
+    ui.pushbuffer()
+    req = dispatch.request(["b", "list", "-r"], ui=ui, repo=repo)
+    dispatch.dispatch(req)
+    resolvedbuglines = [line for line in ui.popbuffer().splitlines() if "-" in line]
+    # now turn them into a list of bugs
+    openbugs = []
+    for bugline in openbuglines:
+        bugid, fullid, description, details = getbuginfo(ui, repo, bugline)
+        openbugs.append(BBug(bugid, fullid, description, "open", details))
+    resolvedbugs = []
+    for bugline in resolvedbuglines:
+        bugid, fullid, description, details = getbuginfo(ui, repo, bugline)
+        resolvedbugs.append(BBug(bugid, fullid, description, "resolved", details))
+    return openbugs, resolvedbugs
+
 def parsereadme(filepath, truncated=False):
     """Parse the readme file"""
     with open(filepath) as r:
@@ -114,7 +197,6 @@ def overviewlogstring(ui, repo, revs, te
         t.show(ctx)
     return ui.popbuffer()
 
-
 def writeoverview(ui, repo, target, name):
     """Create the overview page"""
     overview = ""
@@ -132,7 +214,17 @@ 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>"
+    overview += "\n<p id='nav'><a href='commits'>" + _("changelog") + "</a> | <a href='src/" + repo["tip"].hex() + "/'>" + _("files") + "</a>"
+    # and the bugs
+    openbugs, resolvedbugs = getbugs(ui, repo)
+    if openbugs or resolvedbugs:
+        overview += " | <a href=\"bugs\">" + _("bugs") + "</a>"
+        if openbugs:
+            overview += " <span class=\"bugnumbers\">(<span class=\"openbugnumber\"><a href=\"bugs#open\">" + str(len(openbugs)) + "!</a></span> "
+        else:
+            overview += " <span class=\"bugnumber openbugnumberzero\">0</span>"
+        overview += "<span class=\"bugnumber resolvedbugnumber\"><a href=\"bugs#resolved\">" + str(len(resolvedbugs)) + "√</a></span>)</span>"
+        
     # and the forks
     forks = getforkinfo(ui, target)
     if forks:
@@ -143,9 +235,9 @@ def writeoverview(ui, repo, target, name
             overview += "<small>(" + str(len(incoming))
             outgoing, fn, localother = getoutgoing(ui, repo, otheruri=forkuri, othername=forkname)
             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"
@@ -163,8 +255,9 @@ def writeoverview(ui, repo, target, name
     for branch, heads in repo.branchmap().items(): 
         if branch and branch != "default": # not default
             branches.extend(heads)
-
+            
     try: 
+        # FIXME: For some reason this does not seem to give the tags anymore.
         tags = repo._tags
     except AttributeError: 
         tags = []
@@ -203,7 +296,7 @@ def writeoverview(ui, repo, target, name
     overview += "<div id='readme'><h2>"+_("Readme")+"</h2>\n"
     overview += readme
     overview += "</div>"
-
+    
     # finish the overview
     overview += templates["foot"]
     indexfile = os.path.join(target, "index.html")
@@ -214,7 +307,7 @@ def writeoverview(ui, repo, target, name
 def writelog(ui, repo, target, name):
     """Write the full changelog, in steps of 100."""
     commits = os.path.join(target, "commits")
-
+    
     # create the folders
     if not os.path.isdir(commits):
         os.makedirs(commits)
@@ -222,7 +315,7 @@ def writelog(ui, repo, target, name):
         d = commits+"-"+str(i+1)+"00"
         if not os.path.isdir(d):
             os.makedirs(d)
-
+            
     # create the log files
     t = cmdutil.changeset_templater(ui, repo, patch=False, diffopts=None, mapfile=None, buffered=False)
     t.use_template(templates["commitlog"].replace("{relativepath}", "../"))
@@ -243,13 +336,13 @@ def writelog(ui, repo, target, name):
         else:
             d = commits
             logs.append([os.path.join(d, "index.html"), ""])
-
+            
         logs[-1][-1] += templates["head"].replace("{reponame}", "<a href='../'>"+name+"</a>").replace("{title}", name)
         for c in range(ck*100+1, min(len(repo.changelog)+1, (ck+1)*100)):
             ctx = repo.changectx(str(-c))
             t.show(ctx)
         logs[-1][-1] += ui.popbuffer()
-
+        
     for filepath,data in logs:
         data += templates["foot"].replace("{reponame}", "<a href='../'>"+name+"</a>")
         if not contentequals(filepath,data): 
@@ -258,7 +351,7 @@ def writelog(ui, repo, target, name):
 
 def getlocalother(repo, ui, otheruri, othername):
     """Get a local clone of the repo identified by uri and name within .hg/paths.
-
+    
     This creates that local clone!
     """
     # if we cannot get the changes via bundlerepo, we create a
@@ -296,7 +389,7 @@ def getincoming(ui, repo, otheruri, othe
     if isftpuri or isfreenetpriv:
         chlist = []
         return chlist, cleanupfn, other
-
+    
     if not other:
         other = hg.peer(repo, {}, otheruri)
     ui.pushbuffer() # ignore ui events
@@ -326,10 +419,10 @@ def getoutgoing(ui, repo, otheruri, othe
     if isftpuri or isfreenetpriv:
         chlist = []
         return chlist, cleanupfn, other
-
+    
     if not other: 
         other = hg.peer(repo, {}, otheruri)
-
+        
     def outgoingchanges(repo, other):
         from mercurial import discovery
         fco = discovery.findcommonoutgoing
@@ -342,7 +435,7 @@ def getoutgoing(ui, repo, otheruri, othe
             return o
             
     other.ui.pushbuffer() # ignore ui events
-
+    
     try:
         chlist = outgoingchanges(repo, other)
     except (AttributeError, util.Abort):
@@ -350,11 +443,10 @@ def getoutgoing(ui, repo, otheruri, othe
         other = getlocalother(repo, ui, otheruri, othername)
         other.ui.pushbuffer()
         chlist = outgoingchanges(repo, other)
-
+        
     other.ui.popbuffer()
     return chlist, cleanupfn, other
 
-
 def getforkinfo(ui, target):
     """Name and Uri of all forks."""
     forks = dict(ui.configitems("paths"))
@@ -378,18 +470,18 @@ def getforkdata(ui, repo, target, name, 
     """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}", safeuri(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, localother = getincoming(ui, repo, otheruri=forkuri, other=other, othername=forkname)
@@ -400,11 +492,11 @@ def getforkdata(ui, repo, target, name, 
         t.show(ctx)
     html += ui.popbuffer()
     cleanupfn()
-
+    
     # add outgoing commits
     html += "<div id='outgoing'><h2>Outgoing commits</h2>"
     chlist, cleanupfn, localother = getoutgoing(ui, repo, forkuri, other=other, othername=forkname)
-
+    
     ui.pushbuffer()
     for ch in chlist:
         ctx = repo.changectx(ch)
@@ -421,7 +513,7 @@ def getforkdir(target, 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)
@@ -436,15 +528,14 @@ def writeforks(ui, repo, target, name):
             f.write(
                 getforkdata(ui, repo, target, name, forkname, forkuri))
 
-
 def writecommits(ui, repo, target, name, force=False):
     """Write all not yet existing commit files."""
     commit = os.path.join(target, "commit")
-
+    
     # create the folders
     if not os.path.isdir(commit):
         os.makedirs(commit)
-
+        
     t = cmdutil.changeset_templater(ui, repo, patch=False, diffopts=None, mapfile=None, buffered=False)
     t.use_template(templates["commitlog"].replace("{relativepath}", "../"))
     for c in range(len(repo.changelog)):
@@ -462,6 +553,54 @@ def writecommits(ui, repo, target, name,
             cf.write("<pre>"+ui.popbuffer().replace("<", "<")+"</pre>")
             cf.write(templates["foot"].replace("{reponame}", "<a href='../'>"+name+"</a>"))
 
+#: html escape codes thanks to http://wiki.python.org/moin/EscapingHtml
+htmlescapetable = {
+    "&": "&",
+    '"': """,
+    "'": "'",
+    ">": ">",
+    "<": "<",
+    }
+
+def htmlescape(text):
+    """Produce entities within text."""
+    return "".join(htmlescapetable.get(c,c) for c in text)
+
+def writebugs(ui, repo, target, name):
+    """Write bug information, a listing and the details for each bug."""
+    bugdir = os.path.join(target, "bugs")
+    
+    # create the bugs folder
+    if not os.path.isdir(bugdir):
+        os.makedirs(bugdir)
+        
+    # get all bugs
+    openbugs, resolvedbugs = getbugs(ui, repo)
+    # write the bugs list
+    bugslist = os.path.join(bugdir, "index.html")
+    content = "<h2 id=\"open\">Open Bugs</h2>\n<ul>"
+    for bug in openbugs:
+        content += "<li><a href=\"" + bug.fullid + ".html\">" + bug.shortid + "</a> - " + htmlescape(bug.description) + "</li>\n"
+    content += "</ul>\n"
+    content += "<h2 id=\"resolved\">Resolved Bugs</h2>\n<ul>"
+    for bug in resolvedbugs:
+        content += "<li><a href=\"" + bug.fullid + ".html\">" + bug.shortid + "</a> - " + htmlescape(bug.description) + "</li>\n"
+    content += "</ul>\n"
+    with open(bugslist, "w") as f:
+        f.write(templates["head"].replace("{reponame}", "<a href='../'>"+name+"</a>").replace("{title}", name))
+        f.write(content)
+        f.write(templates["foot"].replace("{reponame}", "<a href='../'>"+name+"</a>"))
+    # write all bug details
+    for bug in openbugs + resolvedbugs:
+        bugsfile = bugslist = os.path.join(bugdir, bug.fullid + ".html")
+        content = "<h2>" + bug.description + "</h2>\n"
+        content += "<pre>" + bug.details + "</pre>\n"
+        content += "<hr>"
+        content += "- <a href=\"index.html\">" + _("all bugs") + "</a> -"
+        with open(bugsfile, "w") as bf:
+            bf.write(templates["head"].replace("{reponame}", "<a href='../'>"+name+"</a>").replace("{title}", name))
+            bf.write(content)
+            bf.write(templates["foot"].replace("{reponame}", "<a href='../'>"+name+"</a>"))
 
 def escapename(filename):
     """escape index.html as .index.html and .ind… as ..ind… and so fort."""
@@ -469,7 +608,6 @@ def escapename(filename):
         return "." + filename
     else: return filename
 
-
 def parsesrcdata(data):
     """Parse a src file into a html file."""
     return "<pre>"+data.replace("<", "<")+"</pre>"
@@ -484,7 +622,10 @@ def rawpath(target, ctx, filename):
 
 def ctxdiffstat(ui, repo, ctx):
     """Get the diffstat of a change context."""
-    command = "log -r " + ctx.hex() + " --stat --color=never"
+    if "color" in extensions.enabled():
+        command = "log -r " + ctx.hex() + " --stat --color=never"
+    else:
+        command = "log -r " + ctx.hex() + " --stat"
     req = dispatch.request(command.split(), ui=ui, repo=repo)
     ui.pushbuffer()
     dispatch.dispatch(req)
@@ -496,6 +637,7 @@ def ctxdiffstat(ui, repo, ctx):
             "[0;31m", "").replace(
                 "","")
     
+
 def createindex(ui, repo, target, ctx):
     """Create an index page for the changecontext: the commit message + the user + all files in the changecontext."""
     # first the head
@@ -515,7 +657,7 @@ def createindex(ui, repo, target, ctx):
 
 def writesourcetree(ui, repo, target, name, force, rawfiles=False):
     """Write manifests for all commits and websites for all files.
-
+    
     * For each file, write sites for all revisions where the file was changed: under src/<hex>/path as html site (with linenumbers and maybe colored source), under raw/<hex>/<path> as plain files. If there is an index.html file, write it as .index.html. If there also is .index.html, turn it to ..index.html, …
     * For each commit write an index with links to the included files at their latest revisions before/at the commit.
     """
@@ -577,12 +719,12 @@ def parsesite(ui, repo, target, **opts):
                 return
             with open(idfile, "w") as i:
                 i.write("")
-
+                
     if opts["sitename"]:
         name = opts["sitename"]
     elif target != "static": name = target
     else: name = os.path.basename(repo.root)
-
+    
     # first the stylesheets
     screenstyle = opts["screenstyle"]
     screenfile = os.path.join(target, "style.css")
@@ -598,22 +740,24 @@ def parsesite(ui, repo, target, **opts):
     elif not contentequals(printfile, templates["printstyle"]):
         with open(printfile, "w") as f:
             f.write(templates["printstyle"])
-
+            
     # then the overview
     writeoverview(ui, repo, target, name)
-
+    
     # and the log
     writelog(ui, repo, target, name)
-
+    
     # and all commit files
     writecommits(ui, repo, target, name, force=opts["force"])
-
+    
     # and all file data
     writesourcetree(ui, repo, target, name, force=opts["force"])
-
+    
     # and all forks
     writeforks(ui, repo, target, name)
-
+    
+    # and all bugs
+    writebugs(ui, repo, target, name)
 
 def addrepo(ui, repo, target, bookmarks, force):
     """Add the repo to the target and make sure it is up to date."""
@@ -622,7 +766,7 @@ def addrepo(ui, repo, target, bookmarks,
     except mercurial.error.RepoError, e:
         # already exists
         pass
-
+    
     ui.pushbuffer()
     if bookmarks: 
         commands.push(ui, repo, dest=target, bookmark=repo._bookmarks, force=force)
@@ -630,7 +774,6 @@ def addrepo(ui, repo, target, bookmarks,
         commands.push(ui, repo, dest=target, force=force)
     ui.popbuffer()
 
-
 def upload(ui, repo, target, ftpstring, force):
     """upload the repo to the FTP server identified by the ftp string."""
     try:
@@ -648,9 +791,9 @@ def upload(ui, repo, target, ftpstring, 
     except socket.timeout:
         ui.warn(_("connection to "), server, _(" timed out after "), timeout, _(" seconds.\n"))
         return
-
+    
     ui.status(ftp.getwelcome(), "\n")
-
+    
     # create the target dir.
     serverdir = os.path.dirname(ftppath)
     serverdirparts = ftppath.split("/")
@@ -662,26 +805,26 @@ def upload(ui, repo, target, ftpstring, 
         sd = os.path.join(sd, sdp)
         if not sd in ftp.nlst(sdo):
             ftp.mkd(sd)
-
-
+            
+            
     ftp.cwd(ftppath)
     if not ftp.pwd() == "/" + ftppath:
         ui.warn(_("not in the correct ftp directory. Cowardly bailing out.\n"))
         return
-
+    
     #ftp.dir()
     #return
     ftpfeatures = ftp.sendcmd("FEAT")
     featuremtime = " MDTM" in ftpfeatures.splitlines()
     _ftplistcache = set()
-
+    
     for d, dirnames, filenames in os.walk(target):
         for filename in filenames:
             localfile = os.path.join(d, filename)
             serverfile = localfile[len(target)+1:]
             serverdir = os.path.dirname(serverfile)
             serverdirparts = serverdir.split("/")
-#            print serverdirparts, serverfile
+            # print serverdirparts, serverfile
             with open(localfile, "rb") as f:
                 sd = serverdirparts[0]
                 if sd and not sd in _ftplistcache: # should happen only once per superdir
@@ -694,7 +837,7 @@ def upload(ui, repo, target, ftpstring, 
                     except ftplib.error_perm, resp:
                         ui.warn(_("could not create directory "), sd, ": " , resp, "\n")
                     else: _ftplistcache.add(sd)
-
+                    
                 for sdp in serverdirparts[1:]:
                     sdold = sd
                     sd = os.path.join(sd, sdp)
@@ -710,7 +853,7 @@ def upload(ui, repo, target, ftpstring, 
                         except ftplib.error_perm, resp:
                             ui.warn(_("could not create directory "),
                                     sd, ": " , resp, "\n")
-
+                            
                 if not serverfile in _ftplistcache: # should happen for existing files only once per dir.
                     _ftplistcache.update(set(ftp.nlst(serverdir)))
                 if not serverfile in _ftplistcache or force:
@@ -720,7 +863,7 @@ def upload(ui, repo, target, ftpstring, 
                     else:
                         ui.status(_("uploading "), serverfile,
                                   _(" because it is not yet online.\n"))
-
+                        
                     ftp.storbinary("STOR "+ serverfile, f)
                 else:
                     # reupload the file if the file on the server is older than the local file.
@@ -734,8 +877,6 @@ def upload(ui, repo, target, ftpstring, 
                                       _(" because it is newer than the file on the FTP server.\n"))
                             ftp.storbinary("STOR "+ serverfile, f)
 
-
-
 def staticsite(ui, repo, target=None, **opts):
     """Create a static copy of the repository and/or upload it to an FTP server."""
     if repo.root == target:
@@ -754,7 +895,6 @@ def staticsite(ui, repo, target=None, **
         # upload the repo
         upload(ui, repo, target, opts["upload"], opts["force"])
 
-
 cmdtable = {
     # "command-name": (function-call, options-list, help-string)
     "site": (staticsite,
@@ -912,10 +1052,10 @@ def test():
     showcall(["hg", "--config", "extensions.site="+__file__, "site", "-B", "-n", "mysite"])
     # check if uploading works: Only a valid test, if you have a
     # post-push hook which does the uploading
-    showcall(["hg", "--config", "extensions.site="+__file__, "push"])
+    # showcall(["hg", "--config", "extensions.site="+__file__, "push"])
     # check if push directly to ftp works. Requires the path draketo
     # to be set up in .hg/hgrc as ftp://user:password/path
-    showcall(["hg", "--config", "extensions.site="+__file__, "push", "draketo", "--sitename", "site extension"])
+    # showcall(["hg", "--config", "extensions.site="+__file__, "push", "draketo", "--sitename", "hg site extension"])
 
 if __name__ == "__main__":
     test()