Tue Mar  7 23:13:19 ART 2006  Alberto Bertogli <albertogli@telpin.com.ar>
  tagged 0.15
Tue Mar  7 23:13:01 ART 2006  Alberto Bertogli <albertogli@telpin.com.ar>
  * Version 0.15.
Thu Feb 23 22:43:41 ART 2006  Alberto Bertogli <albertogli@telpin.com.ar>
  tagged 0.15-rc1
Thu Feb 23 22:37:00 ART 2006  Alberto Bertogli <albertogli@telpin.com.ar>
  * Fix repoencoding, this time for real (I hope).
Thu Feb 23 21:54:19 ART 2006  Alberto Bertogli <albertogli@telpin.com.ar>
  * Fix to allow having a single repoencoding.
Thu Feb 23 21:31:28 ART 2006  Alberto Bertogli <albertogli@telpin.com.ar>
  * Rewrite encoding handling.
  This patch rewrites fixu8() and affects a couple of places where decode() was
  used directly.
  
  It has the benefit of removing the ugly previous function, replacing it
  with...  well, another ugly function, but with a different kind of uglyness.
  
  It also supports multiple encodings, so if the first one fails, a second one
  is tried (and so on).
  
  There still are some corner cases with file named in alternative encodings,
  but should work much better than the older code.
Thu Feb 23 16:47:31 ART 2006  Alberto Bertogli <albertogli@telpin.com.ar>
  * Include the repository in log_times() output.
Thu Feb 23 15:51:21 ART 2006  Alberto Bertogli <albertogli@telpin.com.ar>
  * Quote filenames in several places.
  There were several places using filenames in URLs without quoting them
  properly, this fixes them.
  
  Thanks to VMiklos for the report.
Thu Feb 23 15:34:51 ART 2006  Alberto Bertogli <albertogli@telpin.com.ar>
  * Filter '"' in filenames.
  For security, don't allow '"' in filenames.
  If there is high demand, some alternative workaround could be implemented.
Thu Feb 23 14:21:30 ART 2006  Alberto Bertogli <albertogli@telpin.com.ar>
  * Add /etc/darcsweb to the module lookup path.
  This allows darcsweb's configuration to be in /etc/darcsweb instead of the
  current directory. It's mostly wanted by distributions which want to isolate
  the config file to keep things clean.
  
  A similar patch was sent by Gaetan Lehmann (Mandriva), VMiklos (Frugalware)
  and Fabian Linzberger (Debian).
