We're back after a server migration that caused effbot.org to fall over a bit harder than expected. Expect some glitches.

Publishing SVN Backups as Trac Attachments

Fredrik Lundh | January 2008

We’re using customer-specific Trac and Subversion instances for some of our customers, which is working pretty well. Recently, one customer wanted to be able to download complete Subversion archives of their code from the site, at will. Since the customer was already downloading code releases via the Trac wiki, we decided to make the archives available through the Trac wiki as well. Here’s how we did it.

Trac stores attachments in a subdirectory, attachments/wiki in the Trac data directory. For example, on our server, attachments for the SvnBackup page in the customer.trac wiki would be stored in this directory:

/home/company/webapps/customer.trac/attachments/wiki/SvnBackup

Creating Archive Tarballs

So, the first step is to create regular backups, and store them in that directory. Well, we’re already creating nightly backups using the hot-backup.py script, so the first step is to tell the hot-backup.py script to put those backups in the right directory:

$ mkdir webapps/customer.trac/attachments/wiki/SvnBackup

$ bin/hot-backup.py --archive-type=gz --num-backups=10 \
  webapps/customer.svn/ \
  webapps/customer.trac/attachments/wiki/SvnBackup

(the hot-backup.py command should be written on a single line)

The above command tells the hot-backup.py script to create a hotcopy of the webapps/customer.svn repository, run it through tar and gzip, and store the resulting tarball in the SvnBackup attachment directory (which has to exist). Up to ten tarballs are kept around.

Attaching the Tarballs to a Wiki Page

Next, we need to tell Trac about the attachments. Just putting some files in the directory isn’t enough; we also need to update Trac’s database. Trac stores a list of attachments in the attachment table in a local sqlite3 database; to update the list, we just need to clean out any old attachments for the page, and add entries for the existing files.

This script (svnbackup.py) does the trick:

# usage: svnbackup.py <trac dir> <page>
#   e.g: svnbackup.py webapps/customer.trac SvnBackup

import glob, sys
import sqlite3 as dbapi

from os.path import *

TRAC_ROOT, PAGE = sys.argv[1:3]

db = dbapi.connect(join(TRAC_ROOT, "db/trac.db"))

db.execute("delete from attachment where type = 'wiki' and id = ?", [PAGE])

for file in glob.glob(join(TRAC_ROOT, "attachments/wiki", PAGE, "*")):
    print os.path.basename(file)
    db.execute(
        "insert into attachment"
        " (type, id, filename, size, time, description, author, ipnr)"
        " values ('wiki', ?, ?, ?, ?, '', 'system', '127.0.0.1')",
        [PAGE, basename(file), getsize(file), getmtime(file)]
        )

db.commit()
db.close()

Running this script adds the files to the given page:

$ python svnbackup.py webapps/customer.trac SvnBackup
customer.svn-500.tar.gz
customer.svn-505.tar.gz
customer.svn-501.tar.gz

This results in an attachment section that looks something like:

Attachments

Wrapping Up

The final step is to add the hot-backup.py and svnbackup.py commands to your standard backup cronjob, and tell the customer about it. My cron entry looks like this:

15 0 * * * /home/company/BACKUP.sh >/home/company/backup.log 2>&1

and the BACKUP.sh script simply contains the hot-backup.py and svnbackup.py commands shown earlier (and lots of other stuff as well, but that’s outside the scope of this article).