Author: hwright Date: Mon Jul 25 19:56:35 2011 New Revision: 1150868 URL: http://svn.apache.org/viewvc?rev=1150868&view=rev Log: Add a rudimentary detatch script to the client-side tools. This is far from comprehensive or exhaustively tested, but should work in most scenarios.
Improvements, patches, testing, etc welcome and encouraged. * tools/client-side/detatch.py: New. Added: subversion/trunk/tools/client-side/detatch.py (with props) Added: subversion/trunk/tools/client-side/detatch.py URL: http://svn.apache.org/viewvc/subversion/trunk/tools/client-side/detatch.py?rev=1150868&view=auto ============================================================================== --- subversion/trunk/tools/client-side/detatch.py (added) +++ subversion/trunk/tools/client-side/detatch.py Mon Jul 25 19:56:35 2011 @@ -0,0 +1,271 @@ +#!/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() Propchange: subversion/trunk/tools/client-side/detatch.py ------------------------------------------------------------------------------ svn:eol-style = native Propchange: subversion/trunk/tools/client-side/detatch.py ------------------------------------------------------------------------------ svn:executable = *