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