Initial import.
Fri Jul 1 02:55:14 UTC 2005 Alberto Bertogli <albertogli@telpin.com.ar>
* Initial import.
diff -rN -u old-darcsweb/README new-darcsweb/README
--- old-darcsweb/README 1970-01-01 00:00:00.000000000 +0000
+++ new-darcsweb/README 2013-07-15 17:00:12.000000000 +0000
@@ -0,0 +1,16 @@
+
+darcsweb - A web interface for darcs
+Alberto Bertogli (albertogli@telpin.com.ar)
+-------------------------------------------
+
+This is a very simple web interface for darcs, inspired in gitweb (written by
+Kay Sievers and Christian Gierke).
+
+To configure, copy the "config.py.sample" file to "config.py" and edit it; you
+will configure your repositories there. Then just browse to the cgi file.
+
+If you have any question, suggestions or comments, please let me know.
+
+Thanks,
+ Alberto
+
diff -rN -u old-darcsweb/TODO new-darcsweb/TODO
--- old-darcsweb/TODO 1970-01-01 00:00:00.000000000 +0000
+++ new-darcsweb/TODO 2013-07-15 17:00:12.000000000 +0000
@@ -0,0 +1,7 @@
+
+* export plains (done, needs to be added to the interface)
+* be able to see a file tree at any point of time/patchflow
+ * we need darcs support for this, which is currently not available
+ * when this is done, show tags and allow displaying of blobs
+* annotate
+
diff -rN -u old-darcsweb/config.py.sample new-darcsweb/config.py.sample
--- old-darcsweb/config.py.sample 1970-01-01 00:00:00.000000000 +0000
+++ new-darcsweb/config.py.sample 2013-07-15 17:00:12.000000000 +0000
@@ -0,0 +1,54 @@
+
+# base configuration, common to all repos
+class base:
+ # this script's name, usually just "darcsweb.cgi" unless you rename it
+ myname = "darcsweb.cgi"
+
+ # our url, used only to generate RSS links, without the script name
+ myurl = "http://albertito.homeip.net:8026/darcsweb"
+
+ # location of the darcs logo
+ darcslogo = "darcs.png"
+
+ # location of the darcs favicon
+ darcsfav = "minidarcs.png"
+
+ # the CSS file to use
+ cssfile = 'style.css'
+
+
+#
+# From now on, every class is a repo configuration, with the same format
+# There are no restrictions on the class' name, except that it can't be named
+# "base" (because it's the name of the one avobe).
+#
+
+class repo1:
+ # the descriptive name
+ reponame = 'repo1'
+
+ # a brief description
+ repodesc = 'Example repository'
+
+ # the real path to the repository
+ repodir = '/usr/src/repo1'
+
+ # an url so people know where to do "darcs get" from
+ repourl = 'http://auriga.wearlab.de/~alb/repos/repo1'
+
+ # the encoding used in the repo
+ # NOTE: if you use utf8, you _must_ write 'utf8' (and not the variants
+ # like 'utf-8' or 'UTF8') if you expect darcsweb to work properly.
+ # This is because to workaround a bug in darcs we need to do some
+ # codec mangling and it needs special cases for UTF8.
+ repoencoding = "latin1"
+
+
+class repo2:
+ reponame = 'repo2'
+ repodesc = 'Second example repository'
+ repodir = '/usr/src/repo2'
+ repourl = 'http://auriga.wearlab.de/~alb/repos/repo2'
+ repoencoding = 'latin1'
+
+
Binary files old-darcsweb/darcs.png and new-darcsweb/darcs.png differ
diff -rN -u old-darcsweb/darcsweb.cgi new-darcsweb/darcsweb.cgi
--- old-darcsweb/darcsweb.cgi 1970-01-01 00:00:00.000000000 +0000
+++ new-darcsweb/darcsweb.cgi 2013-07-15 17:00:12.000000000 +0000
@@ -0,0 +1,1237 @@
+#!/usr/bin/env python
+
+"""
+darcsweb - A web interface for darcs
+Alberto Bertogli (albertogli@telpin.com.ar)
+
+Inspired on gitweb (as of 28/Jun/2005), which is written by Kay Sievers
+<kay.sievers@vrfy.org> and Christian Gierke <ch@gierke.de>
+"""
+
+import sys
+import os
+import string
+import time
+import stat
+import cgi
+import cgitb; cgitb.enable()
+import xml.sax
+from xml.sax.saxutils import escape
+
+
+# empty configuration class, we will fill it in later depending on the repo
+class config:
+ pass
+
+
+#
+# utility functions
+#
+
+def filter_num(s):
+ l = [c for c in s if c in string.digits]
+ return string.join(l, "")
+
+
+allowed_alphanum = string.ascii_letters + string.digits
+def filter_an(s):
+ l = [c for c in s if c in allowed_alphanum]
+ return string.join(l, "")
+
+
+allowed_in_hash = string.ascii_letters + string.digits + '-.'
+def filter_hash(s):
+ l = [c for c in s if c in allowed_in_hash]
+ return string.join(l, "")
+
+
+def filter_file(s):
+ if '..' in s:
+ raise 'FilterFile FAILED'
+ if s == '/':
+ return s
+
+ # remove extra "/"s
+ r = s[0]
+ last = s[0]
+ for c in s[1:]:
+ if c == last and c == '/':
+ continue
+ r += c
+ last = c
+ return r
+
+
+def printd(*params):
+ print string.join(params), '<br>'
+
+
+# I _hate_ this.
+def fixu8(s):
+ openpos = s.find('[_')
+ if openpos < 0:
+ # small optimization to avoid the conversion to utf8 and
+ # entering the loop
+ return s.decode(config.repoencoding).encode('utf8')
+
+ s = s.encode(config.repoencoding).decode('raw_unicode_escape')
+ while openpos >= 0:
+ closepos = s.find('_]', openpos)
+ if closepos < 0:
+ # not closed, probably just luck
+ break
+
+ # middle should be something like 'c3', so we get it by
+ # removing the first three characters ("[_\")
+ middle = s[openpos + 3:closepos]
+ if len(middle) == 2:
+ # now we turn middle into the character "\xc3"
+ char = chr(int(middle, 16))
+
+ # finally, replace s with our new improved string, and repeat
+ # the ugly procedure
+ char = char.decode('raw_unicode_escape')
+ mn = '[_\\' + middle + '_]'
+ s = s.replace(mn, char, 1)
+ openpos = s.find('[_', openpos + 1)
+
+ if config.repoencoding != 'utf8':
+ s = s.encode('utf8')
+ else:
+ s = s.encode('raw_unicode_escape', 'replace')
+ return s
+
+
+def how_old(epoch):
+ age = int(time.time()) - int(epoch)
+ if age > 60*60*24*365*2:
+ s = str(age/60/60/24/365)
+ s += " years ago"
+ elif age > 60*60*24*(365/12)*2:
+ s = str(age/60/60/24/(365/12))
+ s += " month ago"
+ elif age > 60*60*24*7*2:
+ s = str(age/60/60/24/7)
+ s += " weeks ago"
+ elif age > 60*60*24*2:
+ s = str(age/60/60/24)
+ s += " days ago"
+ elif age > 60*60*2:
+ s = str(age/60/60)
+ s += " hours ago"
+ elif age > 60*2:
+ s = str(age/60)
+ s += " minutes ago"
+ elif age > 2:
+ s = str(age)
+ s += " seconds ago"
+ else:
+ s = "right now"
+ return s
+
+def shorten_str(s, max = 60):
+ if len(s) > max:
+ s = s[:max - 4] + ' ...'
+ return s
+
+def fperms(fname):
+ m = os.stat(fname)[stat.ST_MODE]
+ m = m & 0777
+ s = []
+ if os.path.isdir(fname): s.append('d')
+ else: s.append('-')
+
+ if m & 0400: s.append('r')
+ else: s.append('-')
+ if m & 0200: s.append('w')
+ else: s.append('-')
+ if m & 0100: s.append('x')
+ else: s.append('-')
+
+ if m & 0040: s.append('r')
+ else: s.append('-')
+ if m & 0020: s.append('w')
+ else: s.append('-')
+ if m & 0010: s.append('x')
+ else: s.append('-')
+
+ if m & 0004: s.append('r')
+ else: s.append('-')
+ if m & 0002: s.append('w')
+ else: s.append('-')
+ if m & 0001: s.append('x')
+ else: s.append('-')
+
+ return string.join(s, '')
+
+
+def isbinary(fname):
+ import re
+ bins = open(config.repodir + '/_darcs/prefs/binaries').readlines()
+ bins = [b[:-1] for b in bins if b and b[0] != '#']
+ for b in bins:
+ if re.compile(b).search(fname):
+ return 1
+ return 0
+
+
+#
+# generic html functions
+#
+
+def print_header():
+ print "Content-type: text/html; charset=utf-8\n"
+ print """
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US">
+<!-- darcsweb 0.10
+ Alberto Bertogli (albertogli@telpin.com.ar).
+
+ Based on gitweb, which is written by Kay Sievers <kay.sievers@vrfy.org>
+ and Christian Gierke <ch@gierke.de>
+-->
+<head>
+<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
+<meta name="robots" content="index, nofollow"/>
+<title>darcs - %(reponame)s</title>
+<link rel="stylesheet" type="text/css" href="%(css)s">
+<link rel="alternate" title="%(reponame)s" href="%(url)s;a=rss"
+ type="application/rss+xml"/>
+<link rel="shortcut icon" href="%(fav)s">
+<link rel="icon" href="%(fav)s">
+</head>
+
+<body>
+<div class="page_header">
+<a href=http://darcs.net title="darcs">
+<img src="%(logo)s" alt="darcs logo" style="float:right; border-width:0px;">
+</a>
+ """ % {
+ 'reponame': config.reponame,
+ 'css': config.cssfile,
+ 'url': config.myurl + '/' + config.myreponame,
+ 'fav': config.darcsfav,
+ 'logo': config.darcslogo,
+ }
+ print '<a href="%s">repos</a> /' % config.myname
+ print '<a href="%s;a=summary">%s</a>' % (config.myreponame,
+ config.reponame),
+ print '/ ' + action
+ print "</div>"
+
+
+def print_footer(put_rss = 1):
+ print """
+<div class="page_footer">
+<div class="page_footer_text">Crece desde el pueblo el futuro /
+ crece desde el pie</div>
+ """
+ if put_rss:
+ print '<a class=rss_logo href="%s;a=rss">RSS</a>' % \
+ (config.myurl + '/' + config.myreponame)
+ print "</div>\n</body>\n</html>"
+
+
+def print_navbar(h = "", f = ""):
+ print """
+<div class="page_nav">
+<a href="%(myreponame)s;a=summary">summary</a>
+| <a href="%(myreponame)s;a=shortlog">shortlog</a>
+| <a href="%(myreponame)s;a=log">log</a>
+| <a href="%(myreponame)s;a=tree">tree</a>
+ """ % { "myreponame": config.myreponame }
+
+ if h:
+ print """
+| <a href="%(myreponame)s;a=commit;h=%(hash)s">commit</a>
+| <a href="%(myreponame)s;a=commitdiff;h=%(hash)s">commitdiff</a>
+| <a href="%(myreponame)s;a=headdiff;h=%(hash)s">headdiff</a>
+ """ % { "myreponame": config.myreponame, 'hash': h }
+
+ realf = filter_file(config.repodir + '/_darcs/current/' + f)
+
+ if f and os.path.isfile(realf):
+ print """
+| <a href="%(myreponame)s;a=headblob;f=%(fname)s">headblob</a>
+ """ % { "myreponame": config.myreponame, 'fname': f }
+ # TODO: annotate, when it's implemented
+
+ if f and os.path.isdir(realf):
+ print """
+| <a href="%(myreponame)s;a=tree;f=%(fname)s">headtree</a>
+ """ % { "myreponame": config.myreponame, 'fname': f }
+
+ if h and f and (os.path.isfile(realf) or os.path.isdir(realf)):
+ print """
+| <a href="%(myreponame)s;a=headfilediff;h=%(hash)s;f=%(fname)s">headfilediff</a>
+ """ % { "myreponame": config.myreponame, 'hash': h, 'fname': f }
+
+ print '<br><br></div>'
+
+
+def print_plain_header():
+ print "Content-type: text/plain\n"
+
+
+#
+# darcs repo manipulation
+#
+
+def repo_get_owner():
+ try:
+ fd = open(config.repodir + '/_darcs/prefs/author')
+ author = fd.readlines()[0].strip()
+ except:
+ author = "Unknown owner <unknown@example.org>"
+ return author
+
+def run_darcs(params):
+ """Runs darcs on the repodir with the given params, return a file
+ object with its output."""
+ os.chdir(config.repodir)
+ cmd = "darcs " + params
+ inf, outf = os.popen4(cmd, 'r')
+ return outf
+
+
+class Patch:
+ "Represents a single patch/record"
+ def __init__(self):
+ self.hash = ''
+ self.author = ''
+ self.shortauthor = ''
+ self.date = 0
+ self.local_date = 0
+ self.name = ''
+ self.comment = ''
+ self.inverted = False;
+ self.adds = []
+ self.removes = []
+ self.modifies = {}
+ self.diradds = []
+ self.dirremoves = []
+ self.moves = {}
+
+ def tostr(self):
+ s = "%s\n\tAuthor: %s\n\tDate: %s\n\tHash: %s\n" % \
+ (self.name, self.author, self.date, self.hash)
+ return s
+
+ def getdiff(self):
+ """Returns a list of lines from the diff -u corresponding with
+ the patch."""
+ params = 'diff -u --match "hash %s"' % self.hash
+ f = run_darcs(params)
+ return f.readlines()
+
+
+# patch parsing, we get them through "darcs changes --xml-output"
+class BuildPatchList(xml.sax.handler.ContentHandler):
+ def __init__(self):
+ self.db = {}
+ self.list = []
+ self.cur_hash = ''
+ self.cur_elem = None
+ self.cur_val = ''
+ self.cur_file = ''
+
+ def startElement(self, name, attrs):
+ if name == 'patch':
+ p = Patch()
+ p.hash = fixu8(attrs.get('hash'))
+
+ au = attrs.get('author', None)
+ p.author = fixu8(escape(au))
+ if au.find('<') != -1:
+ au = au[:au.find('<')].strip()
+ p.shortauthor = fixu8(escape(au))
+
+ td = time.strptime(attrs.get('date', None),
+ "%Y%m%d%H%M%S")
+ p.date = time.mktime(td)
+ p.date_str = time.strftime("%a, %d %b %Y %H:%M:%S", td)
+
+ td = time.strptime(attrs.get('local_date', None),
+ "%a %b %d %H:%M:%S %Z %Y")
+ p.local_date = time.mktime(td)
+ p.local_date_str = \
+ time.strftime("%a, %d %b %Y %H:%M:%S", td)
+
+ inverted = attrs.get('inverted', None)
+ if inverted and inverted == 'True':
+ p.inverted = True
+
+ self.db[p.hash] = p
+ self.current = p.hash
+ self.list.append(p.hash)
+ elif name == 'name':
+ self.db[self.current].name = ''
+ self.cur_elem = 'name'
+ elif name == 'comment':
+ self.db[self.current].comment = ''
+ self.cur_elem = 'comment'
+ elif name == 'add_file':
+ self.cur_elem = 'add_file'
+ elif name == 'remove_file':
+ self.cur_elem = 'remove_file'
+ elif name == 'add_directory':
+ self.cur_elem = 'add_directory'
+ elif name == 'remove_directory':
+ self.cur_elem = 'remove_dir'
+ elif name == 'modify_file':
+ self.cur_elem = 'modify_file'
+ elif name == 'removed_lines':
+ if self.cur_val:
+ self.cur_file = fixu8(self.cur_val.strip())
+ cf = self.cur_file
+ p = self.db[self.current]
+ # the current value holds the file name at this point
+ if not p.modifies.has_key(cf):
+ p.modifies[cf] = { '+': 0, '-': 0 }
+ p.modifies[cf]['-'] = int(attrs.get('num', None))
+ elif name == 'added_lines':
+ if self.cur_val:
+ self.cur_file = fixu8(self.cur_val.strip())
+ cf = self.cur_file
+ p = self.db[self.current]
+ if not p.modifies.has_key(cf):
+ p.modifies[cf] = { '+': 0, '-': 0 }
+ p.modifies[cf]['+'] = int(attrs.get('num', None))
+ elif name == 'move':
+ src = fixu8(attrs.get('from', None))
+ dst = fixu8(attrs.get('to', None))
+ p = self.db[self.current]
+ p.moves[src] = dst
+ else:
+ self.cur_elem = None
+
+ def characters(self, s):
+ if not self.cur_elem:
+ return
+ self.cur_val += s
+
+ def endElement(self, name):
+ if name == 'name':
+ p = self.db[self.current]
+ p.name = fixu8(self.cur_val)
+ if p.inverted:
+ p.name = 'UNDO: ' + p.name
+ elif name == 'comment':
+ self.db[self.current].comment = fixu8(self.cur_val)
+ elif name == 'add_file':
+ scv = fixu8(self.cur_val.strip())
+ self.db[self.current].adds.append(scv)
+ elif name == 'remove_file':
+ scv = fixu8(self.cur_val.strip())
+ self.db[self.current].removes.append(scv)
+ elif name == 'add_directory':
+ scv = fixu8(self.cur_val.strip())
+ self.db[self.current].diradds.append(scv)
+ elif name == 'remove_directory':
+ scv = fixu8(self.cur_val.strip())
+ self.db[self.current].dirremoves.append(scv)
+
+ elif name == 'modify_file':
+ if not self.cur_file:
+ # binary modification appear without a line
+ # change summary, so we add it manually here
+ f = fixu8(self.cur_val.strip())
+ p = self.db[self.current]
+ p.modifies[f] = { '+': 0, '-': 0, 'b': 1 }
+ self.cur_file = ''
+
+ self.cur_elem = None
+ self.cur_val = ''
+
+ def get_list(self):
+ plist = []
+ for h in self.list:
+ plist.append(self.db[h])
+ return plist
+
+ def get_db(self):
+ return self.db
+
+ def get_list_db(self):
+ return (self.list, self.db)
+
+def get_changes_handler(params):
+ "Returns a handler for the changes output, run with the given params"
+ parser = xml.sax.make_parser()
+ handler = BuildPatchList()
+ parser.setContentHandler(handler)
+
+ # get the xml output and parse it
+ xmlf = run_darcs("changes --xml-output " + params)
+ parser.parse(xmlf)
+ xmlf.close()
+
+ return handler
+
+def get_last_patches(last = 15, topi = 0):
+ """Gets the last N patches from the repo, returns a patch list. If
+ "topi" is specified, then it will return the N patches that preceeded
+ the patch number topi in the list. It sounds messy but it's quite
+ simple. FIXME: there's probably a more efficient way of doing this."""
+ toget = last + topi
+ handler = get_changes_handler("-s --last=%d" % toget)
+
+ # return the list of all the patch objects
+ return handler.get_list()[topi:toget]
+
+def get_patch(hash):
+ handler = get_changes_handler('-s --match "hash %s"' % hash)
+ patch = handler.db[handler.list[0]]
+ return patch
+
+def get_file_diff(hash, fname):
+ return run_darcs("diff -u --match 'hash %s' '%s'" % (hash, fname))
+
+def get_file_headdiff(hash, fname):
+ return run_darcs("diff -u --from-match 'hash %s' '%s'" % (hash, fname))
+
+def get_patch_headdiff(hash):
+ return run_darcs("diff -u --from-match 'hash %s'" % hash)
+
+
+#
+# specific html functions
+#
+
+def print_diff(dsrc):
+ for l in dsrc:
+ l = l.decode(config.repoencoding, 'replace').encode('utf-8')
+
+ # remove the trailing newline
+ if len(l) > 1:
+ l = l[:-1]
+
+ if l.startswith('diff'):
+ # file lines, they have their own class
+ print '<div class="diff_info">%s</div>' % l
+ continue
+
+ color = ""
+ if l[0] == '+':
+ color = 'style="color:#008800;"'
+ elif l[0] == '-':
+ color = 'style="color:#cc0000;"'
+ elif l[0] == '@':
+ color = 'style="color:#990099;"'
+ elif l.startswith('Files'):
+ # binary differences
+ color = 'style="color:#666;"'
+ print '<div class="pre" %s>' % color + escape(l) + '</div>'
+
+
+def print_shortlog(last = 50, topi = 0):
+ ps = get_last_patches(last, topi)
+ print '<div><a class=title href="%s;a=shortlog">shortlog</a></div>' \
+ % config.myreponame
+ print '<table cellspacing=0>'
+
+ if topi != 0:
+ # put a link to the previous page
+ ntopi = topi - last
+ if ntopi < 0:
+ ntopi = 0
+ print '<td><a href="%s;a=shortlog;topi=%d">...</a></td>' % \
+ (config.myreponame, ntopi)
+
+ alt = False
+ for p in ps:
+ if alt:
+ print '<tr class="dark">'
+ else:
+ print '<tr class="light">'
+ alt = not alt
+
+ print """
+ <td><i>%(age)s</i></td>
+ <td>%(author)s</td>
+ <td>
+ <a class="list" href="%(myrname)s;a=commit;h=%(hash)s"><b>%(name)s</b></a>
+ </td>
+ <td class="link"><a href="%(myrname)s;a=commit;h=%(hash)s">commit</a></td>
+ <td class="link">
+ <a href="%(myrname)s;a=commitdiff;h=%(hash)s">commitdiff</a>
+ </td>
+ """ % {
+ 'age': how_old(p.local_date),
+ 'author': p.shortauthor,
+ 'myrname': config.myreponame,
+ 'hash': p.hash,
+ 'name': shorten_str(p.name),
+ }
+ print "</tr>"
+
+ if len(ps) >= last:
+ # only show if we've not shown them all already
+ print '<td><a href="%s;a=shortlog;topi=%d">...</a></td>' % \
+ (config.myreponame, topi + last)
+ print "</table>"
+
+
+def print_log(last = 50, topi = 0):
+ ps = get_last_patches(last, topi)
+
+ if topi != 0:
+ # put a link to the previous page
+ ntopi = topi - last
+ if ntopi < 0:
+ ntopi = 0
+ print '<p><a href="%s;a=log;topi=%d">&lt;- Prev</a><p>' % \
+ (config.myreponame, ntopi)
+
+ for p in ps:
+ if p.comment:
+ fmt_comment = p.comment.replace('\n', '<br>') + '\n'
+ fmt_comment += '<br><br>'
+ else:
+ fmt_comment = ''
+ print """
+<div><a class=title href="%(myreponame)s;a=commit;h=%(hash)s">
+ <span class=age>%(age)s</span>%(desc)s
+</a></div>
+<div class=title_text>
+ <div class=log_link>
+ <a href="%(myreponame)s;a=commit;h=%(hash)s">commit</a> |
+ <a href="%(myreponame)s;a=commitdiff;h=%(hash)s">commitdiff</a><br>
+ </div>
+ <i>%(author)s [%(date)s]</i><br>
+</div>
+<div class="log_body">
+ %(desc)s<br>
+ <br>
+ %(comment)s
+</div>
+
+ """ % {
+ 'myreponame': config.myreponame,
+ 'age': how_old(p.local_date),
+ 'date': p.local_date_str,
+ 'author': p.shortauthor,
+ 'hash': p.hash,
+ 'desc': shorten_str(p.name),
+ 'comment': fmt_comment
+ }
+
+ if len(ps) >= last:
+ # only show if we've not shown them all already
+ print '<p><a href="%s;a=log;topi=%d">Next -&gt;</a><p>' % \
+ (config.myreponame, topi + last)
+
+
+def print_blob(fname):
+ print '<div class=page_path><b>%s</b></div>' % fname
+ print '<div class=page_body>'
+ if isbinary(fname):
+ print """
+<i>This is a binary file and it's contents will not be displayed.</i>
+</div>
+ """
+ return
+
+ f = open(config.repodir + '/_darcs/current/' + fname, 'r')
+ count = 1
+ for l in f:
+ l = fixu8(escape(l))
+ if l and l[-1] == '\n':
+ l = l[:-1]
+
+ print """
+<div class=pre><a id="l%(c)d" href="#l%(c)d" class=linenr>%(c)4d</a> %(l)s</div>
+ """ % {
+ 'c': count,
+ 'l': l
+ }
+ count += 1
+ print '</div>'
+
+
+#
+# available actions
+#
+
+def do_summary():
+ print_header()
+ print_navbar()
+ owner = repo_get_owner()
+
+ # we should optimize this, it's a pity to go in such a mess for just
+ # one hash
+ ps = get_last_patches(1)
+
+ print '<div class=title>&nbsp;</div>'
+ print '<table cellspacing=0>'
+ print ' <tr><td>description</td><td>%s</td></tr>' % config.repodesc
+ print ' <tr><td>owner</td><td>%s</td></tr>' % owner
+ print ' <tr><td>last change</td><td>%s</td></tr>' % \
+ ps[0].local_date_str
+ print ' <tr><td>url</td><td><a href="%(url)s">%(url)s</a></td></tr>' %\
+ { 'url': config.repourl }
+ print '</table>'
+
+ print_shortlog(15)
+ print_footer()
+
+
+def do_commitdiff(phash):
+ print_header()
+ print_navbar(h = phash)
+ p = get_patch(phash)
+ print """
+<div>
+ <a class=title href="%(myreponame)s;a=commit;h=%(hash)s">%(name)s</a>
+</div>
+ """ % {
+ 'myreponame': config.myreponame,
+ 'hash': p.hash,
+ 'name': p.name,
+ }
+
+ dsrc = p.getdiff()
+ print_diff(dsrc)
+ print_footer()
+
+
+def do_headdiff(phash):
+ print_header()
+ print_navbar(h = phash)
+ p = get_patch(phash)
+ print """
+<div>
+ <a class=title href="%(myreponame)s;a=commit;h=%(hash)s">
+ %(name)s --&gt; to head</a>
+</div>
+ """ % {
+ 'myreponame': config.myreponame,
+ 'hash': p.hash,
+ 'name': p.name,
+ }
+
+ dsrc = get_patch_headdiff(phash)
+ print_diff(dsrc)
+ print_footer()
+
+
+def do_filediff(phash, fname):
+ print_header()
+ print_navbar(h = phash, f = fname)
+ p = get_patch(phash)
+ dsrc = get_file_diff(phash, fname)
+ print """
+<div>
+ <a class=title href="%(myreponame)s;a=commit;h=%(hash)s">%(name)s</a>
+</div>
+<div class="page_path"><b>%(fname)s</b></div>
+ """ % {
+ 'myreponame': config.myreponame,
+ 'hash': p.hash,
+ 'name': p.name,
+ 'fname': fname,
+ }
+
+ print_diff(dsrc)
+ print_footer()
+
+
+def do_file_headdiff(phash, fname):
+ print_header()
+ print_navbar(h = phash, f = fname)
+ p = get_patch(phash)
+ dsrc = get_file_headdiff(phash, fname)
+ print """
+<div>
+ <a class=title href="%(myreponame)s;a=commit;h=%(hash)s">
+ %(name)s --&gt; to head</a>
+</div>
+<div class="page_path"><b>%(fname)s</b></div>
+ """ % {
+ 'myreponame': config.myreponame,
+ 'hash': p.hash,
+ 'name': p.name,
+ 'fname': fname,
+ }
+
+ print_diff(dsrc)
+ print_footer()
+
+
+def do_plainfilediff(phash, fname):
+ print_plain_header()
+ dsrc = get_file_diff(phash, fname)
+ for l in dsrc:
+ sys.stdout.write(l)
+
+
+def do_commit(phash):
+ print_header()
+ print_navbar(h = phash)
+ p = get_patch(phash)
+
+ print """
+<div>
+ <a class=title href="%(myreponame)s;a=commitdiff;h=%(hash)s">%(name)s</a>
+</div>
+
+<div class=title_text>
+<table cellspacing=0>
+<tr><td>author</td><td>%(author)s</td></tr>
+<tr><td>local date</td><td>%(local_date)s</td></tr>
+<tr><td>date</td><td>%(date)s</td></tr>
+<tr><td>hash</td><td style="font-family: monospace">%(hash)s</td></tr>
+</table>
+</div>
+ """ % {
+ 'myreponame': config.myreponame,
+ 'author': p.author,
+ 'local_date': p.local_date_str,
+ 'date': p.date_str,
+ 'hash': p.hash,
+ 'name': p.name,
+ }
+ if p.comment:
+ c = p.comment.replace('\n', '<br>')
+ print '<div class=page_body>', c, '</div>'
+
+ changed = p.adds + p.removes + p.modifies.keys() + p.moves.keys() + \
+ p.diradds + p.dirremoves
+
+ if changed or p.moves:
+ n = len(changed)
+ print '<div class=list_head>%d file(s) changed:</div>' % n
+
+ print '<table cellspacing=0>'
+ changed.sort()
+ alt = False
+ for f in changed:
+ if alt:
+ print '<tr class="dark">'
+ else:
+ print '<tr class="light">'
+ alt = not alt
+
+ show_diff = 1
+ if p.moves.has_key(f):
+ # don't show diffs for moves, they're broken as of
+ # darcs 1.0.3
+ show_diff = 0
+
+ if show_diff:
+ print """
+<td>
+ <a class=list href="%(myreponame)s;a=filediff;h=%(hash)s;f=%(file)s">
+ %(file)s</a>
+</td>
+ """ % {
+ 'myreponame': config.myreponame,
+ 'hash': p.hash,
+ 'file': f
+ }
+ else:
+ print "<td>%s</td>" % f
+
+ show_diff = 1
+ if f in p.adds:
+ print '<td><span style="color:#008000">',
+ print '[added]',
+ print '</span></td>'
+ elif f in p.diradds:
+ print '<td><span style="color:#008000">',
+ print '[added dir]',
+ print '</span></td>'
+ elif f in p.removes:
+ print '<td><span style="color:#800000">',
+ print '[removed]',
+ print '</span></td>'
+ elif f in p.dirremoves:
+ print '<td><span style="color:#800000">',
+ print '[removed dir]',
+ print '</span></td>'
+ elif p.moves.has_key(f):
+ print '<td><span style="color:#000080">',
+ print '[moved to "%s"]' % p.moves[f]
+ print '</span></td>'
+ show_diff = 0
+ else:
+ print '<td><span style="color:#000080">',
+ if p.modifies[f].has_key('b'):
+ # binary modification
+ print '(binary)'
+ else:
+ print '+%(+)d -%(-)d' % p.modifies[f],
+ print '</span></td>'
+
+ if show_diff:
+ print """
+<td>
+ <a class=link href="%(myreponame)s;a=filediff;h=%(hash)s;f=%(file)s">diff
+</td>
+ """ % {
+ 'myreponame': config.myreponame,
+ 'hash': p.hash,
+ 'file': f
+ }
+ print '</tr>'
+ print '</table>'
+ print_footer()
+
+
+def do_tree(dname):
+ print_header()
+ print_navbar()
+
+ # the head
+ print """
+<div><a class=title href="%s;a=tree">Current tree</a></div>
+<div class="page_path"><b>
+ """ % config.myreponame
+
+ # and the linked, with links
+ parts = dname.split('/')
+ print '/ '
+ sofar = '/'
+ for p in parts:
+ if not p: continue
+ sofar += '/' + p
+ print '<a href="%s;a=tree;f=%s">%s</a> /' % \
+ (config.myreponame, sofar, p)
+
+ print """
+ </b></div>
+<div class="page_body">
+<table cellspacing="0">
+ """
+
+ realpath = config.repodir + '/_darcs/current/' + dname + '/'
+ alt = False
+ files = os.listdir(realpath)
+ files.sort()
+
+ # list directories first
+ dlist = []
+ flist = []
+ for f in files:
+ realfile = realpath + f
+ if os.path.isdir(realfile):
+ dlist.append(f)
+ else:
+ flist.append(f)
+ files = dlist + flist
+
+ for f in files:
+ if alt:
+ print '<tr class="dark">'
+ else:
+ print '<tr class="light">'
+ alt = not alt
+ realfile = realpath + f
+ print '<td style="font-family:monospace">', fperms(realfile),
+ print '</td>'
+
+ if f in dlist:
+ print """
+ <td><a class=list href="%(myrname)s;a=tree;f=%(newf)s">%(f)s</a></td>
+ <td><a class=link href="%(myrname)s;a=tree;f=%(newf)s">tree</a></td>
+ """ % {
+ 'myrname': config.myreponame,
+ 'f': f,
+ 'newf': filter_file(dname + '/' + f),
+ }
+ else:
+ print """
+ <td><a class=list href="%(myrname)s;a=headblob;f=%(fullf)s">%(f)s</a></td>
+ <td><a class=link href="%(myrname)s;a=headblob;f=%(fullf)s">headblob</a></td>
+ """ % {
+ 'myrname': config.myreponame,
+ 'f': f,
+ 'fullf': filter_file(dname + '/' + f),
+ }
+ print '</tr>'
+ print '</table></div>'
+ print_footer()
+
+
+def do_headblob(fname):
+ print_header()
+ print_navbar(f = fname)
+ filepath = os.path.dirname(fname)
+ print """
+ <div class=title><b>
+ """
+
+ # and the linked, with links
+ parts = filepath.split('/')
+ print '/ '
+ sofar = '/'
+ for p in parts:
+ if not p: continue
+ sofar += '/' + p
+ print '<a href="%s;a=tree;f=%s">%s</a> /' % \
+ (config.myreponame, sofar, p)
+
+ print '</b></div>'
+
+ # TODO: when we have annotate, put some links around here.
+ print_blob(fname)
+ print_footer()
+
+
+def do_plainblob(fname):
+ print_plain_header()
+ f = open(config.repodir + '/_darcs/current/' + fname, 'r')
+ for l in f:
+ sys.stdout.write(l)
+
+
+def do_shortlog(topi):
+ print_header()
+ print_navbar()
+ print_shortlog(topi = topi)
+ print_footer()
+
+
+def do_log(topi):
+ print_header()
+ print_navbar()
+ print_log(topi = topi)
+ print_footer()
+
+
+def do_rss():
+ print "Content-type: text/xml; charset=utf-8\n"
+ print '<?xml version="1.0" encoding="utf-8"?>'
+ print """
+<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
+<channel>
+ <title>%(reponame)s</title>
+ <link>%(url)s</link>
+ <description>%(desc)s</description>
+ <language>en</language>
+ """ % {
+ 'reponame': config.reponame,
+ 'url': config.myurl + '/' + config.myreponame,
+ 'desc': config.repodesc,
+ }
+
+ ps = get_last_patches(20)
+ for p in ps:
+ title = time.strftime('%d %b %H:%S', time.localtime(p.date))
+ title += ' - ' + p.name
+ pdate = time.strftime("%a, %d %b %Y %H:%M:%S +0000",
+ time.localtime(p.date))
+ link = '%s/%s;a=commit;h=%s' % (config.myurl,
+ config.myreponame, p.hash)
+ print """
+ <item>
+ <title>%(title)s</title>
+ <pubDate>%(pdate)s</pubDate>
+ <link>%(link)s</link>
+ <description>%(desc)s</description>
+ """ % {
+ 'title': escape(title),
+ 'pdate': pdate,
+ 'link': link,
+ 'desc': escape(p.name),
+ }
+ print ' <content:encoded><![CDATA['
+ print escape(p.name) + '<br>'
+ if p.comment:
+ print '<br>'
+ print escape(p.comment).replace('\n', '<br>\n')
+ print ']]>'
+ print '</content:encoded></item>'
+
+ print '</channel></rss>'
+
+
+def do_die():
+ print_header()
+ print "<p><font color=red>Error! Malformed query</font></p>"
+ print_footer()
+ sys.exit(1)
+
+
+def do_listrepos():
+ import config as all_configs
+
+ # the header here is special since we don't have a repo
+ print "Content-type: text/html; charset=utf-8\n"
+ print """
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US">
+<head>
+<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
+<meta name="robots" content="index, nofollow"/>
+<title>darcs - Repositories</title>
+<link rel="stylesheet" type="text/css" href="%(css)s">
+<link rel="shortcut icon" href="%(fav)s">
+<link rel="icon" href="%(fav)s">
+</head>
+
+<body>
+<div class="page_header">
+<a href=http://darcs.net title="darcs">
+<img src="%(logo)s" alt="darcs logo" style="float:right; border-width:0px;">
+</a>
+<a href="%(myname)s">repos</a> / index
+</div>
+<div class="index_include">
+This is the repository index for a darcsweb site.<br>
+These are all the available repositories.<br>
+</div>
+<table cellspacing="0">
+<tr>
+<th>Project</th>
+<th>Description</th>
+<th></th>
+</tr>
+ """ % {
+ 'myname': all_configs.base.myname,
+ 'css': all_configs.base.cssfile,
+ 'fav': all_configs.base.darcsfav,
+ 'logo': all_configs.base.darcslogo
+ }
+
+ # some python magic
+ alt = False
+ for conf in dir(all_configs):
+ if conf.startswith('__'):
+ continue
+ c = all_configs.__getattribute__(conf)
+ if 'reponame' not in dir(c):
+ continue
+ name = escape(c.reponame)
+ desc = escape(c.repodesc)
+
+ if alt: print '<tr class="dark">'
+ else: print '<tr class="light">'
+ alt = not alt
+ print """
+<td><a class=list href=%(myname)s?r=%(name)s;a=summary>%(name)s</a></td>
+<td>%(desc)s</td>
+<td class=link><a href=%(myname)s?r=%(name)s;a=summary>summary</a> |
+<a href=%(myname)s?r=%(name)s;a=shortlog>shortlog</a> |
+<a href=%(myname)s?r=%(name)s;a=log>log</a>
+</td>
+</tr>
+ """ % {
+ 'myname': all_configs.base.myname,
+ 'name': name,
+ 'desc': shorten_str(desc, 60)
+ }
+ print "</table>"
+ print_footer(put_rss = 0)
+
+def fill_config(name):
+ import config as all_configs
+ for conf in dir(all_configs):
+ if conf.startswith('__'):
+ continue
+ c = all_configs.__getattribute__(conf)
+ if 'reponame' not in dir(c):
+ continue
+ if c.reponame == name:
+ break
+ else:
+ # not found
+ raise
+
+ # fill the configuration
+ base = all_configs.base
+ config.myname = base.myname
+ config.myreponame = base.myname + '?r=' + name
+ config.myurl = base.myurl
+ config.darcslogo = base.darcslogo
+ config.darcsfav = base.darcsfav
+ config.cssfile = base.cssfile
+ config.reponame = c.reponame
+ config.repodesc = c.repodesc
+ config.repodir = c.repodir
+ config.repourl = c.repourl
+ config.repoencoding = c.repoencoding
+
+
+#
+# main
+#
+
+form = cgi.FieldStorage()
+
+# if they don't specify a repo, print the list and exit
+if not form.has_key('r'):
+ do_listrepos()
+ sys.exit(0)
+
+# get the repo configuration and fill the config class
+current_repo = form['r'].value
+fill_config(current_repo)
+
+
+# get the action, or default to summary
+if not form.has_key("a"):
+ action = "summary"
+else:
+ action = filter_an(form["a"].value)
+
+
+# see what should we do according to the received action
+if action == "summary":
+ do_summary()
+elif action == "commit":
+ phash = filter_hash(form["h"].value)
+ do_commit(phash)
+elif action == "commitdiff":
+ phash = filter_hash(form["h"].value)
+ do_commitdiff(phash)
+elif action == 'headdiff':
+ phash = filter_hash(form["h"].value)
+ do_headdiff(phash)
+elif action == "filediff":
+ phash = filter_hash(form["h"].value)
+ fname = filter_file(form["f"].value)
+ do_filediff(phash, fname)
+elif action == 'headfilediff':
+ phash = filter_hash(form["h"].value)
+ fname = filter_file(form["f"].value)
+ do_file_headdiff(phash, fname)
+elif action == "plainfilediff":
+ phash = filter_hash(form["h"].value)
+ fname = filter_file(form["f"].value)
+ do_plainfilediff(phash, fname)
+elif action == "shortlog":
+ if form.has_key("topi"):
+ topi = int(filter_num(form["topi"].value))
+ else:
+ topi = 0
+ do_shortlog(topi)
+elif action == "log":
+ if form.has_key("topi"):
+ topi = int(filter_num(form["topi"].value))
+ else:
+ topi = 0
+ do_log(topi)
+elif action == 'headblob':
+ fname = filter_file(form["f"].value)
+ do_headblob(fname)
+elif action == 'plainblob':
+ fname = filter_file(form["f"].value)
+ do_plainblob(fname)
+elif action == 'tree':
+ if form.has_key('f'):
+ fname = filter_file(form["f"].value)
+ else:
+ fname = '/'
+ do_tree(fname)
+elif action == 'rss':
+ do_rss()
+else:
+ action = "invalid query"
+ do_die()
+
+
Binary files old-darcsweb/minidarcs.png and new-darcsweb/minidarcs.png differ
diff -rN -u old-darcsweb/style.css new-darcsweb/style.css
--- old-darcsweb/style.css 1970-01-01 00:00:00.000000000 +0000
+++ new-darcsweb/style.css 2013-07-15 17:00:12.000000000 +0000
@@ -0,0 +1,212 @@
+
+/* darcsweb CSS
+ * Alberto Bertogli (albertogli@telpin.com.ar)
+ *
+ * Copied almost entirely from gitweb's, written by Kay Sievers
+ * <kay.sievers@vrfy.org> and Christian Gierke <ch@gierke.de>.
+ */
+
+
+body {
+ font-family: sans-serif;
+ font-size: 12px;
+ margin:0px;
+ border:solid #d9d8d1;
+ border-width:1px;
+ margin:10px;
+}
+
+a {
+ color:#0000cc;
+}
+
+a:hover, a:visited, a:active {
+ color:#880000;
+}
+
+div.page_header {
+ height:25px;
+ padding:8px;
+ font-size:18px;
+ font-weight:bold;
+ background-color:#d9d8d1;
+}
+
+div.page_header a:visited {
+ color:#0000cc;
+}
+
+div.page_header a:hover {
+ color:#880000;
+}
+
+div.page_nav {
+ padding:8px;
+}
+div.page_nav a:visited {
+ color:#0000cc;
+}
+
+div.page_path {
+ padding:8px;
+ border:solid #d9d8d1;
+ border-width:0px 0px 1px
+}
+
+div.page_footer {
+ height:17px;
+ padding:4px 8px;
+ background-color: #d9d8d1;
+}
+
+div.page_footer_text {
+ float:left;
+ color:#555555;
+ font-style:italic;
+}
+
+div.page_body {
+ padding:8px;
+}
+
+div.title, a.title {
+ display:block; padding:6px 8px;
+ font-weight:bold;
+ background-color:#edece6;
+ text-decoration:none;
+ color:#000000;
+}
+
+a.title:hover {
+ background-color: #d9d8d1;
+}
+
+div.title_text {
+ padding:6px 0px;
+ border: solid #d9d8d1;
+ border-width:0px 0px 1px;
+}
+
+div.log_body {
+ padding:8px 8px 8px 150px;
+}
+
+span.age {
+ position:relative;
+ float:left;
+ width:142px;
+ font-style:italic;
+}
+
+div.log_link {
+ padding:0px 8px;
+ font-size:10px;
+ font-family:sans-serif;
+ font-style:normal;
+ position:relative;
+ float:left;
+ width:136px;
+}
+
+div.list_head {
+ padding:6px 8px 4px;
+ border:solid #d9d8d1;
+ border-width:1px 0px 0px;
+ font-style:italic;
+}
+
+a.list {
+ text-decoration:none;
+ color:#000000;
+}
+
+a.list:hover {
+ text-decoration:underline;
+ color:#880000;
+}
+
+table {
+ padding:8px 4px;
+}
+
+th {
+ padding:2px 5px;
+ font-size:12px;
+ text-align:left;
+}
+
+tr.light:hover {
+ background-color:#edece6;
+}
+
+tr.dark {
+ background-color:#f6f6f0;
+}
+
+tr.dark:hover {
+ background-color:#edece6;
+}
+
+td {
+ padding:2px 5px;
+ font-size:12px;
+ vertical-align:top;
+}
+
+td.link {
+ padding:2px 5px;
+ font-family:sans-serif;
+ font-size:10px;
+}
+
+div.pre {
+ font-family:monospace;
+ font-size:12px;
+ white-space:pre;
+}
+
+div.diff_info {
+ font-family:monospace;
+ color:#000099;
+ background-color:#edece6;
+ font-style:italic;
+}
+
+div.index_include {
+ border:solid #d9d8d1;
+ border-width:0px 0px 1px;
+ padding:12px 8px;
+}
+
+div.search {
+ margin:4px 8px;
+ position:absolute;
+ top:56px;
+ right:12px
+}
+
+a.linenr {
+ color:#999999;
+ text-decoration:none
+}
+
+a.rss_logo {
+ float:right;
+ padding:3px 0px;
+ width:35px;
+ line-height:10px;
+ border:1px solid;
+ border-color:#fcc7a5 #7d3302 #3e1a01 #ff954e;
+ color:#ffffff;
+ background-color:#ff6600;
+ font-weight:bold;
+ font-family:sans-serif;
+ font-size:10px;
+ text-align:center;
+ text-decoration:none;
+}
+
+a.rss_logo:hover {
+ background-color:#ee5500;
+}
+