git » wikiri » commit e939dc9

Add a darcs history backend

author Alberto Bertogli
2008-11-30 21:57:06 UTC
committer Alberto Bertogli
2008-11-30 21:59:49 UTC
parent 7b470430a9bf4c365e247c6744c094d1a7e89dbd

Add a darcs history backend

Signed-off-by: Alberto Bertogli <albertito@blitiri.com.ar>

config.py.sample +1 -0
wikiri.cgi +162 -1

diff --git a/config.py.sample b/config.py.sample
index 6c5eb71..5758cc7 100644
--- a/config.py.sample
+++ b/config.py.sample
@@ -26,6 +26,7 @@ title = "I like wikis"
 # History backend. Can be one of:
 #  - "none" for no history
 #  - "git" to use git (needs git and the data path to be a repository)
+#  - "darcs" to use darcs (needs darcs and the data path to be a repository)
 #  - "auto" to select automatically (looks if the data path is a repository)
 history = "auto"
 
diff --git a/wikiri.cgi b/wikiri.cgi
index dab565d..52f5c87 100755
--- a/wikiri.cgi
+++ b/wikiri.cgi
@@ -31,6 +31,7 @@ title = "I like wikis"
 # History backend. Can be one of:
 #  - "none" for no history
 #  - "git" to use git (needs git and the data path to be a repository)
+#  - "darcs" to use darcs (needs darcs and the data path to be a repository)
 #  - "auto" to select automatically (looks if the data path is a repository)
 history = "auto"
 
@@ -644,7 +645,7 @@ class Article (object):
 #
 # History backends
 #
-# At the moment we only support git (it's optional, though)
+# At the moment we support none, git and darcs
 
 class HistoryError (Exception):
 	pass
@@ -654,10 +655,14 @@ class History:
 		if history == 'auto':
 			if os.path.isdir(data_path + '.git'):
 				self.be = GitBackend(data_path)
+			elif os.path.isdir(data_path + '_darcs'):
+				self.be = DarcsBackend(data_path)
 			else:
 				self.be = NoneBackend(data_path)
 		elif history == 'git':
 			self.be = GitBackend(data_path)
+		elif history == 'darcs':
+			self.be = DarcsBackend(data_path)
 		else:
 			self.be = NoneBackend(data_path)
 
@@ -665,6 +670,15 @@ class History:
 		self.be.commit(msg, author = author)
 
 	def log(self, fname):
+		# log() yields commits of the form:
+		# {
+		# 	'commit': commit id
+		# 	'author': author of the commit
+		# 	'committer': committer
+		# 	'atime': time of the change itself
+		# 	'ctime': time of the commit
+		# 	'msg': commit msg (one line)
+		# }
 		return self.be.log(file = fname)
 
 	def add(self, *files):
@@ -834,6 +848,153 @@ def _add_times(commit):
 		commit['committer'] = committer
 		commit['ctime'] = datetime.datetime.fromtimestamp(epoch)
 
+class DarcsBackend:
+	def __init__(self, repopath):
+		self.repo = repopath
+		self.prevdir = None
+
+	def cdrepo(self):
+		self.prevdir = os.getcwd()
+		os.chdir(self.repo)
+
+	def cdback(self):
+		os.chdir(self.prevdir)
+
+	def darcs(self, *args):
+		# delay the import to avoid the hit on a regular page view
+		import subprocess
+		self.cdrepo()
+		cmd = subprocess.Popen( ['darcs'] + list(args),
+			stdin = subprocess.PIPE,
+			stdout = subprocess.PIPE,
+			stderr = sys.stderr )
+		self.cdback()
+		return cmd
+
+	def darcsq(self, *args):
+		cmd = self.darcs(*args)
+		stdout, stderr = cmd.communicate()
+		return cmd.returncode
+
+	def commit(self, msg, author = None):
+		if not author:
+			author = "Unknown <unknown@example.com>"
+
+		# see if we have something to commit; if not, just return
+		if self.darcsq('whatsnew') != 0:
+			return
+
+		r = self.darcsq('record',
+			'-a',
+			'-m', msg,
+			'-A', author)
+		if r != 0:
+			raise HistoryError, r
+
+	def log(self, file = None, files = None):
+		import xml.dom.minidom as minidom
+
+		if not files:
+			files = []
+		if file:
+			files.append(file)
+
+		cmd = self.darcs("changes",
+			"--xml-output",
+			"--", *files)
+		cmd.stdin.close()
+
+		xml = minidom.parse(cmd.stdout)
+
+		cmd.wait()
+		if cmd.returncode != 0:
+			raise HistoryError, cmd.returncode
+
+		for p in xml.getElementsByTagName('patch'):
+			# ignore patches not directly inside <changelog> (they
+			# can be, for instance, inside <created_as>, which we
+			# want to ignore)
+			if p.parentNode.nodeName != 'changelog':
+				continue
+
+			cid = p.getAttribute("hash")
+			author = p.getAttribute('author')
+			atime = p.getAttribute('date')
+			atime = datetime.datetime.strptime(atime,
+					"%Y%m%d%H%M%S")
+			msg = p.getElementsByTagName('name')[0]
+			msg = msg.childNodes[0].data
+			msg = msg.split('\n')[0].strip()
+
+			commit = {
+				'commit': cid.encode('utf8'),
+				'author': author.encode('utf8'),
+				'committer': author.encode('utf8'),
+				'atime': atime,
+				'ctime': atime,
+				'msg': msg.encode('utf8'),
+			}
+			yield commit
+
+	def add(self, *files):
+		r = self.darcsq('add', "--", *files)
+		# 0 means success, 2 means the file was already there (which
+		# is ok because we always add the files)
+		if r != 0 and r != 2:
+			raise HistoryError, r
+
+	def remove(self, *files):
+		r = self.darcsq('remove', '--', *files)
+		if r != 0:
+			raise HistoryError, r
+
+	def get_content(self, fname, commitid):
+		cmd = self.darcs("show", "contents",
+			"--match", "hash %s" % commitid,
+			"--", fname)
+		content = cmd.stdout.read()
+		cmd.wait()
+		return content
+
+	def get_commit(self, cid):
+		import xml.dom.minidom as minidom
+
+		cmd = self.darcs("changes",
+			"--xml-output",
+			"--match", "hash %s" % cid)
+		cmd.stdin.close()
+
+		xml = minidom.parse(cmd.stdout)
+
+		cmd.wait()
+		if cmd.returncode != 0:
+			raise HistoryError, cmd.returncode
+
+		try:
+			p = xml.getElementsByTagName('patch')[0]
+		except IndexError:
+			raise HistoryError, "not such patch"
+
+		cid = p.getAttribute("hash")
+		author = p.getAttribute('author')
+		atime = p.getAttribute('date')
+		atime = datetime.datetime.strptime(atime,
+				"%Y%m%d%H%M%S")
+		msg = p.getElementsByTagName('name')[0]
+		msg = msg.childNodes[0].data
+		msg = msg.split('\n')[0].strip()
+
+		commit = {
+			'commit': cid.encode('utf8'),
+			'author': author.encode('utf8'),
+			'committer': author.encode('utf8'),
+			'atime': atime,
+			'ctime': atime,
+			'msg': msg.encode('utf8'),
+		}
+		return commit
+
+
 
 #
 # Main