#!/usr/local/bin/python # $FreeBSD: svnadmin/tools/export.py 82 2008-05-24 06:25:22Z peter $ import string import sys import time import os import popen2 import tempfile from svn import core, fs, delta, repos # SVN's API structure is to do callbacks to a class to get notifications class ChangeReceiver(delta.Editor): def __init__(self, fs_root, base_root, rev, fs_ptr, pool): self.fs_root = fs_root self.base_root = base_root self.fs_ptr = fs_ptr self.rev = int(rev) self.pool = pool self.changes = [] def delete_entry(self, path, revision, parent_baton, pool): ### need more logic to detect 'replace' if fs.is_dir(self.base_root, '/' + path): pass else: self.changes.append(['D', path]) def add_file(self, path, parent_baton, copyfrom_path, copyfrom_revision, file_pool): self.changes.append(['A', path]) return [ '_', ' ', None ] def open_file(self, path, parent_baton, base_revision, file_pool): return [ '_', ' ', path ] def apply_textdelta(self, file_baton, base_checksum): text_mod, prop_mod, path = file_baton file_baton[0] = 'U' # no handler return None def change_file_prop(self, file_baton, name, value, pool): text_mod, prop_mod, path = file_baton file_baton[1] = 'U' def close_file(self, file_baton, text_checksum): text_mod, prop_mod, path = file_baton # test the path. it will be None if we added this file. if path: status = text_mod + prop_mod # was there some kind of change? if status != '_ ': self.changes.append(['U', path]) def _basename(path): idx = path.rfind('/') if idx == -1: return path return path[idx+1:] def _dirname(path): idx = path.rfind('/') if idx == -1: return '' return path[:idx] def do_cvs(cvspath, dir, cmd): ioerror = False print("cvs path %s, dir %s, cmd %s" % (cvspath, dir, cmd)) cwd = os.getcwd() os.chdir(os.path.join(cvspath, dir)) pipe = popen2.Popen3(cmd) os.chdir(cwd) output = '' try: output = pipe.fromchild.readlines() except IOError: ioerror = True try: pipe.fromchild.close() except IOError: ioerror = True try: pipe.tochild.close() except IOError: ioerror = True failed = pipe.wait() == 0 and not ioerror print 'Cvs output: ', output return failed def dump_file(fs_ptr, fs_root, rev, svnpath, cvspath, author, date, pool, workpath): kw = fs.node_prop(fs_root, svnpath, core.SVN_PROP_KEYWORDS) if not kw: kw = '' #str = '$' + 'FreeBSD: %s %s %s %s $' % (cvspath, rev, date, author) str = '$' + 'FreeBSD SVNid: %s %s %s %s $' % (cvspath, rev, date, author) print 'Author: ' + author subpool = core.svn_pool_create(pool) stream = core.Stream(fs.file_contents(fs_root, svnpath, subpool)) string = "" while 1: data = stream.read(core.SVN_STREAM_CHUNK_SIZE) string += data if len(data) < core.SVN_STREAM_CHUNK_SIZE: break # Expand keywords if kw == r'FreeBSD=%H': old = '$' + 'FreeBSD$' string = string.replace(old, str) cvsfile = os.path.join(workpath, cvspath) # sys.stdout.write('File contents:\n=========\n') # sys.stdout.write(string) # sys.stdout.write('=========\n') outfile = os.open(cvsfile, os.O_CREAT | os.O_TRUNC | os.O_WRONLY) if not outfile: sys.exit('cannot open %s for write' % cvsfile) n = os.write(outfile, string) if n != len(string): sys.exit('short write. %d instead of %d' % (n, len(string))) core.svn_pool_destroy(subpool) # Hard coded for now. This will be automatically implied based on path transforms. maptable = [ ( 'head/', 5, None ), ( 'stable/3/', 9, 'RELENG_3' ), ( 'stable/4/', 9, 'RELENG_4' ), ( 'stable/5/', 9, 'RELENG_5' ), ( 'stable/6/', 9, 'RELENG_6' ), ( 'stable/7/', 9, 'RELENG_7' ), ( 'releng/6.0/', 11, 'RELENG_6_0' ), ( 'releng/6.1/', 11, 'RELENG_6_1' ), ( 'releng/6.2/', 11, 'RELENG_6_2' ), ( 'releng/6.3/', 11, 'RELENG_6_3' ), ( 'releng/6.4/', 11, 'RELENG_6_4' ), ( 'releng/7.0/', 11, 'RELENG_7_0' ), ( 'releng/7.1/', 11, 'RELENG_7_1' ), ( 'releng/7.2/', 11, 'RELENG_7_2' ), ] def map2cvs(svnpath): #print maptable for prefix, plen, branch in maptable: #print 'p: %s, plen: %s, branch:%s' % (prefix, plen, branch) if svnpath.startswith(prefix): return 'src/' + svnpath[plen:], branch return None, None def testmap(): print map2cvs('head/bin/ls/ls.c') print map2cvs('stable/7/bin/ls/ls.c') print map2cvs('stable/6/bin/ls/ls.c') print map2cvs('stable/4/bin/ls/ls.c') print map2cvs('releng/7.0/bin/ls/ls.c') print map2cvs('releng/7.1/bin/ls/ls.c') print map2cvs('release/7.1.0/bin/ls/ls.c') sys.exit(1) def makedirs(cvspath, path): #print 'Makedirs:', cvspath, path if not path.startswith('src'): sys.exit('Illegal path %s' % path) if path == 'src': return makedirs(cvspath, _dirname(path)) fullpath = os.path.join(cvspath, path) if os.path.isfile(fullpath): sys.exit('Dest dir is a file' % path) if not os.path.isdir(fullpath): try: #print "Making directory " + fullpath os.makedirs(fullpath) do_cvs(cvspath, _dirname(path), "cvs -q add %s" % _basename(path)) except OSError: sys.exit('Cannot mkdir %s' % path) #print 'Dirpath complete: ' + path def exportrev(pool, fs_ptr, rev, cvspath): def authz_cb(root, path, pool): return True # Connect up to the revision fs_root = fs.revision_root(fs_ptr, rev, pool) base_root = fs.revision_root(fs_ptr, rev - 1, pool) editor = ChangeReceiver(fs_root, base_root, rev, fs_ptr, pool) e_ptr, e_baton = delta.make_editor(editor, pool) repos.dir_delta(base_root, '', '', fs_root, '', e_ptr, e_baton, authz_cb, 0, 1, 0, 0, pool) # Author author = fs.revision_prop(fs_ptr, rev, core.SVN_PROP_REVISION_AUTHOR) if not author: author = 'NoAuthor' # Date date = fs.revision_prop(fs_ptr, rev, core.SVN_PROP_REVISION_DATE) if date: aprtime = core.svn_time_from_cstring(date) secs = aprtime / 1000000 # aprtime is microseconds; make seconds tm = time.gmtime(secs) date = time.strftime('%Y-%m-%d %H:%M:%SZ', tm) else: date = 'NoDate' # Build log message to export cvslog = 'SVN rev %d on %s by %s\n' % (rev, date, author) svnlog = fs.revision_prop(fs_ptr, rev, core.SVN_PROP_REVISION_LOG) if svnlog: cvslog += '\n' + svnlog for k, p in editor.changes: #print 'Path ', p (path, tag) = map2cvs(p) if not path: continue if tag: workpath = os.path.join(cvspath, tag) uptag = '-r ' + tag else: workpath = cvspath uptag = '-A' #print workpath if not os.path.isdir(workpath): os.makedirs(workpath) if not os.path.isdir(os.path.join(workpath, 'src')): do_cvs(workpath, '', "cvs -q co %s -l src" % uptag) # at this point, the top directory and /src should exist #print p, path, k makedirs(workpath, _dirname(path)) # Now the directory for the files must exist, and branch tag will be sticky assert os.path.isdir(os.path.join(workpath, _dirname(path))) if k == 'A' or k == 'U': print 'add/update file ' + path + '.' destpath = os.path.join(workpath, path) existed = os.path.isfile(destpath) dump_file(fs_ptr, fs_root, rev, p, path, author, date, pool, workpath) if not existed: print 'cvs add file ' + path + '.' do_cvs(workpath, _dirname(path), "cvs -q add %s" % _basename(path)) elif k == 'D': print 'cvs rm -f file ' + path + '.' do_cvs(workpath, _dirname(path), "cvs -q rm -f %s" % _basename(path)) else: sys.exit('FAIL') fd, logfile = tempfile.mkstemp() os.write(fd, cvslog) os.close(fd) do_cvs(workpath, 'src', "cvs -q commit -F %s" % logfile) os.remove(logfile) def export(pool, repos_path, askrev, cvspath): repos_path = core.svn_path_canonicalize(repos_path) fs_ptr = repos.fs(repos.open(repos_path, pool)) curr_rev = fs.youngest_rev(fs_ptr) if askrev: exportrev(pool, fs_ptr, int(askrev), cvspath) else: for rev in xrange(1, curr_rev + 1): print "===========\nExporting change %d\n===========" % rev exportrev(pool, fs_ptr, rev, cvspath) if __name__ == '__main__': #assert len(sys.argv) == 3 #Under normal circumstances, called once per commit to export the changes in question. #core.run_app(export, sys.argv[1], int(sys.argv[2]), '/s/peter/work/cvs') core.run_app(export, '/s/repo', 0, '/s/peter/work/cvs')