On the explore-wc branch:
Bring up to date with trunk.
Resolved conflicts in entries.c with an #ifdef FROM_TRUNK around the code
sitting on trunk, which we aren't using in this branch (calls to the old
functions to read/write 'entries').
git-svn-id: https://svn.apache.org/repos/asf/subversion/branches/explore-wc@875995 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/BRANCH-README b/BRANCH-README
new file mode 100644
index 0000000..7f08ace
--- /dev/null
+++ b/BRANCH-README
@@ -0,0 +1,6 @@
+This branch exists as a place to stash patches and get review on WC-NG work
+prior to the 1.6.x branch. Shortly after that branch is created, this branch
+will be merged back to trunk and disappear.
+
+For more information about WC-NG, including a proposed implementation plan,
+see: http://svn.collab.net/repos/svn/trunk/notes/wc-ng-design
diff --git a/Makefile.in b/Makefile.in
index db3ed9d..42a8829 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -329,7 +329,9 @@
(cd $$i && rm -f *.o *.lo *.la *.la-a *.spo *.mo && \
rm -rf .libs) ; \
done
- rm -f $(CLEAN_FILES) $(abs_srcdir)/subversion/libsvn_fs_fs/rep-cache-db.h
+ rm -f $(CLEAN_FILES) \
+ $(abs_srcdir)/subversion/libsvn_fs_fs/rep-cache-db.h \
+ $(abs_srcdir)/subversion/libsvn_wc/wc-metadata.h
find $(abs_srcdir) -name "*.pyc" -exec rm {} ';'
# clean all but bulky test output, returning to before './configure' was run.
diff --git a/build.conf b/build.conf
index d938595..7da6cc3 100644
--- a/build.conf
+++ b/build.conf
@@ -34,6 +34,7 @@
private-built-includes =
subversion/svn_private_config.h
subversion/libsvn_fs_fs/rep-cache-db.h
+ subversion/libsvn_wc/wc-metadata.h
subversion/bindings/swig/proxy/swig_python_external_runtime.swg
subversion/bindings/swig/proxy/swig_perl_external_runtime.swg
subversion/bindings/swig/proxy/swig_ruby_external_runtime.swg
diff --git a/build/generator/gen_win.py b/build/generator/gen_win.py
index 206a519..b42246e 100644
--- a/build/generator/gen_win.py
+++ b/build/generator/gen_win.py
@@ -1387,6 +1387,7 @@
import transform_sql
sql_sources = [
os.path.join('subversion', 'libsvn_fs_fs', 'rep-cache-db'),
+ os.path.join('subversion', 'libsvn_wc', 'wc-metadata'),
]
for sql in sql_sources:
transform_sql.main(open(sql + '.sql', 'r'),
diff --git a/subversion/include/svn_error_codes.h b/subversion/include/svn_error_codes.h
index a4aa17c..7bdbb71 100644
--- a/subversion/include/svn_error_codes.h
+++ b/subversion/include/svn_error_codes.h
@@ -458,6 +458,16 @@
SVN_ERR_WC_CATEGORY_START + 31,
"Cannot move a file external")
+ /** @since New in 1.7. */
+ SVN_ERRDEF(SVN_ERR_WC_DB_ERROR,
+ SVN_ERR_WC_CATEGORY_START + 32,
+ "Something's amiss with the wc sqlite database")
+
+ /** @since New in 1.7. */
+ SVN_ERRDEF(SVN_ERR_WC_MISSING,
+ SVN_ERR_WC_CATEGORY_START + 33,
+ "The working copy is missing")
+
/* fs errors */
SVN_ERRDEF(SVN_ERR_FS_GENERAL,
diff --git a/subversion/libsvn_wc/README b/subversion/libsvn_wc/README
index a7c6de0..abdd9d0 100644
--- a/subversion/libsvn_wc/README
+++ b/subversion/libsvn_wc/README
@@ -107,6 +107,8 @@
props/ /* tmp area for props */
empty-file /* Obsolete, no longer used, not present in
post-1.3 working copies */
+ wc.db /* A SQLite database which contains *all* the
+ administrative information for a wc-ng. */
`format':
Says what version of the working copy adm format this is (so future
diff --git a/subversion/libsvn_wc/adm_ops.c b/subversion/libsvn_wc/adm_ops.c
index 0875359..d9280d0 100644
--- a/subversion/libsvn_wc/adm_ops.c
+++ b/subversion/libsvn_wc/adm_ops.c
@@ -182,7 +182,9 @@
if (current_entry->schedule != svn_wc_schedule_add
&& !excluded)
{
- svn_wc__entry_remove(entries, name);
+ SVN_ERR(svn_wc__entry_remove(
+ entries, svn_wc_adm_access_path(dirpath),
+ name, subpool));
if (notify_func)
{
notify = svn_wc_create_notify(child_path,
@@ -1222,7 +1224,9 @@
with! this means we're dealing with a missing item
that's scheduled for addition. Easiest to just
remove the entry. */
- svn_wc__entry_remove(entries, base_name);
+ SVN_ERR(svn_wc__entry_remove(
+ entries, svn_wc_adm_access_path(parent_access),
+ base_name, pool));
SVN_ERR(svn_wc__entries_write(entries, parent_access, pool));
}
}
@@ -2068,7 +2072,9 @@
adm_access, for which we definitely can't use the 'else'
code path (as it will remove the parent from version
control... (See issue 2425) */
- svn_wc__entry_remove(entries, basey);
+ SVN_ERR(svn_wc__entry_remove(
+ entries, svn_wc_adm_access_path(parent_access),
+ basey, pool));
SVN_ERR(svn_wc__entries_write(entries, parent_access, pool));
}
else
@@ -2461,7 +2467,8 @@
/* Remove NAME from PATH's entries file: */
SVN_ERR(svn_wc_entries_read(&entries, adm_access, TRUE, pool));
- svn_wc__entry_remove(entries, name);
+ SVN_ERR(svn_wc__entry_remove(entries, svn_wc_adm_access_path(adm_access),
+ name, pool));
SVN_ERR(svn_wc__entries_write(entries, adm_access, pool));
/* Remove text-base/NAME.svn-base */
@@ -2561,7 +2568,9 @@
/* The directory is either missing or excluded,
so don't try to recurse, just delete the
entry in the parent directory. */
- svn_wc__entry_remove(entries, current_entry_name);
+ SVN_ERR(svn_wc__entry_remove(
+ entries, svn_wc_adm_access_path(adm_access),
+ current_entry_name, subpool));
}
else
{
@@ -2625,7 +2634,9 @@
APR_HASH_KEY_STRING);
if (dir_entry->depth != svn_depth_exclude)
{
- svn_wc__entry_remove(parent_entries, base_name);
+ SVN_ERR(svn_wc__entry_remove(
+ parent_entries, svn_wc_adm_access_path(parent_access),
+ base_name, pool));
SVN_ERR(svn_wc__entries_write(parent_entries, parent_access, pool));
}
diff --git a/subversion/libsvn_wc/crop.c b/subversion/libsvn_wc/crop.c
index a26cb6c..f46f758 100644
--- a/subversion/libsvn_wc/crop.c
+++ b/subversion/libsvn_wc/crop.c
@@ -129,7 +129,9 @@
logically not exist. */
if (depth < svn_depth_immediates)
{
- svn_wc__entry_remove(entries, current_entry->name);
+ SVN_ERR(svn_wc__entry_remove(
+ entries, svn_wc_adm_access_path(dir_access),
+ current_entry->name, iterpool));
SVN_ERR(svn_wc__entries_write(entries, dir_access, iterpool));
}
continue;
diff --git a/subversion/libsvn_wc/entries.c b/subversion/libsvn_wc/entries.c
index fc4e66b..2237c94 100644
--- a/subversion/libsvn_wc/entries.c
+++ b/subversion/libsvn_wc/entries.c
@@ -27,6 +27,7 @@
#include "svn_pools.h"
#include "svn_path.h"
#include "svn_ctype.h"
+#include "svn_string.h"
#include "wc.h"
#include "adm_files.h"
@@ -37,6 +38,173 @@
#include "svn_private_config.h"
#include "private/svn_wc_private.h"
+#include "private/svn_sqlite.h"
+#include "private/svn_skel.h"
+
+#include "wc-metadata.h"
+
+#define MAYBE_ALLOC(x,p) ((x) ? (x) : apr_pcalloc((p), sizeof(*(x))))
+
+static const char * const upgrade_sql[] = { NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, WC_METADATA_SQL };
+
+/* This values map to the members of STATEMENTS below, and should be added
+ and removed at the same time. */
+enum statement_keys {
+ STMT_INSERT_REPOSITORY,
+ STMT_INSERT_WCROOT,
+ STMT_INSERT_BASE_NODE,
+ STMT_INSERT_WORKING_NODE,
+ STMT_INSERT_ACTUAL_NODE,
+ STMT_INSERT_CHANGELIST,
+ STMT_SELECT_REPOSITORY,
+ STMT_SELECT_WCROOT_NULL,
+ STMT_SELECT_REPOSITORY_BY_ID,
+ STMT_SELECT_BASE_NODE,
+ STMT_SELECT_WORKING_NODE,
+ STMT_SELECT_ACTUAL_NODE,
+ STMT_DELETE_BASE_NODE,
+ STMT_DELETE_WORKING_NODE,
+ STMT_DELETE_ACTUAL_NODE,
+ STMT_SELECT_BASE_NODE_BY_RELPATH
+};
+
+static const char * const statements[] = {
+ "insert into repository (root, uuid) "
+ "values (?1, ?2);",
+
+ "insert into wcroot (local_abspath) "
+ "values (?1);",
+
+ "insert or replace into base_node "
+ "(wc_id, local_relpath, repos_id, repos_relpath, parent_id, revnum, "
+ "kind, checksum, translated_size, changed_rev, changed_date, "
+ "changed_author, depth, last_mod_time, properties, incomplete_children)"
+ "values (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, "
+ "?15, ?16);",
+
+ "insert or replace into working_node "
+ "(wc_id, local_relpath, parent_relpath, kind, copyfrom_repos_id, "
+ "copyfrom_repos_path, copyfrom_revnum, moved_from, moved_to, checksum, "
+ "translated_size, changed_rev, changed_date, changed_author, depth, "
+ "last_mod_time, properties, changelist_id, tree_conflict_data) "
+ "values (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, "
+ "?15, ?16, ?17, ?18, ?19);",
+
+ "insert or replace into actual_node "
+ "(wc_id, local_relpath, properties, conflict_old, conflict_new, "
+ "conflict_working, prop_reject, changelist_id) "
+ "values (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8);",
+
+ "insert or replace into changelist "
+ "(wc_id, name) "
+ "values (?1, ?2);",
+
+ "select id, root from repository where uuid = ?1;",
+
+ "select id from wcroot where local_abspath is null;",
+
+ "select root, uuid from repository where id = ?1;",
+
+ "select id, wc_id, local_relpath, repos_id, repos_relpath, parent_id, "
+ "revnum, kind, checksum, translated_size, changed_rev, changed_date, "
+ "changed_author, depth, last_mod_time, properties, incomplete_children "
+ "from base_node;",
+
+ "select id, wc_id, local_relpath, parent_relpath, kind, copyfrom_repos_id, "
+ "copyfrom_repos_path, copyfrom_revnum, moved_from, moved_to, checksum, "
+ "translated_size, changed_rev, changed_date, changed_author, depth, "
+ "last_mod_time, properties, changelist_id, tree_conflict_data "
+ "from working_node;",
+
+ "select id, wc_id, local_relpath, properties, conflict_old, conflict_new, "
+ "conflict_working, prop_reject, changelist_id "
+ "from actual_node;",
+
+ "delete from base_node where wc_id = ?1 and local_relpath = ?2;",
+
+ "delete from working_node where wc_id = ?1 and local_relpath = ?2;",
+
+ "delete from actual_node where wc_id = ?1 and local_relpath = ?2;",
+
+ "select repos_relpath, root, uuid "
+ "from base_node, repository "
+ "where local_relpath = ?1 and repository.id = base_node.repos_id;",
+
+ NULL
+ };
+
+/* Temporary structures which mirror the tables in wc-metadata.sql.
+ For detailed descriptions of each field, see that file. */
+typedef struct {
+ apr_int64_t id;
+ apr_int64_t wc_id;
+ const char *local_relpath;
+ apr_int64_t repos_id;
+ const char *repos_relpath;
+ apr_int64_t parent_id;
+ svn_revnum_t revision;
+ svn_node_kind_t kind;
+ svn_checksum_t *checksum;
+ apr_size_t translated_size;
+ svn_revnum_t changed_rev;
+ apr_time_t changed_date;
+ const char *changed_author;
+ svn_depth_t depth;
+ apr_time_t last_mod_time;
+ apr_hash_t *properties;
+ svn_boolean_t incomplete_children;
+} db_base_node_t;
+
+typedef struct {
+ apr_int64_t id;
+ apr_int64_t wc_id;
+ const char *local_relpath;
+ const char *parent_relpath;
+ svn_node_kind_t kind;
+ apr_int64_t copyfrom_repos_id;
+ const char *copyfrom_repos_path;
+ svn_revnum_t copyfrom_revnum;
+ const char *moved_from;
+ const char *moved_to;
+ svn_checksum_t *checksum;
+ apr_size_t translated_size;
+ svn_revnum_t changed_rev;
+ apr_time_t changed_date;
+ const char *changed_author;
+ svn_depth_t depth;
+ apr_time_t last_mod_time;
+ apr_hash_t *properties;
+ apr_int64_t changelist_id;
+ const char *tree_conflict_data;
+} db_working_node_t;
+
+typedef struct {
+ apr_int64_t id;
+ apr_int64_t wc_id;
+ const char *local_relpath;
+ apr_hash_t *properties;
+ const char *conflict_old;
+ const char *conflict_new;
+ const char *conflict_working;
+ const char *prop_reject;
+ apr_int64_t changelist_id;
+} db_actual_node_t;
+
+typedef struct {
+ apr_int64_t id;
+ apr_int64_t wc_id;
+ const char *name;
+} db_changelist_t;
+
+typedef struct {
+ const char *url;
+ const char *lock_token;
+ const char *lock_owner;
+ const char *lock_comment;
+ apr_time_t lock_date;
+} db_lock_t;
+
/** Overview **/
@@ -52,6 +220,18 @@
/*--------------------------------------------------------------- */
+/*** Working with the entries sqlite database ***/
+
+/* Return the location of the sqlite database containing the entry information
+ for PATH in the filesystem. Allocate in RESULT_POOL. ***/
+static const char *
+db_path(const char *path,
+ apr_pool_t *result_pool)
+{
+ return svn_wc__adm_child(path, "wc.db", result_pool);
+}
+
+
/*** reading and writing the entries file ***/
@@ -102,7 +282,6 @@
}
-
svn_error_t *
svn_wc__atts_to_entry(svn_wc_entry_t **new_entry,
apr_uint64_t *modify_flags,
@@ -500,15 +679,488 @@
}
+/* Select all the rows from base_node table in WC_DB and put them into *NODES,
+ allocated in RESULT_POOL. */
+static svn_error_t *
+fetch_base_nodes(apr_hash_t **nodes,
+ svn_sqlite__db_t *wc_db,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ *nodes = apr_hash_make(result_pool);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wc_db, STMT_SELECT_BASE_NODE));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while (have_row)
+ {
+ apr_size_t len;
+ const void *val;
+ db_base_node_t *base_node = apr_pcalloc(result_pool,
+ sizeof(*base_node));
+
+ base_node->wc_id = svn_sqlite__column_int(stmt, 1);
+ base_node->local_relpath = svn_sqlite__column_text(stmt, 2, result_pool);
+
+ if (!svn_sqlite__column_is_null(stmt, 3))
+ {
+ base_node->repos_id = svn_sqlite__column_int(stmt, 3);
+ base_node->repos_relpath = svn_sqlite__column_text(stmt, 4,
+ result_pool);
+ }
+
+ if (!svn_sqlite__column_is_null(stmt, 5))
+ base_node->parent_id = svn_sqlite__column_int(stmt, 5);
+
+ base_node->revision = svn_sqlite__column_int(stmt, 6);
+ base_node->kind = svn_node_kind_from_word(
+ svn_sqlite__column_text(stmt, 7, NULL));
+
+ if (!svn_sqlite__column_is_null(stmt, 8))
+ {
+ const char *digest = svn_sqlite__column_text(stmt, 8, NULL);
+ svn_checksum_kind_t kind = (digest[1] == 'm'
+ ? svn_checksum_md5 : svn_checksum_sha1);
+ SVN_ERR(svn_checksum_parse_hex(&base_node->checksum, kind,
+ digest + 6, result_pool));
+ }
+
+ base_node->translated_size = svn_sqlite__column_int(stmt, 9);
+
+ base_node->changed_rev = svn_sqlite__column_int(stmt, 10);
+ base_node->changed_date = svn_sqlite__column_int(stmt, 11);
+ base_node->changed_author = svn_sqlite__column_text(stmt, 12,
+ result_pool);
+
+ base_node->depth = svn_depth_from_word(
+ svn_sqlite__column_text(stmt, 13, NULL));
+ base_node->last_mod_time = svn_sqlite__column_int(stmt, 14);
+
+ val = svn_sqlite__column_blob(stmt, 15, &len);
+ SVN_ERR(svn_skel__parse_proplist(&base_node->properties,
+ svn_skel__parse(val, len, scratch_pool),
+ result_pool));
+
+ base_node->incomplete_children = svn_sqlite__column_boolean(stmt, 16);
+
+ apr_hash_set(*nodes, base_node->local_relpath, APR_HASH_KEY_STRING,
+ base_node);
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Select all the rows from working_node table in WC_DB and put them into
+ *NODES allocated in RESULT_POOL. */
+static svn_error_t *
+fetch_working_nodes(apr_hash_t **nodes,
+ svn_sqlite__db_t *wc_db,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ *nodes = apr_hash_make(result_pool);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wc_db, STMT_SELECT_WORKING_NODE));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while (have_row)
+ {
+ apr_size_t len;
+ const void *val;
+ db_working_node_t *working_node = apr_pcalloc(result_pool,
+ sizeof(*working_node));
+
+ working_node->wc_id = svn_sqlite__column_int(stmt, 1);
+ working_node->local_relpath = svn_sqlite__column_text(stmt, 2,
+ result_pool);
+ working_node->parent_relpath = svn_sqlite__column_text(stmt, 3,
+ result_pool);
+ working_node->kind = svn_node_kind_from_word(
+ svn_sqlite__column_text(stmt, 4, NULL));
+
+ if (!svn_sqlite__column_is_null(stmt, 5))
+ {
+ working_node->copyfrom_repos_id = svn_sqlite__column_int(stmt, 5);
+ working_node->copyfrom_repos_path = svn_sqlite__column_text(stmt, 6,
+ result_pool);
+ working_node->copyfrom_revnum = svn_sqlite__column_revnum(stmt, 7);
+ }
+
+ if (!svn_sqlite__column_is_null(stmt, 8))
+ working_node->moved_from = svn_sqlite__column_text(stmt, 8,
+ result_pool);
+
+ if (!svn_sqlite__column_is_null(stmt, 9))
+ working_node->moved_to = svn_sqlite__column_text(stmt, 9, result_pool);
+
+ if (!svn_sqlite__column_is_null(stmt, 10))
+ {
+ const char *digest = svn_sqlite__column_text(stmt, 10, NULL);
+ svn_checksum_kind_t kind = (digest[1] == 'm'
+ ? svn_checksum_md5 : svn_checksum_sha1);
+ SVN_ERR(svn_checksum_parse_hex(&working_node->checksum, kind,
+ digest + 6, result_pool));
+ working_node->translated_size = svn_sqlite__column_int(stmt, 11);
+ }
+
+ if (!svn_sqlite__column_is_null(stmt, 12))
+ {
+ working_node->changed_rev = svn_sqlite__column_revnum(stmt, 12);
+ working_node->changed_date = svn_sqlite__column_int(stmt, 13);
+ working_node->changed_author = svn_sqlite__column_text(stmt, 14,
+ result_pool);
+ }
+
+ working_node->depth = svn_depth_from_word(
+ svn_sqlite__column_text(stmt, 15, NULL));
+ working_node->last_mod_time = svn_sqlite__column_int(stmt, 16);
+
+ val = svn_sqlite__column_blob(stmt, 17, &len);
+ SVN_ERR(svn_skel__parse_proplist(&working_node->properties,
+ svn_skel__parse(val, len, scratch_pool),
+ result_pool));
+
+ if (!svn_sqlite__column_is_null(stmt, 18))
+ working_node->changelist_id = svn_sqlite__column_int(stmt, 18);
+
+ if (!svn_sqlite__column_is_null(stmt, 19))
+ working_node->tree_conflict_data = svn_sqlite__column_text(stmt, 19,
+ result_pool);
+
+ apr_hash_set(*nodes, working_node->local_relpath, APR_HASH_KEY_STRING,
+ working_node);
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Select all the rows from actual_node table in WC_DB and put them into
+ *NODES allocated in RESULT_POOL. */
+static svn_error_t *
+fetch_actual_nodes(apr_hash_t **nodes,
+ svn_sqlite__db_t *wc_db,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ *nodes = apr_hash_make(result_pool);
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wc_db, STMT_SELECT_ACTUAL_NODE));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while (have_row)
+ {
+ db_actual_node_t *actual_node = apr_pcalloc(result_pool,
+ sizeof(*actual_node));
+
+ actual_node->wc_id = svn_sqlite__column_int(stmt, 1);
+ actual_node->local_relpath = svn_sqlite__column_text(stmt, 2,
+ result_pool);
+
+ if (!svn_sqlite__column_is_null(stmt, 3))
+ {
+ apr_size_t len;
+ const void *val;
+
+ val = svn_sqlite__column_blob(stmt, 3, &len);
+ SVN_ERR(svn_skel__parse_proplist(&actual_node->properties,
+ svn_skel__parse(val, len,
+ scratch_pool),
+ result_pool));
+ }
+
+ if (!svn_sqlite__column_is_null(stmt, 4))
+ {
+ actual_node->conflict_old = svn_sqlite__column_text(stmt, 4,
+ result_pool);
+ actual_node->conflict_new = svn_sqlite__column_text(stmt, 5,
+ result_pool);
+ actual_node->conflict_working = svn_sqlite__column_text(stmt, 6,
+ result_pool);
+ }
+
+ if (!svn_sqlite__column_is_null(stmt, 7))
+ actual_node->prop_reject = svn_sqlite__column_text(stmt, 7,
+ result_pool);
+
+ if (!svn_sqlite__column_is_null(stmt, 8))
+ actual_node->changelist_id = svn_sqlite__column_int(stmt, 8);
+
+ apr_hash_set(*nodes, actual_node->local_relpath, APR_HASH_KEY_STRING,
+ actual_node);
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+fetch_wc_id(apr_int64_t *wc_id, svn_sqlite__db_t *wc_db)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wc_db, STMT_SELECT_WCROOT_NULL));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (!have_row)
+ return svn_error_create(SVN_ERR_WC_DB_ERROR, NULL, _("No WC table entry"));
+ *wc_id = svn_sqlite__column_int(stmt, 0);
+ return svn_sqlite__reset(stmt);
+}
+
+static svn_error_t *
+get_repos_info(const char **repos_root,
+ const char **repos_uuid,
+ svn_sqlite__db_t *wc_db,
+ apr_int64_t repos_id,
+ apr_pool_t *result_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wc_db,
+ STMT_SELECT_REPOSITORY_BY_ID));
+ SVN_ERR(svn_sqlite__bindf(stmt, "i", repos_id));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (!have_row)
+ return svn_error_createf(SVN_ERR_WC_DB_ERROR, NULL,
+ _("No REPOSITORY table entry for id '%ld'"),
+ (long int)repos_id);
+
+ *repos_root = svn_sqlite__column_text(stmt, 0, result_pool);
+ *repos_uuid = svn_sqlite__column_text(stmt, 1, result_pool);
+
+ return svn_sqlite__reset(stmt);
+}
+
+/* This function exists for one purpose: to find the expected future url of
+ an entry which is schedule-add. In a centralized metadata storage
+ situation, this is pretty easy, but in the current one-db-per-.svn scenario,
+ we need to jump through some hoops, so here it is. */
+static svn_error_t *
+find_working_add_entry_url_stuffs(const char *adm_access_path,
+ svn_wc_entry_t *entry,
+ const char *relative_path,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *wc_db_path = db_path(adm_access_path, scratch_pool);
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ svn_sqlite__db_t *wc_db;
+
+ /* Open parent database. */
+ /*fprintf(stderr, "%d: access path: '%s'; relative path: '%s'; db path: '%s'\n",
+ __LINE__, adm_access_path, relative_path, wc_db_path);*/
+ SVN_ERR(svn_sqlite__open(&wc_db, wc_db_path,
+ svn_sqlite__mode_readwrite, statements,
+ SVN_WC__VERSION, upgrade_sql, scratch_pool,
+ scratch_pool));
+
+ /* Check to see if a base_node exists for the directory. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wc_db,
+ STMT_SELECT_BASE_NODE_BY_RELPATH));
+ SVN_ERR(svn_sqlite__bindf(stmt, "s", SVN_WC_ENTRY_THIS_DIR));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ /* If so, cat the url with the existing relative path, put that in
+ entry->url and return. */
+ if (have_row)
+ {
+ const char *base = svn_sqlite__column_text(stmt, 0, NULL);
+
+ entry->repos = svn_sqlite__column_text(stmt, 1, result_pool);
+ entry->uuid = svn_sqlite__column_text(stmt, 2, result_pool);
+ entry->url = svn_path_join_many(result_pool, entry->repos, base,
+ relative_path, NULL);
+ return svn_sqlite__reset(stmt);
+ }
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ /* If not, move a path segement from adm_access_path to relative_path and
+ recurse. */
+ return find_working_add_entry_url_stuffs(
+ svn_path_dirname(adm_access_path, scratch_pool),
+ entry,
+ svn_path_join(svn_path_basename(adm_access_path,
+ scratch_pool),
+ relative_path, scratch_pool),
+ result_pool, scratch_pool);
+}
/* Fill the entries cache in ADM_ACCESS. The full hash cache will be
- populated. POOL is used for local memory allocation, the access baton
- pool is used for the cache. */
+ populated. SCRATCH_POOL is used for local memory allocation, the access
+ baton pool is used for the cache. */
static svn_error_t *
read_entries(svn_wc_adm_access_t *adm_access,
apr_pool_t *scratch_pool)
{
+ apr_hash_t *base_nodes;
+ apr_hash_t *working_nodes;
+ apr_hash_t *actual_nodes;
+ svn_sqlite__db_t *wc_db;
+ apr_hash_index_t *hi;
+ const char *repos_root = NULL;
+ const char *repos_uuid = NULL;
+ apr_pool_t *result_pool = svn_wc_adm_access_pool(adm_access);
+ apr_hash_t *entries = apr_hash_make(result_pool);
+ const char *wc_db_path = db_path(svn_wc_adm_access_path(adm_access),
+ scratch_pool);
+
+ /* Open the wc.db sqlite database. */
+ SVN_ERR(svn_sqlite__open(&wc_db, wc_db_path, svn_sqlite__mode_readwrite,
+ statements, SVN_WC__VERSION, upgrade_sql,
+ scratch_pool, scratch_pool));
+
+ /* The basic strategy here is to get all the node information from the
+ database for the directory in question and convert that to
+ svn_wc_entry_t structs. To do that, we fetch each of the nodes from
+ the three node tables into a hash, then iterate over them, linking them
+ together as required.
+
+ TODO: A smarter way would be to craft a query using the correct type of
+ outer join so that we can get all the nodes in one fell swoop. However,
+ that takes more thought and effort than I'm willing to invest right now.
+ We can put it on the stack of future optimizations. */
+
+ SVN_ERR(fetch_base_nodes(&base_nodes, wc_db, scratch_pool, scratch_pool));
+ SVN_ERR(fetch_working_nodes(&working_nodes, wc_db, scratch_pool,
+ scratch_pool));
+ SVN_ERR(fetch_actual_nodes(&actual_nodes, wc_db, scratch_pool, scratch_pool));
+
+ for (hi = apr_hash_first(scratch_pool, base_nodes); hi;
+ hi = apr_hash_next(hi))
+ {
+ const db_base_node_t *base_node;
+ const db_working_node_t *working_node;
+ const db_actual_node_t *actual_node;
+ const char *rel_path;
+ svn_wc_entry_t *entry = alloc_entry(result_pool);
+
+ apr_hash_this(hi, (const void **) &rel_path, NULL, (void **) &base_node);
+
+ /* Get any corresponding working and actual nodes, removing them from
+ their respective hashs to indicate we've seen them. */
+ working_node = apr_hash_get(working_nodes, rel_path, APR_HASH_KEY_STRING);
+ apr_hash_set(working_nodes, rel_path, APR_HASH_KEY_STRING, NULL);
+ actual_node = apr_hash_get(actual_nodes, rel_path, APR_HASH_KEY_STRING);
+ apr_hash_set(actual_nodes, rel_path, APR_HASH_KEY_STRING, NULL);
+
+ entry->name = apr_pstrdup(result_pool, base_node->local_relpath);
+
+ if (working_node)
+ {
+ if (working_node->kind == svn_node_none)
+ entry->schedule = svn_wc_schedule_delete;
+ else
+ entry->schedule = svn_wc_schedule_replace;
+ }
+ else
+ {
+ entry->schedule = svn_wc_schedule_normal;
+ }
+
+ if (base_node->repos_id)
+ {
+ if (repos_root == NULL)
+ SVN_ERR(get_repos_info(&repos_root, &repos_uuid, wc_db,
+ base_node->repos_id, result_pool));
+
+ entry->uuid = repos_uuid;
+ entry->url = svn_path_join(repos_root, base_node->repos_relpath,
+ result_pool);
+ entry->repos = repos_root;
+ }
+
+ if (working_node && (working_node->copyfrom_repos_path != NULL))
+ entry->copied = TRUE;
+
+ if (working_node && (working_node->tree_conflict_data != NULL))
+ entry->tree_conflict_data = apr_pstrdup(result_pool,
+ working_node->tree_conflict_data);
+
+
+ if (base_node->checksum)
+ entry->checksum = svn_checksum_to_cstring(base_node->checksum,
+ result_pool);
+
+ if (actual_node && (actual_node->conflict_old != NULL))
+ {
+ entry->conflict_old = apr_pstrdup(result_pool,
+ actual_node->conflict_old);
+ entry->conflict_new = apr_pstrdup(result_pool,
+ actual_node->conflict_new);
+ entry->conflict_wrk = apr_pstrdup(result_pool,
+ actual_node->conflict_working);
+ }
+
+ if (actual_node && (actual_node->prop_reject != NULL))
+ entry->prejfile = apr_pstrdup(result_pool, actual_node->prop_reject);
+
+ entry->depth = base_node->depth;
+ entry->revision = base_node->revision;
+ entry->kind = base_node->kind;
+
+ entry->incomplete = base_node->incomplete_children;
+
+ apr_hash_set(entries, entry->name, APR_HASH_KEY_STRING, entry);
+ }
+
+ /* Loop over any additional working nodes. */
+ for (hi = apr_hash_first(scratch_pool, working_nodes); hi;
+ hi = apr_hash_next(hi))
+ {
+ const db_working_node_t *working_node;
+ const char *rel_path;
+ svn_wc_entry_t *entry = alloc_entry(result_pool);
+
+ apr_hash_this(hi, (const void **) &rel_path, NULL,
+ (void **) &working_node);
+ entry->name = apr_pstrdup(result_pool, working_node->local_relpath);
+
+ /* This node is in WORKING, but not in BASE, so it must be an add. */
+ entry->schedule = svn_wc_schedule_add;
+
+ if (working_node->copyfrom_repos_path != NULL)
+ entry->copied = TRUE;
+
+ if (working_node->checksum)
+ entry->checksum = svn_checksum_to_cstring(working_node->checksum,
+ result_pool);
+
+ SVN_ERR(find_working_add_entry_url_stuffs(
+ entry->name[0] == 0
+ ? svn_path_dirname(svn_wc_adm_access_path(
+ adm_access), scratch_pool)
+ : svn_wc_adm_access_path(adm_access),
+ entry,
+ entry->name[0] == 0
+ ? svn_path_basename(svn_wc_adm_access_path(
+ adm_access), scratch_pool)
+ : entry->name,
+ result_pool, scratch_pool));
+ entry->kind = working_node->kind;
+ entry->revision = 0;
+
+ apr_hash_set(entries, entry->name, APR_HASH_KEY_STRING, entry);
+ }
+
+ /* Fill in any implied fields. */
+ SVN_ERR(resolve_to_defaults(entries, result_pool));
+ svn_wc__adm_access_set_entries(adm_access, TRUE, entries);
+
+ return SVN_NO_ERROR;
+
+#ifdef FROM_TRUNK
return svn_wc__read_entries_old(adm_access, scratch_pool);
+#endif
}
/* For non-directory PATHs full entry information is obtained by reading
@@ -609,12 +1261,513 @@
return SVN_NO_ERROR;
}
+static svn_error_t *
+insert_base_node(svn_sqlite__db_t *wc_db,
+ const db_base_node_t *base_node,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ const svn_stringbuf_t *properties;
+ svn_skel_t *skel;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wc_db, STMT_INSERT_BASE_NODE));
+
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 1, base_node->wc_id));
+ SVN_ERR(svn_sqlite__bind_text(stmt, 2, base_node->local_relpath));
+
+ if (base_node->repos_id)
+ {
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 3, base_node->repos_id));
+ SVN_ERR(svn_sqlite__bind_text(stmt, 4, base_node->repos_relpath));
+ }
+
+ if (base_node->parent_id)
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 5, base_node->parent_id));
+
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 6, base_node->revision));
+ SVN_ERR(svn_sqlite__bind_text(stmt, 7,
+ svn_node_kind_to_word(base_node->kind)));
+
+ if (base_node->checksum)
+ {
+ const char *kind_str = (base_node->checksum->kind == svn_checksum_md5
+ ? "$md5 $" : "$sha1$");
+ SVN_ERR(svn_sqlite__bind_text(stmt, 8, apr_pstrcat(scratch_pool,
+ kind_str, svn_checksum_to_cstring(base_node->checksum,
+ scratch_pool), NULL)));
+ }
+
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 9, base_node->translated_size));
+
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 10, base_node->changed_rev));
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 11, base_node->changed_date));
+ SVN_ERR(svn_sqlite__bind_text(stmt, 12, base_node->changed_author));
+
+ SVN_ERR(svn_sqlite__bind_text(stmt, 13, svn_depth_to_word(base_node->depth)));
+
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 14, base_node->last_mod_time));
+
+ if (base_node->properties)
+ SVN_ERR(svn_skel__unparse_proplist(&skel, base_node->properties,
+ scratch_pool));
+ else
+ skel = svn_skel__make_empty_list(scratch_pool);
+
+ properties = svn_skel__unparse(skel, scratch_pool);
+ SVN_ERR(svn_sqlite__bind_blob(stmt, 15, properties->data, properties->len));
+
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 16, base_node->incomplete_children));
+
+ /* Execute and reset the insert clause. */
+ return svn_sqlite__insert(NULL, stmt);
+}
+
+static svn_error_t *
+insert_working_node(svn_sqlite__db_t *wc_db,
+ const db_working_node_t *working_node,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_stringbuf_t *properties;
+ svn_skel_t *skel;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wc_db, STMT_INSERT_WORKING_NODE));
+
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 1, working_node->wc_id));
+ SVN_ERR(svn_sqlite__bind_text(stmt, 2, working_node->local_relpath));
+ SVN_ERR(svn_sqlite__bind_text(stmt, 3, working_node->parent_relpath));
+ SVN_ERR(svn_sqlite__bind_text(stmt, 4,
+ svn_node_kind_to_word(working_node->kind)));
+
+ if (working_node->copyfrom_repos_id > 0)
+ {
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 5,
+ working_node->copyfrom_repos_id));
+ SVN_ERR(svn_sqlite__bind_text(stmt, 6,
+ working_node->copyfrom_repos_path));
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 7, working_node->copyfrom_revnum));
+ }
+
+ if (working_node->moved_from)
+ SVN_ERR(svn_sqlite__bind_text(stmt, 8, working_node->moved_from));
+
+ if (working_node->moved_to)
+ SVN_ERR(svn_sqlite__bind_text(stmt, 9, working_node->moved_to));
+
+ if (working_node->checksum)
+ {
+ const char *kind_str = (working_node->checksum->kind == svn_checksum_md5
+ ? "$md5 $" : "$sha1$");
+ SVN_ERR(svn_sqlite__bind_text(stmt, 10, apr_pstrcat(scratch_pool,
+ kind_str, svn_checksum_to_cstring(working_node->checksum,
+ scratch_pool), NULL)));
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 11, working_node->translated_size));
+ }
+
+ if (working_node->changed_rev != SVN_INVALID_REVNUM)
+ {
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 12, working_node->changed_rev));
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 13, working_node->changed_date));
+ SVN_ERR(svn_sqlite__bind_text(stmt, 14, working_node->changed_author));
+ }
+
+ SVN_ERR(svn_sqlite__bind_text(stmt, 15,
+ svn_depth_to_word(working_node->depth)));
+
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 16, working_node->last_mod_time));
+
+ if (working_node->properties)
+ SVN_ERR(svn_skel__unparse_proplist(&skel, working_node->properties,
+ scratch_pool));
+ else
+ skel = svn_skel__make_empty_list(scratch_pool);
+
+ properties = svn_skel__unparse(skel, scratch_pool);
+ SVN_ERR(svn_sqlite__bind_blob(stmt, 17, properties->data, properties->len));
+
+ if (working_node->changelist_id > 0)
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 18, working_node->changelist_id));
+
+ if (working_node->tree_conflict_data)
+ SVN_ERR(svn_sqlite__bind_text(stmt, 19, working_node->tree_conflict_data));
+
+ /* Execute and reset the insert clause. */
+ return svn_sqlite__insert(NULL, stmt);
+}
+
+static svn_error_t *
+insert_actual_node(svn_sqlite__db_t *wc_db,
+ const db_actual_node_t *actual_node,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_stringbuf_t *properties;
+ svn_skel_t *skel;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wc_db, STMT_INSERT_ACTUAL_NODE));
+
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 1, actual_node->wc_id));
+ SVN_ERR(svn_sqlite__bind_text(stmt, 2, actual_node->local_relpath));
+
+ if (actual_node->properties)
+ SVN_ERR(svn_skel__unparse_proplist(&skel, actual_node->properties,
+ scratch_pool));
+ else
+ skel = svn_skel__make_empty_list(scratch_pool);
+
+ properties = svn_skel__unparse(skel, scratch_pool);
+ SVN_ERR(svn_sqlite__bind_blob(stmt, 3, properties->data, properties->len));
+
+ if (actual_node->conflict_old)
+ {
+ SVN_ERR(svn_sqlite__bind_text(stmt, 4, actual_node->conflict_old));
+ SVN_ERR(svn_sqlite__bind_text(stmt, 5, actual_node->conflict_new));
+ SVN_ERR(svn_sqlite__bind_text(stmt, 6, actual_node->conflict_working));
+ }
+
+ if (actual_node->prop_reject)
+ SVN_ERR(svn_sqlite__bind_text(stmt, 7, actual_node->prop_reject));
+
+ if (actual_node->changelist_id > 0)
+ SVN_ERR(svn_sqlite__bind_int64(stmt, 8, actual_node->changelist_id));
+
+ /* Execute and reset the insert clause. */
+ return svn_sqlite__insert(NULL, stmt);
+}
+
+static svn_error_t *
+insert_changelist(svn_sqlite__db_t *wc_db,
+ db_changelist_t *changelist,
+ apr_int64_t *changelist_id,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__stmt_t *stmt;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wc_db, STMT_INSERT_CHANGELIST));
+
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", (apr_int64_t) changelist->wc_id,
+ changelist->name));
+
+ /* Execute and reset the insert clause. */
+ return svn_sqlite__insert(changelist_id, stmt);
+}
+
+/* Write the information for ENTRY to WC_DB. The WC_ID, REPOS_ID and
+ REPOS_ROOT will all be used for writing ENTRY. */
+static svn_error_t *
+write_entry(svn_sqlite__db_t *wc_db,
+ apr_int64_t wc_id,
+ apr_int64_t repos_id,
+ const char *repos_root,
+ const svn_wc_entry_t *entry,
+ const char *name,
+ const svn_wc_entry_t *this_dir,
+ apr_pool_t *pool)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t got_row;
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+ db_base_node_t *base_node = NULL;
+ db_working_node_t *working_node = NULL;
+ db_actual_node_t *actual_node = NULL;
+
+ switch (entry->schedule)
+ {
+ case svn_wc_schedule_normal:
+ base_node = MAYBE_ALLOC(base_node, scratch_pool);
+
+ /* We also need to chuck any existing working node. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wc_db,
+ STMT_DELETE_WORKING_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, name));
+ SVN_ERR(svn_sqlite__step(&got_row, stmt));
+ SVN_ERR(svn_sqlite__reset(stmt));
+ break;
+
+ case svn_wc_schedule_add:
+ working_node = MAYBE_ALLOC(working_node, scratch_pool);
+
+ /* We also need to chuck any existing base node. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wc_db,
+ STMT_DELETE_BASE_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, name));
+ SVN_ERR(svn_sqlite__step(&got_row, stmt));
+ SVN_ERR(svn_sqlite__reset(stmt));
+ break;
+
+ case svn_wc_schedule_delete:
+ case svn_wc_schedule_replace:
+ working_node = MAYBE_ALLOC(working_node, scratch_pool);
+ base_node = MAYBE_ALLOC(base_node, scratch_pool);
+ break;
+ }
+
+ if (entry->copied)
+ {
+ working_node = MAYBE_ALLOC(working_node, scratch_pool);
+ working_node->copyfrom_repos_path = entry->copyfrom_url;
+ working_node->copyfrom_revnum = entry->copyfrom_rev;
+ }
+
+ if (entry->deleted)
+ working_node = MAYBE_ALLOC(working_node, scratch_pool);
+
+ if (entry->absent)
+ {
+ /* TODO: Adjust kinds to absent kinds. */
+ }
+
+ if (entry->incomplete)
+ {
+ base_node = MAYBE_ALLOC(base_node, scratch_pool);
+ base_node->incomplete_children = TRUE;
+ }
+
+ if (entry->conflict_old)
+ {
+ actual_node = MAYBE_ALLOC(actual_node, scratch_pool);
+ actual_node->conflict_old = entry->conflict_old;
+ actual_node->conflict_new = entry->conflict_new;
+ actual_node->conflict_working = entry->conflict_wrk;
+ }
+
+ if (entry->prejfile)
+ {
+ actual_node = MAYBE_ALLOC(actual_node, scratch_pool);
+ actual_node->prop_reject = entry->prejfile;
+ }
+
+ if (entry->changelist)
+ {
+ db_changelist_t changelist;
+ apr_int64_t changelist_id;
+
+ changelist.wc_id = wc_id;
+ changelist.name = entry->changelist;
+
+ SVN_ERR(insert_changelist(wc_db, &changelist, &changelist_id,
+ scratch_pool));
+
+ if (working_node)
+ working_node->changelist_id = changelist_id;
+ if (actual_node)
+ actual_node->changelist_id = changelist_id;
+ }
+
+ if (entry->tree_conflict_data)
+ {
+ working_node = MAYBE_ALLOC(working_node, scratch_pool);
+ working_node->tree_conflict_data = entry->tree_conflict_data;
+ }
+
+ /* Insert the base node. */
+ if (base_node)
+ {
+ base_node->wc_id = wc_id;
+ base_node->local_relpath = name;
+ base_node->kind = entry->kind;
+ base_node->revision = entry->revision;
+ base_node->depth = entry->depth;
+
+ if (entry->kind == svn_node_dir)
+ base_node->checksum = NULL;
+ else
+ SVN_ERR(svn_checksum_parse_hex(&base_node->checksum, svn_checksum_md5,
+ entry->checksum, scratch_pool));
+
+ if (repos_root)
+ {
+ base_node->repos_id = repos_id;
+ if (entry->url != NULL)
+ {
+ base_node->repos_relpath = svn_path_is_child(repos_root,
+ entry->url,
+ scratch_pool);
+
+ if (base_node->repos_relpath == NULL)
+ base_node->repos_relpath = "";
+ }
+ else
+ {
+ const char *base_path = svn_path_is_child(repos_root,
+ this_dir->url,
+ scratch_pool);
+ if (base_path == NULL)
+ base_path = repos_root;
+
+ base_node->repos_relpath = svn_path_join(base_path, entry->name,
+ scratch_pool);
+ }
+ }
+
+ /* TODO: These values should always be present, if they are missing
+ during an upgrade, set a flag, and then ask the user to talk to the
+ server. */
+ base_node->changed_rev = entry->cmt_rev;
+ base_node->changed_date = entry->cmt_date;
+ base_node->changed_author = entry->cmt_author == NULL
+ ? "<unknown author>"
+ : entry->cmt_author;
+
+ SVN_ERR(insert_base_node(wc_db, base_node, scratch_pool));
+ }
+
+ /* Insert the working node. */
+ if (working_node)
+ {
+ working_node->wc_id = wc_id;
+ working_node->local_relpath = name;
+ working_node->parent_relpath = "";
+ working_node->depth = entry->depth;
+
+ if (entry->kind == svn_node_dir)
+ working_node->checksum = NULL;
+ else
+ SVN_ERR(svn_checksum_parse_hex(&working_node->checksum,
+ svn_checksum_md5,
+ entry->checksum, scratch_pool));
+
+ if (entry->schedule == svn_wc_schedule_delete)
+ working_node->kind = svn_node_none;
+ else
+ working_node->kind = entry->kind;
+
+ SVN_ERR(insert_working_node(wc_db, working_node, scratch_pool));
+ }
+
+ /* Insert the actual node. */
+ if (actual_node)
+ {
+ actual_node->wc_id = wc_id;
+ actual_node->local_relpath = name;
+
+ SVN_ERR(insert_actual_node(wc_db, actual_node, scratch_pool));
+ }
+
+ svn_pool_destroy(scratch_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Baton for use with entries_write_body(). */
+struct entries_write_txn_baton
+{
+ apr_hash_t *entries;
+ const svn_wc_entry_t *this_dir;
+ apr_pool_t *scratch_pool;
+};
+
+/* Actually do the sqlite work within a transaction.
+ This implements svn_sqlite__transaction_callback_t */
+static svn_error_t *
+entries_write_body(void *baton,
+ svn_sqlite__db_t *wc_db)
+{
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ apr_hash_index_t *hi;
+ struct entries_write_txn_baton *ewtb = baton;
+ apr_pool_t *iterpool = svn_pool_create(ewtb->scratch_pool);
+ const char *repos_root;
+ apr_int64_t repos_id;
+ apr_int64_t wc_id;
+
+ /* Get the repos ID. */
+ if (ewtb->this_dir->uuid != NULL)
+ {
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wc_db, STMT_SELECT_REPOSITORY));
+ SVN_ERR(svn_sqlite__bindf(stmt, "s", ewtb->this_dir->uuid));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+ if (!have_row)
+ return svn_error_createf(SVN_ERR_WC_DB_ERROR, NULL,
+ _("No REPOSITORY table entry for uuid '%s'"),
+ ewtb->this_dir->uuid);
+
+ repos_id = svn_sqlite__column_int(stmt, 0);
+ repos_root = svn_sqlite__column_text(stmt, 1, ewtb->scratch_pool);
+ SVN_ERR(svn_sqlite__reset(stmt));
+ }
+ else
+ {
+ repos_id = 0;
+ repos_root = NULL;
+ }
+
+ /* Write out "this dir" */
+ SVN_ERR(fetch_wc_id(&wc_id, wc_db));
+ SVN_ERR(write_entry(wc_db, wc_id, repos_id, repos_root, ewtb->this_dir,
+ SVN_WC_ENTRY_THIS_DIR, ewtb->this_dir,
+ ewtb->scratch_pool));
+
+ for (hi = apr_hash_first(ewtb->scratch_pool, ewtb->entries); hi;
+ hi = apr_hash_next(hi))
+ {
+ const void *key;
+ void *val;
+ const svn_wc_entry_t *this_entry;
+
+ svn_pool_clear(iterpool);
+
+ /* Get the entry and make sure its attributes are up-to-date. */
+ apr_hash_this(hi, &key, NULL, &val);
+ this_entry = val;
+
+ /* Don't rewrite the "this dir" entry! */
+ if (strcmp(key, SVN_WC_ENTRY_THIS_DIR) == 0)
+ continue;
+
+ /* Write the entry. */
+ SVN_ERR(write_entry(wc_db, wc_id, repos_id, repos_root,
+ this_entry, key, ewtb->this_dir, iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
svn_error_t *
svn_wc__entries_write(apr_hash_t *entries,
svn_wc_adm_access_t *adm_access,
apr_pool_t *pool)
{
+ svn_sqlite__db_t *wc_db;
+ const svn_wc_entry_t *this_dir;
+ struct entries_write_txn_baton ewtb;
+
+ SVN_ERR(svn_wc__adm_write_check(adm_access, pool));
+
+ /* Get a copy of the "this dir" entry for comparison purposes. */
+ this_dir = apr_hash_get(entries, SVN_WC_ENTRY_THIS_DIR,
+ APR_HASH_KEY_STRING);
+
+ /* If there is no "this dir" entry, something is wrong. */
+ if (! this_dir)
+ return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL,
+ _("No default entry in directory '%s'"),
+ svn_path_local_style
+ (svn_wc_adm_access_path(adm_access), pool));
+
+ /* Open the wc.db sqlite database. */
+ SVN_ERR(svn_sqlite__open(&wc_db,
+ db_path(svn_wc_adm_access_path(adm_access), pool),
+ svn_sqlite__mode_readwrite, statements,
+ SVN_WC__VERSION, upgrade_sql, pool, pool));
+
+ /* Do the work in a transaction. */
+ ewtb.entries = entries;
+ ewtb.this_dir = this_dir;
+ ewtb.scratch_pool = pool;
+ SVN_ERR(svn_sqlite__with_transaction(wc_db, entries_write_body, &ewtb));
+
+ svn_wc__adm_access_set_entries(adm_access, TRUE, entries);
+ svn_wc__adm_access_set_entries(adm_access, FALSE, NULL);
+
+ return SVN_NO_ERROR;
+
+#ifdef FROM_TRUNK
return svn_wc__entries_write_old(entries, adm_access, pool);
+#endif
}
@@ -843,11 +1996,63 @@
return SVN_NO_ERROR;
}
-
-void
-svn_wc__entry_remove(apr_hash_t *entries, const char *name)
+/* Actually do the sqlite removal work within a transaction.
+ This implements svn_sqlite__transaction_callback_t */
+static svn_error_t *
+entry_remove_body(void *baton,
+ svn_sqlite__db_t *wc_db)
{
+ const char *local_relpath = baton;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t got_row; /* Meaningless when doing a delete. */
+ apr_int64_t wc_id;
+
+ SVN_ERR(fetch_wc_id(&wc_id, wc_db));
+
+ /* Remove the base node. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wc_db, STMT_DELETE_BASE_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&got_row, stmt));
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ /* Remove the working node. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wc_db, STMT_DELETE_WORKING_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&got_row, stmt));
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ /* Remove the actual node. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wc_db, STMT_DELETE_ACTUAL_NODE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, local_relpath));
+ SVN_ERR(svn_sqlite__step(&got_row, stmt));
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__entry_remove(apr_hash_t *entries,
+ const char *parent_dir,
+ const char *name,
+ apr_pool_t *scratch_pool)
+{
+ svn_sqlite__db_t *wc_db;
+
apr_hash_set(entries, name, APR_HASH_KEY_STRING, NULL);
+
+ /* Also remove from the sqlite database. */
+ /* Open the wc.db sqlite database. */
+ SVN_ERR(svn_sqlite__open(&wc_db, db_path(parent_dir, scratch_pool),
+ svn_sqlite__mode_readwrite, statements,
+ SVN_WC__VERSION, upgrade_sql,
+ scratch_pool, scratch_pool));
+
+ /* Do the work in a transaction, for consistency. */
+ SVN_ERR(svn_sqlite__with_transaction(wc_db, entry_remove_body,
+ (void *) name));
+
+ return SVN_NO_ERROR;
}
@@ -1316,6 +2521,57 @@
/*** Initialization of the entries file. ***/
+/* Baton for use with init_body() */
+struct init_txn_baton
+{
+ const char *uuid;
+ const char *url;
+ const char *repos;
+ svn_revnum_t initial_rev;
+ svn_depth_t depth;
+ apr_pool_t *scratch_pool;
+};
+
+/* Actually do the sqlite work within a transaction.
+ This implements svn_sqlite__transaction_callback_t */
+static svn_error_t *
+init_body(void *baton,
+ svn_sqlite__db_t *wc_db)
+{
+ struct init_txn_baton *itb = baton;
+ svn_sqlite__stmt_t *stmt;
+ apr_int64_t wc_id;
+ apr_int64_t repos_id;
+ svn_wc_entry_t *entry = alloc_entry(itb->scratch_pool);
+
+ /* Insert the repository. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wc_db, STMT_INSERT_REPOSITORY));
+ SVN_ERR(svn_sqlite__bindf(stmt, "ss", itb->repos, itb->uuid));
+ SVN_ERR(svn_sqlite__insert(&repos_id, stmt));
+
+ /* Insert the wcroot. */
+ /* TODO: Right now, this just assumes wc metadata is being stored locally. */
+ SVN_ERR(svn_sqlite__get_statement(&stmt, wc_db, STMT_INSERT_WCROOT));
+ SVN_ERR(svn_sqlite__insert(&wc_id, stmt));
+
+ /* Add an entry for the dir itself. The directory has no name. It
+ might have a UUID, but otherwise only the revision and default
+ ancestry are present as XML attributes, and possibly an
+ 'incomplete' flag if the revnum is > 0. */
+
+ entry->kind = svn_node_dir;
+ entry->url = itb->url;
+ entry->revision = itb->initial_rev;
+ entry->uuid = itb->uuid;
+ entry->repos = itb->repos;
+ entry->depth = itb->depth;
+ if (itb->initial_rev > 0)
+ entry->incomplete = TRUE;
+
+ return write_entry(wc_db, wc_id, repos_id, itb->repos, entry,
+ SVN_WC_ENTRY_THIS_DIR, entry, itb->scratch_pool);
+}
+
svn_error_t *
svn_wc__entries_init(const char *path,
const char *uuid,
@@ -1325,12 +2581,10 @@
svn_depth_t depth,
apr_pool_t *pool)
{
- svn_stream_t *stream;
- const char *temp_file_path;
- svn_stringbuf_t *accum = svn_stringbuf_createf(pool, "%d\n",
- SVN_WC__VERSION);
- svn_wc_entry_t *entry = alloc_entry(pool);
- apr_size_t len;
+ svn_node_kind_t kind;
+ svn_sqlite__db_t *wc_db;
+ const char *wc_db_path = db_path(path, pool);
+ struct init_txn_baton itb;
SVN_ERR_ASSERT(! repos || svn_path_is_ancestor(repos, url));
SVN_ERR_ASSERT(depth == svn_depth_empty
@@ -1338,37 +2592,26 @@
|| depth == svn_depth_immediates
|| depth == svn_depth_infinity);
- /* Create the entries file, which must not exist prior to this. */
- SVN_ERR(svn_wc__open_adm_writable(&stream, &temp_file_path,
- path, SVN_WC__ADM_ENTRIES, pool, pool));
+ /* Check that the entries sqlite database does not yet exist. */
+ SVN_ERR(svn_io_check_path(wc_db_path, &kind, pool));
+ if (kind != svn_node_none)
+ return svn_error_createf(SVN_ERR_WC_DB_ERROR, NULL,
+ _("Existing sqlite database found at '%s'"),
+ svn_path_local_style(wc_db_path, pool));
- /* Add an entry for the dir itself. The directory has no name. It
- might have a UUID, but otherwise only the revision and default
- ancestry are present as XML attributes, and possibly an
- 'incomplete' flag if the revnum is > 0. */
+ /* Create the entries database, and start a transaction. */
+ SVN_ERR(svn_sqlite__open(&wc_db, wc_db_path, svn_sqlite__mode_rwcreate,
+ statements, SVN_WC__VERSION, upgrade_sql, pool,
+ pool));
- entry->kind = svn_node_dir;
- entry->url = url;
- entry->revision = initial_rev;
- entry->uuid = uuid;
- entry->repos = repos;
- entry->depth = depth;
- if (initial_rev > 0)
- entry->incomplete = TRUE;
-
- SVN_ERR(svn_wc__write_entry_old(accum, entry, SVN_WC_ENTRY_THIS_DIR, entry,
- pool));
-
- len = accum->len;
- SVN_ERR_W(svn_stream_write(stream, accum->data, &len),
- apr_psprintf(pool,
- _("Error writing entries file for '%s'"),
- svn_path_local_style(path, pool)));
-
- /* Now we have a `entries' file with exactly one entry, an entry
- for this dir. Close the file and sync it up. */
- return svn_wc__close_adm_stream(stream, temp_file_path, path,
- SVN_WC__ADM_ENTRIES, pool);
+ /* Do the body of the work within an sqlite transaction. */
+ itb.uuid = uuid;
+ itb.url = url;
+ itb.repos = repos;
+ itb.initial_rev = initial_rev;
+ itb.depth = depth;
+ itb.scratch_pool = pool;
+ return svn_sqlite__with_transaction(wc_db, init_body, &itb);
}
diff --git a/subversion/libsvn_wc/entries.h b/subversion/libsvn_wc/entries.h
index d25b278..c8ca635 100644
--- a/subversion/libsvn_wc/entries.h
+++ b/subversion/libsvn_wc/entries.h
@@ -198,8 +198,15 @@
svn_boolean_t do_sync,
apr_pool_t *pool);
-/* Remove entry NAME from ENTRIES, unconditionally. */
-void svn_wc__entry_remove(apr_hash_t *entries, const char *name);
+/* Remove entry NAME from ENTRIES, unconditionally. PARENT_DIR should be
+ the directory which contains the .svn administrative directory for PATH
+ (in most cases, this will be svn_wc_adm_access_path() applied to the
+ access baton which ENTRIES is or will eventually be a part of. */
+svn_error_t *
+svn_wc__entry_remove(apr_hash_t *entries,
+ const char *parent_dir,
+ const char *name,
+ apr_pool_t *scratch_pool);
/* Tweak the entry NAME within hash ENTRIES. If NEW_URL is non-null,
@@ -245,7 +252,6 @@
const svn_wc_entry_t *this_dir,
apr_pool_t *pool);
-
#ifdef __cplusplus
}
#endif /* __cplusplus */
diff --git a/subversion/libsvn_wc/log.c b/subversion/libsvn_wc/log.c
index 2751b06..8eaa955 100644
--- a/subversion/libsvn_wc/log.c
+++ b/subversion/libsvn_wc/log.c
@@ -946,7 +946,9 @@
{
SVN_ERR(svn_wc_entries_read(&entries, loggy->adm_access,
TRUE, loggy->pool));
- svn_wc__entry_remove(entries, name);
+ SVN_ERR(svn_wc__entry_remove(
+ entries, svn_wc_adm_access_path(loggy->adm_access),
+ name, loggy->pool));
SVN_ERR(svn_wc__entries_write(entries, loggy->adm_access,
loggy->pool));
}
diff --git a/subversion/libsvn_wc/questions.c b/subversion/libsvn_wc/questions.c
index f8eb59e..7639264 100644
--- a/subversion/libsvn_wc/questions.c
+++ b/subversion/libsvn_wc/questions.c
@@ -38,6 +38,7 @@
#include "entries.h"
#include "props.h"
#include "translate.h"
+#include "wc_db.h"
#include "svn_private_config.h"
#include "private/svn_wc_private.h"
@@ -51,32 +52,15 @@
apr_pool_t *pool)
{
svn_error_t *err;
- const char *format_file_path = svn_wc__adm_child(path, SVN_WC__ADM_ENTRIES,
- pool);
+ svn_node_kind_t kind;
- /* First try to read the format number from the entries file. */
- err = svn_io_read_version_file(wc_format, format_file_path, pool);
+ err = svn_wc__db_version(wc_format, path, pool);
+ if (err && err->apr_err != SVN_ERR_WC_MISSING)
+ return err;
- /* If that didn't work and the first line of the entries file contains
- something other than a number, then it is probably in XML format. */
- if (err && err->apr_err == SVN_ERR_BAD_VERSION_FILE_FORMAT)
+ if (err)
{
svn_error_clear(err);
- /* Fall back on reading the format file instead.
- Note that the format file might not exist in newer working copies
- (format 7 and higher), but in that case, the entries file should
- have contained the format number. */
- format_file_path = svn_wc__adm_child(path, SVN_WC__ADM_FORMAT, pool);
-
- err = svn_io_read_version_file(wc_format, format_file_path, pool);
- }
-
- if (err && (APR_STATUS_IS_ENOENT(err->apr_err)
- || APR_STATUS_IS_ENOTDIR(err->apr_err)))
- {
- svn_node_kind_t kind;
-
- svn_error_clear(err);
/* Check path itself exists. */
SVN_ERR(svn_io_check_path(path, &kind, pool));
@@ -88,50 +72,34 @@
svn_path_local_style(path, pool));
}
- /* If the format file does not exist or path not directory, then for
+ /* If the metadata does not exist or path not directory, then for
our purposes this is not a working copy, so return 0. */
*wc_format = 0;
+ return SVN_NO_ERROR;
}
- else if (err)
- return err;
else
{
- /* If we managed to read the format file we assume that we
+ /* If we managed to read the format we assume that we
are dealing with a real wc so we can return a nice
error. */
- SVN_ERR(svn_wc__check_format(*wc_format, path, pool));
+ return svn_wc__check_format(*wc_format, path, pool);
}
-
- return SVN_NO_ERROR;
}
svn_error_t *
svn_wc__check_format(int wc_format, const char *path, apr_pool_t *pool)
{
- if (wc_format < 2)
+ if (wc_format < SVN_WC__WC_NG_VERSION)
{
return svn_error_createf
(SVN_ERR_WC_UNSUPPORTED_FORMAT, NULL,
_("Working copy format of '%s' is too old (%d); "
- "please check out your working copy again"),
- svn_path_local_style(path, pool), wc_format);
- }
- else if (wc_format > SVN_WC__VERSION)
- {
- /* This won't do us much good for the 1.4<->1.5 crossgrade,
- since 1.4.x clients don't refer to this FAQ entry, but at
- least post-1.5 crossgrades will be somewhat less painful. */
- return svn_error_createf
- (SVN_ERR_WC_UNSUPPORTED_FORMAT, NULL,
- _("This client is too old to work with working copy '%s'. You need\n"
- "to get a newer Subversion client, or to downgrade this working "
- "copy.\n"
+ "please run 'svn cleanup' to upgrade your working copy\n"
"See "
"http://subversion.tigris.org/faq.html#working-copy-format-change\n"
- "for details."
- ),
- svn_path_local_style(path, pool));
+ "for details."),
+ svn_path_local_style(path, pool), wc_format);
}
return SVN_NO_ERROR;
diff --git a/subversion/libsvn_wc/update_editor.c b/subversion/libsvn_wc/update_editor.c
index 04de446..abc2244 100644
--- a/subversion/libsvn_wc/update_editor.c
+++ b/subversion/libsvn_wc/update_editor.c
@@ -191,6 +191,9 @@
/* The URL to the root of the repository, or NULL. */
const char *repos;
+ /* The UUID of the repos, or NULL. */
+ const char *uuid;
+
/* External diff3 to use for merges (can be null, in which case
internal merge code is used). */
const char *diff3_cmd;
@@ -724,7 +727,9 @@
if (current_entry->deleted)
{
if (current_entry->schedule != svn_wc_schedule_add)
- svn_wc__entry_remove(entries, name);
+ SVN_ERR(svn_wc__entry_remove(
+ entries, svn_wc_adm_access_path(adm_access),
+ name, subpool));
else
{
svn_wc_entry_t tmpentry;
@@ -743,7 +748,9 @@
else if (current_entry->absent
&& (current_entry->revision != *(eb->target_revision)))
{
- svn_wc__entry_remove(entries, name);
+ SVN_ERR(svn_wc__entry_remove(
+ entries, svn_wc_adm_access_path(adm_access),
+ name, subpool));
}
else if (current_entry->kind == svn_node_dir)
{
@@ -759,7 +766,9 @@
&& (! current_entry->absent)
&& (current_entry->schedule != svn_wc_schedule_add))
{
- svn_wc__entry_remove(entries, name);
+ SVN_ERR(svn_wc__entry_remove(
+ entries, svn_wc_adm_access_path(adm_access),
+ name, subpool));
if (eb->notify_func)
{
svn_wc_notify_t *notify
@@ -1019,7 +1028,7 @@
/* Make sure it's the right working copy, either by creating it so,
or by checking that it is so already. */
- SVN_ERR(svn_wc_ensure_adm3(db->path, NULL,
+ SVN_ERR(svn_wc_ensure_adm3(db->path, db->edit_baton->uuid,
ancestor_url, repos,
ancestor_revision, db->ambient_depth, pool));
@@ -1956,7 +1965,9 @@
apr_hash_t *entries;
const char *base_name = svn_path_basename(full_path, pool);
SVN_ERR(svn_wc_entries_read(&entries, parent_adm_access, TRUE, pool));
- svn_wc__entry_remove(entries, base_name);
+ SVN_ERR(svn_wc__entry_remove(
+ entries, svn_wc_adm_access_path(parent_adm_access),
+ base_name, pool));
SVN_ERR(svn_wc__entries_write(entries, parent_adm_access, pool));
if (strcmp(path, eb->target) == 0)
eb->target_deleted = TRUE;
@@ -4664,6 +4675,7 @@
eb->target_revision = target_revision;
eb->switch_url = switch_url;
eb->repos = entry ? entry->repos : NULL;
+ eb->uuid = entry ? entry->uuid : NULL;
eb->adm_access = adm_access;
eb->anchor = anchor;
eb->target = target;
diff --git a/subversion/libsvn_wc/wc.h b/subversion/libsvn_wc/wc.h
index 701d494..faf124b 100644
--- a/subversion/libsvn_wc/wc.h
+++ b/subversion/libsvn_wc/wc.h
@@ -72,9 +72,13 @@
* The change from 9 to 10 was the addition of tree-conflicts, file
* externals and a different canonicalization of urls.
*
+ * The change from 10 to 11 was a complete rewrite of the wc datastore,
+ * which resulted in centralization and migration of data to an sqlite
+ * datebase.
+ *
* Please document any further format changes here.
*/
-#define SVN_WC__VERSION 10
+#define SVN_WC__VERSION 11
/* A version <= this doesn't have property caching in the entries file. */
#define SVN_WC__NO_PROPCACHING_VERSION 5
@@ -88,6 +92,9 @@
/* A version < this can have urls that aren't canonical according to the new
rules. See issue #2475. */
#define SVN_WC__CHANGED_CANONICAL_URLS 10
+
+/* A version < this is pre-wc-ng. */
+#define SVN_WC__WC_NG_VERSION 11
/*** Update traversals. ***/
diff --git a/subversion/libsvn_wc/wc_db.c b/subversion/libsvn_wc/wc_db.c
index e94de21..71036fd 100644
--- a/subversion/libsvn_wc/wc_db.c
+++ b/subversion/libsvn_wc/wc_db.c
@@ -23,10 +23,14 @@
#include "svn_types.h"
#include "svn_error.h"
#include "svn_path.h"
+#include "svn_wc.h"
+#include "wc.h"
#include "wc_db.h"
+#include "adm_files.h"
#include "svn_private_config.h"
+#include "private/svn_sqlite.h"
struct svn_wc__db_t {
@@ -214,6 +218,61 @@
svn_error_t *
+svn_wc__db_version(int *version,
+ const char *path,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ const char *format_file_path;
+
+ /* First, try reading the wc.db file. Instead of stat'ing the file to
+ see if it exists, and then opening it, we just try opening it. If we
+ get any kind of an error, wrap that eith an ENOENT error and return. */
+ err = svn_sqlite__get_schema_version(version,
+ svn_wc__adm_child(path, "wc.db",
+ scratch_pool),
+ scratch_pool);
+ if (err && err->apr_err != SVN_ERR_SQLITE_ERROR)
+ return err;
+ else if (!err)
+ return SVN_NO_ERROR;
+
+ /* Hmm, that didn't work. Now try reading the format number from the
+ entries file. */
+ svn_error_clear(err);
+ format_file_path = svn_wc__adm_child(path, SVN_WC__ADM_ENTRIES, scratch_pool);
+ err = svn_io_read_version_file(version, format_file_path, scratch_pool);
+ if (err && err->apr_err != SVN_ERR_BAD_VERSION_FILE_FORMAT)
+ return svn_error_createf(SVN_ERR_WC_MISSING, err, _("'%s' does not exist"),
+ svn_path_local_style(path, scratch_pool));
+ else if (!err)
+ return SVN_NO_ERROR;
+
+ /* Wow, another error; this must be a really old working copy! Fall back
+ to reading the format file. */
+ svn_error_clear(err);
+ /* Note that the format file might not exist in newer working copies
+ (format 7 and higher), but in that case, the entries file should
+ have contained the format number. */
+ format_file_path = svn_wc__adm_child(path, SVN_WC__ADM_FORMAT, scratch_pool);
+ err = svn_io_read_version_file(version, format_file_path, scratch_pool);
+
+ if (err && (APR_STATUS_IS_ENOENT(err->apr_err)
+ || APR_STATUS_IS_ENOTDIR(err->apr_err)))
+ return svn_error_createf(SVN_ERR_WC_MISSING, err, _("'%s' does not exist"),
+ svn_path_local_style(path, scratch_pool));
+ else if (!err)
+ return SVN_NO_ERROR;
+
+ /* If we've gotten this far, all of the above checks have failed, so just
+ bail. */
+ return svn_error_createf(SVN_ERR_WC_MISSING, NULL,
+ _("'%s' is not a working copy"),
+ svn_path_local_style(path, scratch_pool));
+}
+
+
+svn_error_t *
svn_wc__db_txn_begin(svn_wc__db_t *db,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
diff --git a/subversion/libsvn_wc/wc_db.h b/subversion/libsvn_wc/wc_db.h
index f9f150d..de88733 100644
--- a/subversion/libsvn_wc/wc_db.h
+++ b/subversion/libsvn_wc/wc_db.h
@@ -185,6 +185,18 @@
apr_pool_t *result_pool,
apr_pool_t *scratch_pool);
+/* This function answers at simple question: what format version of the wc
+ exists at PATH. The reason it takes a PATH instead of an existing db
+ handle is because it may need to use legacy, pre-wc-ng methods to determine
+ what that version is, and such versions don't have any db to open.
+
+ If no working copy exists at PATH, return SVN_ERR_WC_MISSING. */
+svn_error_t *
+svn_wc__db_version(int *version,
+ const char *path,
+ apr_pool_t *scratch_pool);
+
+
/**
* Start a transaction for the database(s) which are part of @a db.
*