Wed Feb 22 01:47:15 ART 2006  Alberto Bertogli <albertogli@telpin.com.ar>
  * Move some one-time used imports to the user location.
  As a minor but noticeable optimization to darcsweb's load time, move the
  importing of sha and email.Utils modules to the place where they're used (only
  one in both cases).
  
  This takes the time spent on imports from 0.027s to 0.021s, which will
  probably be slightly noticeable on slower machines, reducing the time taken by
  a cache hit from 0.030s to 0.024s (on misses it doesn't make a difference).
Tue Feb 21 18:24:30 ART 2006  Alberto Bertogli <albertogli@telpin.com.ar>
  * Begin with alt = True in listings.
  I think it looks better if we start a listing with alt = True (the dark one).
Mon Feb 20 00:23:19 ART 2006  Alberto Bertogli <albertogli@telpin.com.ar>
  * Implement time logging.
  This record adds an option to log the time it took darcsweb to present a
  page. Unfortunately, because of the way it's implemented, it's impossible to
  isolate the time it takes to call darcs; however, every darcs invocation is
  logged too, to allow independant measures.
Sun Feb 19 14:42:23 ART 2006  Alberto Bertogli <albertogli@telpin.com.ar>
  * Change cache file permissions 600.
  Change cache file permissions to 600. This should work on Windows too because
  the bits are ignored, according to python's documentation.
Thu Jan 12 20:52:34 ART 2006  Alberto Bertogli <albertogli@telpin.com.ar>
  * Fix typos in the BOLA license.
Mon Jan  9 19:25:04 ART 2006  Alberto Bertogli <albertogli@telpin.com.ar>
  tagged 0.14
diff -rN -u old-darcsweb/config.py.sample new-darcsweb/config.py.sample
--- old-darcsweb/config.py.sample	2006-03-07 23:24:59.000000000 -0300
+++ new-darcsweb/config.py.sample	2006-03-07 23:24:59.000000000 -0300
@@ -46,6 +46,11 @@
 	# set the limit to a very high number, they will take time.
 	#searchlimit = 100
 
+	# If you want to log the times it took darcsweb to present a page,
+	# uncomment this option. The value should be a file writeable by
+	# darcsweb.
+	#logtimes = "/tmp/darcsweb_times"
+
 
 #
 # From now on, every class is a repo configuration, with the same format
@@ -75,6 +80,9 @@
 	# 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.
+	# You can, optionally, specify multiple encodings; they're tried in
+	# order, and if one fails to decode a string, the next one is tried.
+	# Example: repoencoding = "utf8", "latin1"
 	repoencoding = "latin1"
 
 	# as with the base configuration, the footer is also optional, and it
diff -rN -u old-darcsweb/darcsweb.cgi new-darcsweb/darcsweb.cgi
--- old-darcsweb/darcsweb.cgi	2006-03-07 23:24:59.000000000 -0300
+++ new-darcsweb/darcsweb.cgi	2006-03-07 23:24:59.000000000 -0300
@@ -8,25 +8,33 @@
 <kay.sievers@vrfy.org> and Christian Gierke <ch@gierke.de>
 """
 
+import time
+time_begin = time.time()
 import sys
 import os
 import string
-import time
 import stat
-import sha
 import cgi
 import cgitb; cgitb.enable()
 import urllib
 import xml.sax
 from xml.sax.saxutils import escape as xml_escape
-import email.Utils
+time_imports = time.time() - time_begin
 
 iso_datetime = '%Y-%m-%dT%H:%M:%SZ'
 
+# In order to be able to store the config file in /etc/darcsweb, it has to be
+# added to sys.path. It's mainly used by distributions, which place the
+# default configuration there.
+sys.path.append('/etc/darcsweb')
+
 # empty configuration class, we will fill it in later depending on the repo
 class config:
 	pass
 
+# list of run_darcs() invocations, for performance measures
+darcs_runs = []
+
 # exception handling
 def exc_handle(t, v, tb):
 	try:
@@ -58,7 +66,7 @@
 
 
 def filter_file(s):
-	if '..' in s:
+	if '..' in s or '"' in s:
 		raise 'FilterFile FAILED'
 	if s == '/':
 		return s
@@ -80,42 +88,24 @@
 
 # I _hate_ this.
 def fixu8(s):
-	openpos = s.find('[_')
-	if openpos < 0:
-		# small optimization to avoid the conversion to utf8 and
-		# entering the loop
-		if type(s) == unicode:
-			# workaround for python < 2.4
-			return s.encode('utf8')
-		else:
-			return s.decode(config.repoencoding).encode('utf8')
+	"""Calls _fixu8(), which does the real work, line by line. Otherwise
+	we choose the wrong encoding for big buffers and end up messing
+	output."""
+	n = []
+	for i in s.split('\n'):
+		n.append(_fixu8(i))
+	return string.join(n, '\n')
+
+def _fixu8(s):
+	if type(s) == unicode:
+		return s.encode('utf8', 'replace')
+	for e in config.repoencoding:
+		try:
+			return s.decode(e).encode('utf8', 'replace')
+		except UnicodeDecodeError:
+			pass
+	raise 'DecodingError', config.repoencoding
 
-	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(config.repoencoding)
-			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 escape(s):
 	s = xml_escape(s)
@@ -233,6 +223,36 @@
 		realf = filter_file(config.repodir + '/' + fname)
 	return realf
 
+def log_times(cache_hit, repo = None, event = None):
+	if not config.logtimes:
+		return
+
+	time_total = time.time() - time_begin
+	processing = time_total - time_imports
+	if not event:
+		event = action
+	if cache_hit:
+		event = event + " (hit)"
+	s = '%s\n' % event
+
+	if repo:
+		s += '\trepo: %s\n' % repo
+
+	s += """\
+	total: %.3f
+	processing: %.3f
+	imports: %.3f\n""" % (time_total, processing, time_imports)
+
+	if darcs_runs:
+		s += "\truns:\n"
+		for params in darcs_runs:
+			s += '\t\t%s\n' % params
+	s += '\n'
+
+	lf = open(config.logtimes, 'a')
+	lf.write(s)
+	lf.close()
+
 
 #
 # generic html functions
@@ -245,7 +265,7 @@
 <!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
+<!-- darcsweb 0.15
      Alberto Bertogli (albertogli@telpin.com.ar).
 
      Based on gitweb, which is written by Kay Sievers <kay.sievers@vrfy.org>
@@ -321,6 +341,7 @@
 		""" % { "myreponame": config.myreponame, 'hash': h }
 
 	realf = realpath(f)
+	f = urllib.quote(f)
 
 	if f and h:
 		print """
@@ -450,6 +471,7 @@
 
 class Cache:
 	def __init__(self, basedir, url):
+		import sha
 		self.basedir = basedir
 		self.url = url
 		self.fname = sha.sha(repr(url)).hexdigest()
@@ -467,6 +489,7 @@
 			fname = self.basedir + '/.' + self.fname + '-' + pid
 			self.file = open(fname, 'w')
 			self.mode = 'w'
+			os.chmod(fname, stat.S_IRUSR | stat.S_IWUSR)
 
 			# step over stdout so when "print" tries to write
 			# output, we get it first
@@ -544,8 +567,9 @@
 	"""Runs darcs on the repodir with the given params, return a file
 	object with its output."""
 	os.chdir(config.repodir)
-	cmd = config.darcspath + "darcs " + params
+	cmd = 'DARCS_DONT_ESCAPE_8BIT=1 ' + config.darcspath + "darcs " + params
 	inf, outf = os.popen4(cmd, 't')
+	darcs_runs.append(params)
 	return outf
 
 
@@ -601,6 +625,24 @@
 					return i
 		return ''
 
+class XmlInputWrapper:
+	def __init__(self, fd):
+		self.fd = fd
+		self.times = 0
+		self._read = self.read
+
+	def read(self, *args, **kwargs):
+		self.times += 1
+		if self.times == 1:
+			return '<?xml version="1.0" encoding="utf-8"?>\n'
+		s = self.fd.read(*args, **kwargs)
+		if not s:
+			return s
+		return fixu8(s)
+
+	def close(self, *args, **kwargs):
+		return self.fd.close(*args, **kwargs)
+
 
 # patch parsing, we get them through "darcs changes --xml-output"
 class BuildPatchList(xml.sax.handler.ContentHandler):
@@ -770,7 +812,7 @@
 
 	# get the xml output and parse it
 	xmlf = run_darcs("changes --xml-output " + params)
-	parser.parse(xmlf)
+	parser.parse(XmlInputWrapper(xmlf))
 	xmlf.close()
 
 	return handler
@@ -967,7 +1009,7 @@
 
 def print_diff(dsrc):
 	for l in dsrc:
-		l = l.decode(config.repoencoding, 'replace').encode('utf-8')
+		l = fixu8(l)
 
 		# remove the trailing newline
 		if len(l) > 1:
@@ -993,7 +1035,7 @@
 
 def print_darcs_diff(dsrc):
 	for l in dsrc:
-		l = l.decode(config.repoencoding, 'replace').encode('utf-8')
+		l = fixu8(l)
 
 		if not l.startswith("    "):
 			# comments and normal stuff
@@ -1040,7 +1082,7 @@
 				% (config.myreponame, ntopi)
 		print '</td></tr>'
 
-	alt = False
+	alt = True
 	for p in ps:
 		if p.name.startswith("TAG "):
 			print '<tr class="tag">'
@@ -1552,7 +1594,7 @@
 
 	print '<table cellspacing="0">'
 	changed.sort()
-	alt = False
+	alt = True
 	for f in changed:
 		if alt:
 			print '<tr class="dark">'
@@ -1570,12 +1612,13 @@
 			print """
 <td>
   <a class="list" href="%(myreponame)s;a=filediff;h=%(hash)s;f=%(file)s">
-    %(file)s</a>
+    %(fname)s</a>
 </td>
 			""" % {
 				'myreponame': config.myreponame,
 				'hash': p.hash,
-				'file': f
+				'file': urllib.quote(f),
+				'fname': escape(f),
 			}
 		else:
 			print "<td>%s</td>" % f
@@ -1625,7 +1668,7 @@
 			""" % {
 				'myreponame': config.myreponame,
 				'hash': p.hash,
-				'file': f
+				'file': urllib.quote(f)
 			}
 		print '</tr>'
 	print '</table>'
@@ -1650,7 +1693,7 @@
 		if not p: continue
 		sofar += '/' + p
 		print '<a href="%s;a=tree;f=%s">%s</a> /' % \
-				(config.myreponame, escape(sofar), p)
+				(config.myreponame, urllib.quote(sofar), p)
 
 	print """
   </b></div>
@@ -1660,7 +1703,7 @@
 
 	path = realpath(dname) + '/'
 
-	alt = False
+	alt = True
 	files = os.listdir(path)
 	files.sort()
 
@@ -1684,20 +1727,21 @@
 			print '<tr class="light">'
 		alt = not alt
 		realfile = path + f
+		fullf = filter_file(dname + '/' + 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="list" href="%(myrname)s;a=tree;f=%(fullf)s">%(f)s</a></td>
   <td class="link">
-    <a href="%(myrname)s;a=filehistory;f=%(newf)s">history</a> |
-    <a href="%(myrname)s;a=tree;f=%(newf)s">tree</a>
+    <a href="%(myrname)s;a=filehistory;f=%(fullf)s">history</a> |
+    <a href="%(myrname)s;a=tree;f=%(fullf)s">tree</a>
   </td>
 			""" % {
 				'myrname': config.myreponame,
 				'f': escape(f),
-				'newf': filter_file(dname + '/' + f),
+				'fullf': urllib.quote(fullf),
 			}
 		else:
 			print """
@@ -1710,7 +1754,7 @@
 			""" % {
 				'myrname': config.myreponame,
 				'f': escape(f),
-				'fullf': filter_file(dname + '/' + f),
+				'fullf': urllib.quote(fullf),
 			}
 		print '</tr>'
 	print '</table></div>'
@@ -1835,6 +1879,7 @@
 		link = '%s/%s;a=commit;h=%s' % (config.myurl,
 				config.myreponame, p.hash)
 
+		import email.Utils
 		addr, author = email.Utils.parseaddr(p.author)
 		if not addr:
 			addr = "unknown_email@example.com"
@@ -1957,7 +2002,7 @@
 			% (config.searchlimit, escape(s))
 	print '<table cellspacing="0">'
 
-	alt = False
+	alt = True
 	for p in ps:
 		match = p.matches(s)
 		if not match:
@@ -2047,7 +2092,7 @@
 	}
 
 	# some python magic
