#!/usr/bin/env python
#
# ====================================================================
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ====================================================================
# TODO: if this was part of core subversion, we'd have all sorts of nifty
# checks, and could use a lot of existing code.
import os
import re
import sys
import shutil
import sqlite3
def usage():
print("""usage: %s WC_SRC TARGET
Detatch the working copy subdirectory given by WC_SRC to TARGET. This is
equivalent to copying WC_SRC to TARGET, but it inserts a new set of Subversion
metadata into TARGET/.svn, making TARGET a proper independent working copy.
""" % sys.argv[0])
sys.exit(1)
def find_wcroot(wcdir):
wcroot = os.path.abspath(wcdir)
old_wcroot = ''
while wcroot != old_wcroot:
if os.path.exists(os.path.join(wcroot, '.svn', 'wc.db')):
return wcroot
old_wcroot = wcroot
wcroot = os.path.dirname(wcroot)
return None
def migrate_sqlite(wc_src, target, wcroot):
src_conn = sqlite3.connect(os.path.join(wcroot, '.svn', 'wc.db'))
dst_conn = sqlite3.connect(os.path.join(target, '.svn', 'wc.db'))
local_relsrc = os.path.relpath(wc_src, wcroot)
src_c = src_conn.cursor()
dst_c = dst_conn.cursor()
# We're only going to attempt this if there are no locks or work queue
# items in the source database
### This could probably be tightened up, but for now this suffices
src_c.execute('select count(*) from wc_lock')
count = int(src_c.fetchone()[0])
assert count == 0
src_c.execute('select count(*) from work_queue')
count = int(src_c.fetchone()[0])
assert count == 0
# Copy over the schema
src_c.execute('pragma user_version')
user_version = src_c.fetchone()[0]
# We only know how to handle format 29 working copies
assert user_version == 29
### For some reason, sqlite doesn't like to parameterize the pragma statement
dst_c.execute('pragma user_version = %d' % user_version)
src_c.execute('select name, sql from sqlite_master')
for row in src_c:
if not row[0].startswith('sqlite_'):
dst_c.execute(row[1])
# Insert wcroot row
dst_c.execute('insert into wcroot (id, local_abspath) values (?, ?)',
(1, None))
# Copy repositories rows
### Perhaps prune the repositories based upon the new NODES set?
src_c.execute('select * from repository')
for row in src_c:
dst_c.execute('insert into repository values (?, ?, ?)',
row)
# Copy the root node
src_c.execute('select * from nodes where local_relpath = ?',
(local_relsrc,))
row = list(src_c.fetchone())
row[1] = ''
row[3] = None
dst_c.execute('''insert into nodes values
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?, ?, ?, ?)''', row)
# Copy children nodes rows
src_c.execute('select * from nodes where local_relpath like ?',
(local_relsrc + '/%', ))
for row in src_c:
row = list(row)
row[1] = row[1][len(local_relsrc) + 1:]
row[3] = row[3][len(local_relsrc) + 1:]
dst_c.execute('''insert into nodes values
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?, ?, ?, ?)''',
row)
# Copy root actual_node
src_c.execute('select * from actual_node where local_relpath = ?',
(local_relsrc, ))
row = src_c.fetchone()
if row:
row = list(row)
row[1] = ''
row[2] = None
dst_c.execute('''insert into actual_node values
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''', row)
src_c.execute('select * from actual_node where local_relpath like ?',
(local_relsrc + '/%', ))
for row in src_c:
row = list(row)
row[1] = row[1][len(local_relsrc) + 1:]
row[2] = row[2][len(local_relsrc) + 1:]
dst_c.execute('''insert into actual_node values
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''', row)
# Hard to know which locks we care about, so just copy 'em all (there aren't
# likely to be many)
src_c.execute('select * from lock')
for row in src_c:
dst_c.execute('insert into locks values (?, ?, ?, ?, ?, ?)', row)
# EXTERNALS
src_c.execute('select * from externals where local_relpath = ?',
(local_relsrc, ))
row = src_c.fetchone()
if row:
row = list(row)
row[1] = ''
row[2] = None
dst_c.execute('''insert into externals values
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''', row)
src_c.execute('select * from externals where local_relpath like ?',
(local_relsrc + '/%', ))
for row in src_c:
row = list(row)
row[1] = row[1][len(local_relsrc) + 1:]
row[2] = row[2][len(local_relsrc) + 1:]
dst_c.execute('''insert into externals values
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''', row)
dst_conn.commit()
src_conn.close()
dst_conn.close()
def migrate_pristines(wc_src, target, wcroot):
src_conn = sqlite3.connect(os.path.join(wcroot, '.svn', 'wc.db'))
dst_conn = sqlite3.connect(os.path.join(target, '.svn', 'wc.db'))
src_c = src_conn.cursor()
dst_c = dst_conn.cursor()
regex = re.compile('\$((?:md5 *)|(?:sha1))\$(.*)')
src_proot = os.path.join(wcroot, '.svn', 'pristine')
target_proot = os.path.join(target, '.svn', 'pristine')
checksums = {}
# Grab anything which needs a pristine
src_c.execute('''select checksum from nodes
union
select older_checksum from actual_node
union
select left_checksum from actual_node
union
select right_checksum from actual_node''')
for row in src_c:
if row[0]:
match = regex.match(row[0])
assert match
pristine = match.group(2)
if pristine in checksums:
checksums[pristine] += 1
else:
checksums[pristine] = 1
for pristine, count in checksums.items():
# Copy the pristines themselves over
pdir = os.path.join(target_proot, pristine[0:2])
if not os.path.exists(pdir):
os.mkdir(pdir)
path = os.path.join(pristine[0:2], pristine + '.svn-base')
if os.path.exists(os.path.join(target_proot, path)):
dst_c.execute
else:
shutil.copy2(os.path.join(src_proot, path),
os.path.join(target_proot, path))
src_c.execute('select size, md5_checksum from pristine where checksum=?',
('$sha1$' + pristine, ) )
(size, md5) = src_c.fetchone()
# Insert a db row for the pristine
dst_c.execute('insert into pristine values (?, NULL, ?, ?, ?)',
('$sha1$' + pristine, size, count, md5))
dst_conn.commit()
src_conn.close()
dst_conn.close()
def migrate_metadata(wc_src, target, wcroot):
# Make paths
os.mkdir(os.path.join(target, '.svn'))
os.mkdir(os.path.join(target, '.svn', 'tmp'))
os.mkdir(os.path.join(target, '.svn', 'pristine'))
open(os.path.join(target, '.svn', 'format'), 'w').write('12')
open(os.path.join(target, '.svn', 'entries'), 'w').write('12')
# Two major bits: sqlite data and pristines
migrate_sqlite(wc_src, os.path.abspath(target), wcroot)
migrate_pristines(wc_src, target, wcroot)
def main():
if len(sys.argv) < 3:
usage()
wc_src = os.path.normpath(sys.argv[1])
if not os.path.isdir(wc_src):
print("%s does not exist or is not a directory" % wc_src)
sys.exit(1)
target = os.path.normpath(sys.argv[2])
if os.path.exists(target):
print("Target '%s' already exists" % target)
sys.exit(1)
wcroot = find_wcroot(wc_src)
if not wcroot:
print("'%s' is not part of a working copy" % wc_src)
sys.exit(1)
# Use the OS to copy the subdirectory over to the target
shutil.copytree(wc_src, target)
# Now migrate the worky copy data
migrate_metadata(wc_src, target, wcroot)
if __name__ == '__main__':
raise Exception("""This script is unfinished and not ready to be used on live data.
Trust us.""")
main()