1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
|
#!/usr/bin/env python
# -*- Mode: python -*-
#
# Copyright (C) 2004 James Henstridge
#
# By using this file, you agree to the terms and conditions set forth in
# the LICENSE.html file which can be found at the top level of the ViewCVS
# distribution or at http://www.lyra.org/viewcvs/license-1.html.
#
# -----------------------------------------------------------------------
#
# administrative program for loading Subversion revision information
# into the checkin database. It can be used to add a single revision
# to the database, or rebuild/update all revisions.
#
# To add all the checkins from a Subversion repository to the checkin
# database, run the following:
# /path/to/svndbadmin rebuild /path/to/repo
#
# This script can also be called from the Subversion post-commit hook,
# something like this:
# REPOS="$1"
# REV="$2"
# /path/to/svndbadmin update "$REPOS" "$REV"
#
# If you allow changes to revision properties in your repository, you
# might also want to set up something similar in the
# post-revprop-change hook using "rebuild" instead of "update" to keep
# the checkin database consistent with the repository.
#
# -----------------------------------------------------------------------
#
#########################################################################
#
# INSTALL-TIME CONFIGURATION
#
# These values will be set during the installation process. During
# development, they will remain None.
#
LIBRARY_DIR = None
CONF_PATHNAME = None
# Adjust sys.path to include our library directory
import sys
if LIBRARY_DIR:
sys.path.insert(0, LIBRARY_DIR)
else:
sys.path[:0] = ['../lib'] # any other places to look?
#########################################################################
import os
import string
import re
import svn.core
import svn.repos
import svn.fs
import svn.delta
import cvsdb
class SvnRepo:
"""Class used to manage a connection to a SVN repository."""
pool = None
def __init__(self, path, pool):
self.pool = pool
self.scratch_pool = svn.core.svn_pool_create(self.pool)
self.path = path
self.repo = svn.repos.svn_repos_open(path, self.pool)
self.fs = svn.repos.svn_repos_fs(self.repo)
# youngest revision of base of file system is highest revision number
self.rev_max = svn.fs.youngest_rev(self.fs, self.pool)
def __del__(self):
if self.pool:
svn.core.svn_pool_destroy(self.pool)
def __getitem__(self, rev):
if rev is None:
rev = self.rev_max
elif rev < 0:
rev = rev + self.rev_max + 1
assert 0 <= rev <= self.rev_max
rev = SvnRev(self, rev, self.scratch_pool)
svn.core.svn_pool_clear(self.scratch_pool)
return rev
_re_diff_change_command = re.compile('(\d+)(?:,(\d+))?([acd])(\d+)(?:,(\d+))?')
def _get_diff_counts(last_fsroot, fsroot, path1, path2, pool):
"""Calculate the plus/minus counts by parsing the output of a
normal diff. The reasons for choosing Normal diff format are:
- the output is short, so should be quicker to parse.
- only the change commands need be parsed to calculate the counts.
- All file data is prefixed, so won't be mistaken for a change
command.
This code is based on the description of the format found in the
GNU diff manual."""
diffobj = svn.fs.FileDiff(last_fsroot, path1,
fsroot, path2, pool, [])
fp = diffobj.get_pipe()
plus, minus = 0, 0
line = fp.readline()
while line:
match = re.match(_re_diff_change_command, line)
if match:
# size of first range
if match.group(2):
count1 = int(match.group(2)) - int(match.group(1)) + 1
else:
count1 = 1
cmd = match.group(3)
# size of second range
if match.group(5):
count2 = int(match.group(5)) - int(match.group(4)) + 1
else:
count2 = 1
if cmd == 'a':
# LaR - insert after line L of file1 range R of file2
plus = plus + count2
elif cmd == 'c':
# FcT - replace range F of file1 with range T of file2
minus = minus + count1
plus = plus + count2
elif cmd == 'd':
# RdL - remove range R of file1, which would have been
# at line L of file2
minus = minus + count1
line = fp.readline()
return plus, minus
class SvnRev:
"""Class used to hold information about a particular revision of
the repository."""
def __init__(self, repo, rev, pool):
self.repo = repo
self.rev = rev
# revision properties ...
properties = svn.fs.revision_proplist(repo.fs, rev, pool)
self.author = str(properties.get(svn.core.SVN_PROP_REVISION_AUTHOR,''))
self.date = str(properties.get(svn.core.SVN_PROP_REVISION_DATE, ''))
self.log = str(properties.get(svn.core.SVN_PROP_REVISION_LOG, ''))
# convert the date string to seconds since epoch ...
self.date = svn.core.secs_from_timestr(self.date, pool)
fsroot = svn.fs.revision_root(repo.fs, rev, pool)
if rev > 0:
last_fsroot = svn.fs.revision_root(repo.fs, rev-1, pool)
else:
last_fsroot = None
# find changes in the revision
editor = svn.repos.RevisionChangeCollector(repo.fs, rev, pool)
e_ptr, e_baton = svn.delta.make_editor(editor, pool)
svn.repos.svn_repos_replay(fsroot, e_ptr, e_baton, pool)
self.changes = []
for path, change in editor.changes.items():
if change.item_kind != svn.core.svn_node_file: continue
# we handle
if not change.path:
oldpath, newpath, action = path, None, 'remove'
elif change.added:
oldpath, newpath, action = None, path, 'add'
else:
oldpath, newpath, action = path, path, 'change'
plus, minus = _get_diff_counts(last_fsroot, fsroot,
oldpath, newpath, pool)
self.changes.append((path, action, plus, minus))
def handle_revision(db, command, repo, rev):
"""Adds a particular revision of the repository to the checkin database."""
revision = repo[rev]
for (path, action, plus, minus) in revision.changes:
directory, file = os.path.split(path)
commit = cvsdb.CreateCommit()
commit.SetRepository(repo.path)
commit.SetDirectory(directory)
commit.SetFile(file)
commit.SetRevision(str(rev))
commit.SetAuthor(revision.author)
commit.SetDescription(revision.log)
commit.SetTime(revision.date)
commit.SetPlusCount(plus)
commit.SetMinusCount(minus)
commit.SetBranch(None)
if action == 'add':
commit.SetTypeAdd()
elif action == 'remove':
commit.SetTypeRemove()
elif action == 'change':
commit.SetTypeChange()
if command == 'update':
result = db.CheckCommit(commit)
if result: continue # already recorded
# commit to database
db.AddCommit(commit)
pass
def main(pool, command, repository, rev=None):
db = cvsdb.ConnectDatabase()
repo = SvnRepo(repository, pool)
if rev:
handle_revision(db, command, repo, rev)
else:
for rev in range(repo.rev_max+1):
handle_revision(db, command, repo, rev)
def usage():
sys.stderr.write('Usage: %s {rebuild | update} <repository> [<revision>]\n'
% os.basename(sys.argv[0]))
sys.exit(1)
if __name__ == '__main__':
if len(sys.argv) < 3:
usage()
command = string.lower(sys.argv[1])
if command not in ('rebuild', 'update'):
sys.stderr.write('ERROR: unknown command %s\n' % command)
usage()
repository = sys.argv[2]
if not os.path.exists(repository):
sys.stderr.write('ERROR: could not find repository %s\n' % repository)
usage()
if len(sys.argv) > 3:
rev = sys.argv[3]
try:
rev = int(rev)
except ValueError:
sys.stderr.write('ERROR: revision "%s" is not numeric\n' % rev)
usage()
else:
rev = None
svn.core.run_app(main, command, repository, rev)
|