-	alt = False
+	alt = True
 	expand_multi_config(all_configs)
 	for conf in dir(all_configs):
 		if conf.startswith('__'):
@@ -2141,6 +2186,7 @@
 				repodesc = desc
 				repourl = url
 				repoencoding = c.repoencoding
+
 				if 'footer' in dir(c):
 					footer = c.footer
 			config.__setattr__(name, tmp_config)
@@ -2178,7 +2224,11 @@
 		config.repodesc = c.repodesc
 		config.repodir = c.repodir
 		config.repourl = c.repourl
-		config.repoencoding = c.repoencoding
+		# repoencoding must be a tuple
+		if isinstance(c.repoencoding, str):
+			config.repoencoding = (c.repoencoding, )
+		else:
+			config.repoencoding = c.repoencoding
 
 	# optional parameters
 	if "darcspath" in dir(base):
@@ -2204,6 +2254,11 @@
 	else:
 		config.searchlimit = 100
 
+	if "logtimes" in dir(base):
+		config.logtimes = base.logtimes
+	else:
+		config.logtimes = None
+
 	if name and "footer" in dir(c):
 		config.footer = c.footer
 	elif "footer" in dir(base):
@@ -2224,6 +2279,7 @@
 if not form.has_key('r'):
 	fill_config()
 	do_listrepos()
