I don't expect anyone to review the whole of this, but any comments would be welcome. It's about time I posted what I'm up to. It's not a "finished" patch, by any stretch of the imagination, so please don't commit it.
[[[ For Obliterate, teach the BDB FS implementation to duplicate an existing revision into a new transaction, with a new txn-id but otherwise identical. The purpose is to be able to modify the new txn (delete one or more nodes out of it) and then replace the old one with it. * subversion/libsvn_fs_base/revs-txns.c (rep_dup, copy_dup, node_rev_dup): New functions to make a deep copy of a rep, a copy or a node-rev, while also changing any references to the old txn-id. (begin_txn_args): Rename the 'rev' member to 'based_on_rev' for clarity. (txn_body_begin_txn, svn_fs_base__begin_txn): Adjust accordingly. (txn_body_begin_obliteration_txn): Add code to make a recursive duplicate of all the contents of the txn. Add documentation. (svn_fs_base__begin_obliteration_txn): Return success, because it is now implemented. Rename 'rev' to 'replacing_rev'. Add documentation. * subversion/libsvn_fs_base/revs-txns.h (svn_fs_base__get_txn_ids): Fix a typo in the doc string. (svn_fs_base__begin_obliteration_txn): Rename the 'rev' argument to 'replacing_rev' for clarity. * subversion/libsvn_fs_base/tree.c (svn_fs_base__commit_obliteration_txn): ### Just return success, for testing, even though not implemented yet. * subversion/tests/cmdline/obliterate_tests.py New file, with a skeleton of a test for obliterate. So far, this just exercises the obliterate operation and doesn't actually check the result. * subversion/tests/cmdline/svntest/objects.py New file, with classes for easily performing common testing steps on a repository and on a WC. ### The repos class "dump" function calls an external script in my home dir. ===== Concerns/unfinished business: - Should I be using the DAG layer to do these manipulations? Are there DAG functions that already do such duplication, or part of it? I found some that were useful, but don't understand what the significant difference is between the DAG layer and other levels. (I did read the description in <> section "".) - All the "dup" functions could take the old-txn-id as a parameter, and only change txn-ids that match it. If there are any txn-id fields that refer to the current txn in some cases but not in other cases, then we need this, and need to compare with the old-txn-id to decide whether to change it. Are there any such cases? - I don't know whether I should change the copy-ids. I don't think I should, because future revs refer to them, but I can't dup the copies table rows in the same way unless I give them new copy-ids. If necessary, I could possibly dup them to new ids and then translate the new ids back to the old ones at "commit-obliteration" time. - In a normal txn, are the "changes" rows used and updated during txn building, or only filled in at commit time? If the former, I need to dup them at begin-obliteration-txn time; if the latter, at commit-obliteration-txn time. - In "rep_dup()", when I dup a child rep in a "rep window" (or "delta chunk"), I need to set an "offset" field. I expect there's a function I should be using to ensure that gets set correctly. ]]] - Julian
* subversion/libsvn_fs_base/revs-txns.c (rep_dup, copy_dup, node_rev_dup): New functions. (begin_txn_args): Rename the 'rev' member to 'based_on_rev' for clarity. (txn_body_begin_txn, svn_fs_base__begin_txn): Adjust accordingly. (txn_body_begin_obliteration_txn): Add code to make a recursive duplicate of all the contents of the txn. Add documentation. (svn_fs_base__begin_obliteration_txn): Return success, because it is now implemented. Rename 'rev' to 'replacing_rev'. Add documentation. * subversion/libsvn_fs_base/revs-txns.h (svn_fs_base__get_txn_ids): Fix a typo in the doc string. (svn_fs_base__begin_obliteration_txn): Rename the 'rev' argument to 'replacing_rev' for clarity. * subversion/libsvn_fs_base/tree.c (svn_fs_base__commit_obliteration_txn): ### Just return success, for testing, even though not implemented yet. * subversion/tests/cmdline/obliterate_tests.py New file, with a skeleton of a test for obliterate. So far, this just exercises the obliterate operation and doesn't actually check the result. * subversion/tests/cmdline/svntest/objects.py New file, with classes for easily performing common testing steps on a repository and on a WC. --This line, and those below, will be ignored-- Index: subversion/libsvn_fs_base/revs-txns.c =================================================================== --- subversion/libsvn_fs_base/revs-txns.c (revision 887172) +++ subversion/libsvn_fs_base/revs-txns.c (working copy) @@ -42,8 +42,11 @@ #include "id.h" #include "bdb/rev-table.h" #include "bdb/txn-table.h" +#include "bdb/nodes-table.h" +#include "bdb/reps-table.h" #include "bdb/copies-table.h" #include "bdb/changes-table.h" +#include "bdb/strings-table.h" #include "../libsvn_fs/fs-loader.h" #include "svn_private_config.h" @@ -626,6 +629,171 @@ static txn_vtable_t txn_vtable = { }; +/* Create a new representation that is a duplicate of the one keyed by KEY, + * but make the duplicate refer to TXN_ID2. + * Set *NEW_KEY to the key of the new representation. + * Work within TRAIL within FS. */ +static svn_error_t * +rep_dup(const char **new_key, + const char *txn_id2, + const char *key, + trail_t *trail, + apr_pool_t *pool) +{ + representation_t *rep; + + SVN_ERR(svn_fs_bdb__read_rep(&rep, trail->fs, key, trail, pool)); + + rep->txn_id = txn_id2; + + /* Dup the strings and any recursively used representations */ + if (rep->kind == rep_kind_fulltext) + { + SVN_ERR(svn_fs_bdb__string_copy(trail->fs, &rep->contents.fulltext.string_key, + rep->contents.fulltext.string_key, + trail, pool)); + } + else + { + apr_array_header_t *chunks = rep->contents.delta.chunks; + int i; + + for (i = 0; i < chunks->nelts; i++) + { + rep_delta_chunk_t *w = APR_ARRAY_IDX(chunks, i, rep_delta_chunk_t *); + + SVN_ERR(svn_fs_bdb__string_copy(trail->fs, &w->string_key, w->string_key, + trail, pool)); + SVN_ERR(rep_dup(&w->rep_key, txn_id2, w->rep_key, trail, pool)); + /* ### w->offset = calc_offset(w->rep_key); */ + } + } + + SVN_ERR(svn_fs_bdb__write_new_rep(new_key, trail->fs, rep, trail, pool)); + return SVN_NO_ERROR; +} + +/* +def change_dup_body(change1, txn_id2): + change2 = dup(change1) + change2.node_rev_id.txn = txn_id2 + return change2 + +def changes_dup(txn_id1, txn_id2): + for change1 in changes<txn_id1>: + changes.add_row<txn_id2>(change_dup_body(change1, txn_id2)) +*/ + +/* Make a deep copy of the row keyed by OLD_COPY_ID in the "copies" table, + * and change any references (### to a specific old-txn-id) to TXN_ID2. + * (Specifically: change the txn_id component of the row's dst_noderev_id + * to TXN_ID2.) + * Set *NEW_COPY_ID to the key of the new row. + * Allocate *NEW_COPY_ID in RESULT_POOL. */ +static svn_error_t * +copy_dup(const char **new_copy_id, + const char *old_copy_id, + const char *txn_id2, + trail_t *trail, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + copy_t *copy; + const char *node_id, *copy_id; + + /* Get the old copy */ + SVN_ERR(svn_fs_bdb__get_copy(©, trail->fs, old_copy_id, trail, + scratch_pool)); + + /* Modify it: change dst_noderev_id's txn_id to TXN_ID2 */ + node_id = svn_fs_base__id_node_id(copy->dst_noderev_id); + copy_id = svn_fs_base__id_copy_id(copy->dst_noderev_id); + copy->dst_noderev_id = svn_fs_base__id_create(node_id, copy_id, txn_id2, + scratch_pool); + + /* Save the new copy */ + SVN_ERR(svn_fs_bdb__reserve_copy_id(new_copy_id, trail->fs, trail, + result_pool)); + SVN_ERR(svn_fs_bdb__create_copy(trail->fs, *new_copy_id, copy->src_path, + copy->src_txn_id, copy->dst_noderev_id, + copy->kind, trail, scratch_pool)); + return SVN_NO_ERROR; +} + +/* Create a new node-rev that is a deep copy of that keyed by OLD_ID, + * except ### + * The new node-rev-id is OLD_ID except with the txn-id field changed to TXN_ID2. + * Set *NEW_ID to the new node-rev-id, allocated in RESULT_POOL. + * Work within TRAIL within FS. + * + * ### Use svn_fs_base__dag_copy() instead? + */ +static svn_error_t * +node_rev_dup(const svn_fs_id_t **new_id, + const char *txn_id2, + const svn_fs_id_t *old_id, + trail_t *trail, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + node_revision_t *noderev; + const char *node_id, *copy_id, *txn_id; + svn_fs_id_t *id2; + + SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, trail->fs, old_id, trail, + scratch_pool)); + + /* Set ID2 to ID except with txn_id TXN_ID2 */ + node_id = svn_fs_base__id_node_id(old_id); + copy_id = svn_fs_base__id_copy_id(old_id); + txn_id = svn_fs_base__id_txn_id(old_id); + id2 = svn_fs_base__id_create(node_id, copy_id, txn_id2, result_pool); + + /* Dup the representation of its text or entries, and recurse to dup the + * node-revs of any children. */ + if (noderev->kind == svn_node_dir) + { + dag_node_t *dag_node; + apr_hash_t *entries; + apr_hash_index_t *hi; + + /* Get the children */ + SVN_ERR(svn_fs_base__dag_get_node(&dag_node, trail->fs, old_id, trail, + trail->pool)); + SVN_ERR(svn_fs_base__dag_dir_entries(&entries, dag_node, trail, + scratch_pool)); + + /* Dup the children (recursing) */ + for (hi = apr_hash_first(scratch_pool, entries); hi; + hi = apr_hash_next(hi)) + { + const char *child_name = svn_apr_hash_index_key(hi); + svn_fs_dirent_t *child_entry = svn_apr_hash_index_val(hi); + const svn_fs_id_t *new_noderev_id; + + SVN_ERR(node_rev_dup(&new_noderev_id, txn_id2, child_entry->id, + trail, scratch_pool, scratch_pool)); + SVN_ERR(svn_fs_base__dag_set_entry(dag_node, child_name, + new_noderev_id, txn_id, trail, + scratch_pool)); + /* ### Use instead: svn_fs_base__dag_clone_child() ? */ + } + } + else + { + SVN_ERR(rep_dup(&noderev->data_key, txn_id2, + noderev->data_key, trail, result_pool)); + noderev->data_key_uniquifier = NULL; /* ### ? */ + } + + SVN_ERR(svn_fs_bdb__put_node_revision(trail->fs, id2, noderev, trail, + scratch_pool)); + + *new_id = id2; + return SVN_NO_ERROR; +} + + /* Allocate and return a new transaction object in POOL for FS whose transaction ID is ID. ID is not copied. */ static svn_fs_txn_t * @@ -649,7 +817,7 @@ make_txn(svn_fs_t *fs, struct begin_txn_args { svn_fs_txn_t **txn_p; - svn_revnum_t rev; + svn_revnum_t based_on_rev; apr_uint32_t flags; }; @@ -661,7 +829,7 @@ txn_body_begin_txn(void *baton, trail_t const svn_fs_id_t *root_id; const char *txn_id; - SVN_ERR(svn_fs_base__rev_get_root(&root_id, trail->fs, args->rev, + SVN_ERR(svn_fs_base__rev_get_root(&root_id, trail->fs, args->based_on_rev, trail, trail->pool)); SVN_ERR(svn_fs_bdb__create_txn(&txn_id, trail->fs, root_id, trail, trail->pool)); @@ -688,29 +856,118 @@ txn_body_begin_txn(void *baton, trail_t SVN_ERR(txn_body_change_txn_prop(&cpargs, trail)); } - *args->txn_p = make_txn(trail->fs, txn_id, args->rev, trail->pool); + *args->txn_p = make_txn(trail->fs, txn_id, args->based_on_rev, trail->pool); return SVN_NO_ERROR; } +/* Create a new empty transaction that is able to become a replacement for + * an existing revision. + * + * This is like txn_body_begin_txn() except it does not support the + * CHECK_OOD or CHECK_LOCKS flags. + * + * ### The first part of this function does nothing that txn_body_begin_txn() + * can't do. + * + * Set BATON->args->txn_p to point to the new transaction. + * BATON->args->based_on_rev is the revision on which the existing revision + * is based, i.e. one less than the number of the revision to be replaced. + * BATON->args->flags must be 0: specifically, the CHECK_OOD and CHECK_LOCKS + * flags are not supported. + * BATON is of type (struct begin_txn_args *). + */ static svn_error_t * txn_body_begin_obliteration_txn(void *baton, trail_t *trail) { struct begin_txn_args *args = baton; - const svn_fs_id_t *root_id; - const char *txn_id; - - SVN_ERR(svn_fs_base__rev_get_root(&root_id, trail->fs, args->rev, - trail, trail->pool)); - SVN_ERR(svn_fs_bdb__create_txn(&txn_id, trail->fs, root_id, + int replacing_rev = args->based_on_rev + 1; + const svn_fs_id_t *base_root_id; + const char *old_txn_id, *new_txn_id; + transaction_t *old_txn, *new_txn; + + SVN_ERR_ASSERT(! (args->flags & SVN_FS_TXN_CHECK_OOD)); + SVN_ERR_ASSERT(! (args->flags & SVN_FS_TXN_CHECK_LOCKS)); + + /* Create a new txn, like txn_body_begin_txn() does. */ + SVN_ERR(svn_fs_base__rev_get_root(&base_root_id, trail->fs, + args->based_on_rev, trail, trail->pool)); + SVN_ERR(svn_fs_bdb__create_txn(&new_txn_id, trail->fs, base_root_id, trail, trail->pool)); + /* ### Use svn_fs_base__dag_clone_root() instead? */ - /* ### No need for "CHECK_OOD" and "CHECK_LOCKS" like the non-oblit case? */ + /* Get the old and new txns */ + SVN_ERR(svn_fs_base__rev_get_txn_id(&old_txn_id, trail->fs, replacing_rev, + trail, trail->pool)); + SVN_ERR(svn_fs_bdb__get_txn(&old_txn, trail->fs, old_txn_id, trail, + trail->pool)); + SVN_ERR(svn_fs_bdb__get_txn(&new_txn, trail->fs, new_txn_id, trail, + trail->pool)); + + /* Populate the transaction NEW_TXN with a clone of the changes described + * by revision REPLACING_REV, in TRAIL in FS. + * + * This is like a combination of "dup the txn" and "make the txn mutable". + * "Dup the txn" means making a deep copy, but with a new txn id. + * "Make mutable" is like the opposite of finalizing a txn. + * + * Dup r50: + * * dup TRANSACTIONS<t50> to TRANSACTIONS<t50'> + * except (kind -> transaction) + * * dup all NODES<*.*.t50> to NODES<*.*.t50'> + * * dup all referenced REPRESENTATIONS<*> to REPRESENTATIONS<*'> + * * dup all CHANGES<t50> to CHANGES<t50'> + * * create new STRINGS<*> where necessary (###?) + * * don't dup NODE-ORIGINS<node_id> + * * don't dup CHECKSUM-REPS<csum> + * * don't dup COPIES<cpy_id> + * (We need to keep the copy IDs the same, but will need to modify the + * copy src_txn fields.) + * + * Make mutable: + * * change txn kind from "committed" to "transaction" + * * do something with COPIES? + */ + + SVN_ERR_ASSERT(new_txn->kind == transaction_kind_normal); + + /* Dup the old txn's root node-rev (recursively). + * ### What root node-rev did the new txn have when created? Should we be + * modifying that one instead of replacing it? */ + SVN_ERR(node_rev_dup(&new_txn->root_id, new_txn_id, old_txn->root_id, + trail, trail->pool, trail->pool)); - *args->txn_p = make_txn(trail->fs, txn_id, args->rev, trail->pool); - return SVN_NO_ERROR; -} + /* Dup txn->proplist */ + new_txn->proplist = old_txn->proplist; + /* Dup txn->copies */ + if (old_txn->copies) + { + int i; + new_txn->copies = apr_array_make(trail->pool, old_txn->copies->nelts, + sizeof(const char *)); + for (i = 0; i < old_txn->copies->nelts; i++) + { + const char *old_copy_id = APR_ARRAY_IDX(new_txn->copies, i, const char *); + const char *new_copy_id; + + SVN_ERR(copy_dup(&new_copy_id, old_copy_id, new_txn_id, trail, + trail->pool, trail->pool)); + APR_ARRAY_IDX(new_txn->copies, i, const char *) = new_copy_id; + } + } + + /* ### TODO: Dup the "changes" that are keyed by the txn_id. */ + + /* Save the modified transaction */ + SVN_ERR(svn_fs_bdb__put_txn(trail->fs, new_txn, new_txn_id, trail, + trail->pool)); + + /* Make and return an in-memory txn object referring to the new txn */ + *args->txn_p = make_txn(trail->fs, new_txn_id, args->based_on_rev, + trail->pool); + return SVN_NO_ERROR; +} /* Note: it is acceptable for this function to call back into @@ -729,7 +986,7 @@ svn_fs_base__begin_txn(svn_fs_txn_t **tx SVN_ERR(svn_fs__check_fs(fs, TRUE)); args.txn_p = &txn; - args.rev = rev; + args.based_on_rev = rev; args.flags = flags; SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_begin_txn, &args, FALSE, pool)); @@ -746,11 +1003,17 @@ svn_fs_base__begin_txn(svn_fs_txn_t **tx &date, pool); } - +/* Create a new transaction in FS that is a mutable clone of the transaction + * in revision REPLACING_REV and is intended to replace it. Set *TXN_P to + * point to the new transaction. + * + * This is like svn_fs_base__begin_txn() except that is populates the new txn + * with a mutable clone of revision REPLACING_REV, and it does not support the + * CHECK_OOD and CHECK_LOCKS flags, and it does not change the date stamp. */ svn_error_t * svn_fs_base__begin_obliteration_txn(svn_fs_txn_t **txn_p, svn_fs_t *fs, - svn_revnum_t rev, + svn_revnum_t replacing_rev, apr_pool_t *pool) { svn_fs_txn_t *txn; @@ -758,15 +1021,18 @@ svn_fs_base__begin_obliteration_txn(svn_ SVN_ERR(svn_fs__check_fs(fs, TRUE)); + /* Create a new, empty txn. */ + /* Dup replacing_rev's txn into the new txn. */ + /* ### Does the dup need to be inside or outside the retry_txn? It's inside. */ args.txn_p = &txn; - args.rev = rev; + args.based_on_rev = replacing_rev - 1; args.flags = 0; SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_begin_obliteration_txn, &args, FALSE, pool)); *txn_p = txn; - return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL); + return SVN_NO_ERROR; } Index: subversion/libsvn_fs_base/revs-txns.h =================================================================== --- subversion/libsvn_fs_base/revs-txns.h (revision 887172) +++ subversion/libsvn_fs_base/revs-txns.h (working copy) @@ -91,7 +91,7 @@ svn_error_t *svn_fs_base__txn_get_revisi apr_pool_t *pool); -/* Retrieve information about the Subversion transaction SVN_TXN from +/* Retrieve information about the Subversion transaction TXN_NAME from the `transactions' table of FS, as part of TRAIL. Set *ROOT_ID_P to the ID of the transaction's root directory. Set *BASE_ROOT_ID_P to the ID of the root directory of the @@ -191,7 +191,8 @@ svn_error_t *svn_fs_base__begin_txn(svn_ revision REV. The new transaction is returned in *TXN_P. Allocate the new transaction structure from POOL. */ svn_error_t *svn_fs_base__begin_obliteration_txn(svn_fs_txn_t **txn_p, - svn_fs_t *fs, svn_revnum_t rev, + svn_fs_t *fs, + svn_revnum_t replacing_rev, apr_pool_t *pool); svn_error_t *svn_fs_base__open_txn(svn_fs_txn_t **txn, svn_fs_t *fs, Index: subversion/libsvn_fs_base/tree.c =================================================================== --- subversion/libsvn_fs_base/tree.c (revision 887172) +++ subversion/libsvn_fs_base/tree.c (working copy) @@ -2780,8 +2780,8 @@ svn_fs_base__commit_obliteration_txn(svn { /* svn_fs_t *fs = txn->fs; */ - return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL); - /* return SVN_NO_ERROR; */ + /* return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, NULL); */ + return SVN_NO_ERROR; } Index: subversion/tests/cmdline/obliterate_tests.py =================================================================== --- subversion/tests/cmdline/obliterate_tests.py (revision 0) +++ subversion/tests/cmdline/obliterate_tests.py (working copy) @@ -0,0 +1,86 @@ +#!/usr/bin/env python +# +# obliterate_tests.py: testing Obliterate +# +# Subversion is a tool for revision control. +# See http://subversion.tigris.org for more information. +# +# ==================================================================== +# 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. +###################################################################### + +# General modules +import shutil, sys, re, os + +# Our testing module +import svntest +from svntest import wc, objects + +# (abbreviation) +Item = wc.StateItem +XFail = svntest.testcase.XFail +Skip = svntest.testcase.Skip +SkipUnless = svntest.testcase.SkipUnless + + +###################################################################### +# Tests +# +# Each test must return on success or raise on failure. + +#---------------------------------------------------------------------- + +def obliterate_1(sbox): + "test svn obliterate" + + sbox.build() + wc = objects.SvnWC(sbox) + repo = wc.repo + os.chdir(wc.wc_dir) + + # Set up a repository containing scenarios for obliteration + wc.mkdir('d') + f = 'd/foo' + wc.file_create(f, 'Pear\n') + wc.commit() + wc.file_modify(f, 'Apple\n') + apple_rev = wc.commit() + wc.file_modify(f, 'Orange\n') + wc.commit() + + # Dump the repository state + repo.dump('before.dump') + + # Obliterate d/f...@{content=apple} + repo.obliterate_node_rev(f, apple_rev) + + # Dump the repository state + repo.dump('after.dump') + + +######################################################################## +# Run the tests + +# list all tests here, starting with None: +test_list = [ None, + obliterate_1, + ] + +if __name__ == '__main__': + svntest.main.run_tests(test_list) + # NOTREACHED Property changes on: subversion/tests/cmdline/obliterate_tests.py ___________________________________________________________________ Added: svn:executable ## -0,0 +1 ## +* Added: svn:mime-type ## -0,0 +1 ## +text/x-python Index: subversion/tests/cmdline/svntest/objects.py =================================================================== --- subversion/tests/cmdline/svntest/objects.py (revision 0) +++ subversion/tests/cmdline/svntest/objects.py (working copy) @@ -0,0 +1,208 @@ +#!/usr/bin/env python +# +# objects.py: Objects that keep track of state during a test +# +# Subversion is a tool for revision control. +# See http://subversion.tigris.org for more information. +# +# ==================================================================== +# 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. +###################################################################### + +# General modules +import shutil, sys, re, os, subprocess + +import csvn.wc + +# Our testing module +import svntest +from svntest import wc, actions, main, verify + + +###################################################################### +# Helpers + +def local_path(path): + "Convert a path from '/' separators to the local style." + return os.sep.join(path.split('/')) + + +###################################################################### +# Class SvnRepository + +class SvnRepository: + """An object of class SvnRepository represents a Subversion repository, + providing both a client-side view and a server-side view.""" + + def __init__(self, repo_url, repo_dir): + self.repo_url = repo_url + self.repo_dir = repo_dir + self.head_rev = 1 + + def dump(self, output_dir): + """Dump the repository into the directory OUTPUT_DIR""" + ldir = local_path(output_dir) + os.mkdir(ldir) + print "## SvnRepository::dump(rep_dir=" + self.repo_dir + ")" + + """Run a BDB dump on the repository""" + subprocess.call(["/home/julianfoad/bin/svn-dump-bdb", self.repo_dir, ldir]) + + """Run 'svnadmin dump' on the repository.""" + exit_code, stdout, stderr = \ + actions.run_and_verify_svnadmin(None, None, None, + 'dump', self.repo_dir) + ldumpfile = local_path(output_dir + "/svnadmin.dump") + main.file_write(ldumpfile, ''.join(stderr)) + main.file_append(ldumpfile, ''.join(stdout)) + + + def obliterate_node_rev(self, path, rev): + """Obliterate the single node-rev PATH in revision REV.""" + actions.run_and_verify_svn(None, None, [], + 'obliterate', + self.repo_url + '/' + path + '@' + str(rev)) + + +###################################################################### +# Class SvnWC + +class SvnWC: + """An object of class SvnWC represents a WC, and provides methods for + operating on it. It keeps track of the state of the WC and of the + repository, so that the expected results of common operations are + automatically known. Any relative paths are relative to the WC dir. + """ + + def __init__(self, sbox): + """Initialize the object to use the WC and repository of the given + sandbox SBOX, which must already be initialized and checked out. + """ + self.wc_dir = os.path.join(os.getcwd(), sbox.wc_dir) + self.state = wc.State(self.wc_dir, {}) + + # ### experimenting with csvn (Python Ctypes bindings) + self.wc = csvn.wc.WC(self.wc_dir) + + # The 'repo' object may not strictly belong in the WC object, but it + # is here for convenience, for now. When we write an SvnWC constructor + # that doesn't know the full repo details (only its URL), then this may + # have to be moved out. + self.repo = SvnRepository(sbox.repo_url, os.path.join(os.getcwd(), sbox.repo_dir)) + + def __str__(self): + return "SvnWC(head_rev=" + str(self.repo.head_rev) + ", state={" + \ + str(self.state.desc) + \ + "})" + + def mkdir(self, path): + lpath = local_path(path) + actions.run_and_verify_svn(None, None, [], 'mkdir', lpath) + + self.state.add({ + path : wc.StateItem(status='A') + }) + +# def propset(self, pname, pvalue, *paths): +# "Set property 'pname' to value 'pvalue' on each path in 'paths'" +# local_paths = tuple([local_path(path) for path in paths]) +# actions.run_and_verify_svn(None, None, [], 'propset', pname, pvalue, +# *local_paths) + + def set_props(self, path, props): + """Change the properties of PATH to be the dictionary {name -> value} PROPS. + """ + lpath = local_path(path) + #for prop in path's existing props: + # actions.run_and_verify_svn(None, None, [], 'propdel', + # prop, lpath) + for prop in props: + actions.run_and_verify_svn(None, None, [], 'propset', + prop, props[prop], lpath) + self.state.tweak(path, props=props) + + def file_create(self, path, content=None, props=None): + "Make and add a file with some default content, and keyword expansion." + lpath = local_path(path) + ldirname, filename = os.path.split(lpath) + if content is None: + # Default content + content = "This is the file '" + filename + "'.\n" + \ + "Last changed in '$Revision$'.\n" + main.file_write(lpath, content) + actions.run_and_verify_svn(None, None, [], 'add', lpath) + + self.state.add({ + path : wc.StateItem(status='A') + }) + if props is None: + # Default props + props = { + 'svn:keywords': 'Revision' + } + self.set_props(path, props) + + def file_modify(self, path, content=None, props=None): + "Make text and property mods to a WC file." + lpath = local_path(path) + if content is not None: + #main.file_append(lpath, "An extra line.\n") + #actions.run_and_verify_svn(None, None, [], 'propset', + # 'newprop', 'v', lpath) + main.file_write(lpath, content) + self.state.tweak(path, content=content) + if props is not None: + self.set_props(path, props) + self.state.tweak(path, props=props) + +# def copy(self, s_rev, path1, path2): +# "Copy a WC path locally." +# lpath1 = local_path(path1) +# lpath2 = local_path(path2) +# actions.run_and_verify_svn(None, None, [], 'copy', '--parents', +# '-r', s_rev, lpath1, lpath2) + +# def delete(self, path): +# "Delete a WC path locally." +# lpath = local_path(path) +# actions.run_and_verify_svn(None, None, [], 'delete', lpath) + + def commit(self, path='.'): + "Commit a WC path (recursively). Return the new revision number." + lpath = local_path(path) + actions.run_and_verify_svn(None, verify.AnyOutput, [], + 'commit', '-m', '', lpath) + self.repo.head_rev += 1 + return self.repo.head_rev + +# def merge(self, rev_spec, source, target, exp_out=None): +# """Merge a single change from path 'source' to path 'target'. +# SRC_CHANGE_NUM is either a number (to cherry-pick that specific change) +# or a command-line option revision range string such as '-r10:20'.""" +# lsource = local_path(source) +# ltarget = local_path(target) +# if isinstance(rev_spec, int): +# rev_spec = '-c' + str(rev_spec) +# if exp_out is None: +# target_re = re.escape(target) +# exp_1 = "--- Merging r.* into '" + target_re + ".*':" +# exp_2 = "(A |D |[UG] | [UG]|[UG][UG]) " + target_re + ".*" +# exp_out = verify.RegexOutput(exp_1 + "|" + exp_2) +# actions.run_and_verify_svn(None, exp_out, [], +# 'merge', rev_spec, lsource, ltarget) + Property changes on: subversion/tests/cmdline/svntest/objects.py ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/x-python