summaryrefslogtreecommitdiff
path: root/tools/client-side/detach.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/client-side/detach.py')
-rwxr-xr-xtools/client-side/detach.py271
1 files changed, 271 insertions, 0 deletions
diff --git a/tools/client-side/detach.py b/tools/client-side/detach.py
new file mode 100755
index 0000000..84c725a
--- /dev/null
+++ b/tools/client-side/detach.py
@@ -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()