+	log_times(cache_hit = 0, event = 'index')
 	sys.exit(0)
 
 # get the repo configuration and fill the config class
@@ -2251,6 +2307,7 @@
 		# we have a hit, dump and run
 		cache.dump()
 		cache.close()
+		log_times(cache_hit = 1, repo = config.reponame)
 		sys.exit(0)
 	# if there is a miss, the cache will step over stdout, intercepting
 	# all "print"s and writing them to the cache file automatically
@@ -2403,3 +2460,6 @@
 if config.cachedir:
 	cache.close()
 
+log_times(cache_hit = 0, repo = config.reponame)
+
+
diff -rN -u old-darcsweb/LICENSE new-darcsweb/LICENSE
--- old-darcsweb/LICENSE	2006-03-07 23:24:59.000000000 -0300
+++ new-darcsweb/LICENSE	2006-03-07 23:24:59.000000000 -0300
@@ -2,8 +2,8 @@
 I don't like licenses, because I don't like having to worry about all this
 legal stuff just for a simple piece of software I don't really mind anyone
 using. But I also believe that it's important that people share and give back;
-so I'm placing darcsweb under the following license, so you feel guilty if you
-don't ;)
+so I'm placing darcsweb under the following license, to make you feel guilty
+if you don't ;)
 
 
 BOLA - Buena Onda License Agreement
@@ -24,7 +24,7 @@
 5. Help someone who needs it: sign up for some volunteer work or help your
    neighbour paint the house.
 6. Don't waste. Anything, but specially energy that comes from natural
-   non-renovable resources. Extra points if you discover or invent something
+   non-renewable resources. Extra points if you discover or invent something
    to replace them.
 7. Be tolerant. Everything that's good in nature comes from cooperation.
 

