#!/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()