| /* |
| * wc_db.c : manipulating the administrative database |
| * |
| * ==================================================================== |
| * Copyright (c) 2008 CollabNet. All rights reserved. |
| * |
| * This software is licensed as described in the file COPYING, which |
| * you should have received as part of this distribution. The terms |
| * are also available at http://subversion.tigris.org/license-1.html. |
| * If newer versions of this license are posted there, you may use a |
| * newer version instead, at your option. |
| * |
| * This software consists of voluntary contributions made by many |
| * individuals. For exact contribution history, see the revision |
| * history and logs, available at http://subversion.tigris.org/. |
| * ==================================================================== |
| */ |
| |
| #include <assert.h> |
| #include <apr_pools.h> |
| #include <apr_hash.h> |
| |
| #include "svn_types.h" |
| #include "svn_error.h" |
| #include "svn_dirent_uri.h" |
| #include "svn_wc.h" |
| #include "svn_checksum.h" |
| |
| #include "wc.h" |
| #include "wc_db.h" |
| #include "adm_files.h" |
| #include "wc-metadata.h" |
| |
| #include "svn_private_config.h" |
| #include "private/svn_sqlite.h" |
| #include "private/svn_skel.h" |
| |
| |
| #define NOT_IMPLEMENTED() \ |
| return svn_error__malfunction(TRUE, __FILE__, __LINE__, "Not implemented.") |
| |
| |
| /* |
| * PARAMETER ASSERTIONS |
| * |
| * Every (semi-)public entrypoint in this file has a set of assertions on |
| * the parameters passed into the function. Since this is a brand new API, |
| * we want to make sure that everybody calls it properly. The original WC |
| * code had years to catch stray bugs, but we do not have that luxury in |
| * the wc-nb rewrite. Any extra assurances that we can find will be |
| * welcome. The asserts will ensure we have no doubt about the values |
| * passed into the function. |
| * |
| * Some parameters are *not* specifically asserted. Typically, these are |
| * params that will be used immediately, so something like a NULL value |
| * will be obvious. |
| * |
| * ### near 1.7 release, it would be a Good Thing to review the assertions |
| * ### and decide if any can be removed or switched to assert() in order |
| * ### to remove their runtime cost in the production release. |
| * |
| * |
| * DATABASE OPERATIONS |
| * |
| * Each function should leave the database in a consistent state. If it |
| * does *not*, then the implication is some other function needs to be |
| * called to restore consistency. Subtle requirements like that are hard |
| * to maintain over a long period of time, so this API will not allow it. |
| * |
| * |
| * STANDARD VARIABLE NAMES |
| * |
| * db working copy database (this module) |
| * sdb SQLite database (not to be confused with 'db') |
| * wc_id a WCROOT id associated with a node |
| */ |
| |
| |
| struct svn_wc__db_t { |
| /* What's the appropriate mode for this datastore? */ |
| svn_wc__db_openmode_t mode; |
| |
| /* We need the config whenever we run into a new WC directory, in order |
| to figure out where we should look for the corresponding datastore. */ |
| svn_config_t *config; |
| |
| /* Map a given working copy directory to its relevant data. */ |
| apr_hash_t *dir_data; |
| |
| /* As we grow the state of this DB, allocate that state here. */ |
| apr_pool_t *state_pool; |
| }; |
| |
| /** |
| * This structure records all the information that we need to deal with |
| * a given working copy directory. |
| */ |
| struct svn_wc__db_pdh_t { |
| /* This per-dir state is associated with this global state. */ |
| svn_wc__db_t *db; |
| |
| /* This (versioned) working copy directory is obstructing what *should* |
| be a file in the parent directory (according to its metadata). |
| |
| Note: this PDH should probably be ignored (or not created). |
| |
| ### obstruction is only possible with per-dir wc.db databases. */ |
| svn_boolean_t obstructed_file; |
| |
| /* The absolute path to this working copy directory. */ |
| const char *local_abspath; |
| |
| /* The relative path from the wcroot to this directory. */ |
| const char *local_relpath; |
| |
| /* The SQLite database containing the metadata for everything in |
| this directory. */ |
| svn_sqlite__db_t *sdb; |
| |
| /* The WCROOT id this directory is part of. */ |
| apr_int64_t wc_id; |
| |
| /* The root directory of this WCROOT. */ |
| const char *wcroot_abspath; |
| |
| /* Root of the TEXT-BASE directory structure for the WORKING/ACTUAL files |
| in this directory. */ |
| const char *base_dir; |
| |
| /* The parent directory's per-dir information. */ |
| svn_wc__db_pdh_t *parent; |
| }; |
| |
| /* ### since we're putting the pristine files per-dir, then we don't need |
| ### to create subdirectories in order to keep the directory size down. |
| ### when we can aggregate pristine files across dirs/wcs, then we will |
| ### need to undo the SKIP. */ |
| #define SVN__SKIP_SUBDIR |
| |
| /* ### duplicates entries.c */ |
| static const char * const upgrade_sql[] = { |
| NULL, NULL, NULL, NULL, NULL, |
| NULL, NULL, NULL, NULL, NULL, |
| NULL, NULL, |
| WC_METADATA_SQL |
| }; |
| |
| /* These values map to the members of STATEMENTS below, and should be added |
| and removed at the same time. */ |
| enum statement_keys { |
| STMT_SELECT_BASE_NODE, |
| STMT_SELECT_BASE_NODE_WITH_LOCK, |
| STMT_SELECT_WORKING_NODE, |
| STMT_SELECT_ACTUAL_NODE, |
| STMT_SELECT_REPOSITORY_BY_ID, |
| STMT_SELECT_WCROOT_NULL, |
| STMT_SELECT_REPOSITORY, |
| STMT_INSERT_REPOSITORY, |
| STMT_INSERT_BASE_NODE, |
| STMT_INSERT_BASE_NODE_INCOMPLETE, |
| STMT_SELECT_BASE_NODE_CHILDREN, |
| STMT_SELECT_WORKING_CHILDREN, |
| STMT_SELECT_WORKING_IS_FILE, |
| STMT_SELECT_BASE_IS_FILE, |
| STMT_SELECT_BASE_PROPS, |
| STMT_UPDATE_ACTUAL_PROPS, |
| STMT_SELECT_ALL_PROPS, |
| STMT_SELECT_PRISTINE_PROPS, |
| STMT_INSERT_LOCK, |
| STMT_INSERT_WCROOT, |
| STMT_UPDATE_BASE_WCPROPS, |
| STMT_SELECT_BASE_WCPROPS, |
| STMT_SELECT_DELETION_INFO |
| }; |
| |
| static const char * const statements[] = { |
| "select wc_id, local_relpath, repos_id, repos_relpath, " |
| " presence, kind, revnum, checksum, translated_size, " |
| " changed_rev, changed_date, changed_author, depth, symlink_target, " |
| " last_mod_time " |
| "from base_node " |
| "where wc_id = ?1 and local_relpath = ?2;", |
| |
| "select wc_id, local_relpath, base_node.repos_id, base_node.repos_relpath, " |
| " presence, kind, revnum, checksum, translated_size, " |
| " changed_rev, changed_date, changed_author, depth, symlink_target, " |
| " last_mod_time, " |
| " lock_token, lock_owner, lock_comment, lock_date " |
| "from base_node " |
| "left outer join lock on base_node.repos_id = lock.repos_id " |
| " and base_node.repos_relpath = lock.repos_relpath " |
| "where wc_id = ?1 and local_relpath = ?2;", |
| |
| "select presence, kind, checksum, translated_size, " |
| " changed_rev, changed_date, changed_author, depth, symlink_target, " |
| " copyfrom_repos_id, copyfrom_repos_path, copyfrom_revnum, " |
| " moved_here, moved_to, last_mod_time " |
| "from working_node " |
| "where wc_id = ?1 and local_relpath = ?2;", |
| |
| "select changelist " |
| "from actual_node " |
| "where wc_id = ?1 and local_relpath = ?2;", |
| |
| "select root, uuid from repository where id = ?1;", |
| |
| "select id from wcroot where local_abspath is null;", |
| |
| "select id from repository where uuid = ?1;", |
| |
| "insert into repository (root, uuid) values (?1, ?2);", |
| |
| "insert or replace into base_node (" |
| " wc_id, local_relpath, repos_id, repos_relpath, parent_relpath, presence, " |
| " kind, revnum, properties, changed_rev, changed_date, changed_author, " |
| " depth, checksum, translated_size, symlink_target, incomplete_children) " |
| "values (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, " |
| " ?15, ?16, ?17);", |
| |
| "insert or ignore into base_node (" |
| " wc_id, local_relpath, parent_relpath, presence, kind, revnum) " |
| "values (?1, ?2, ?3, 'incomplete', 'unknown', ?5);", |
| |
| "select local_relpath from base_node " |
| "where wc_id = ?1 and parent_relpath = ?2;", |
| |
| "select local_relpath from base_node " |
| "where wc_id = ?1 and parent_relpath = ?2 " |
| "union " |
| "select local_relpath from working_node " |
| "where wc_id = ?1 and parent_relpath = ?2;", |
| |
| "select kind == 'file' from working_node " |
| "where wc_id = ?1 and local_relpath = ?2;", |
| |
| "select kind == 'file' from base_node " |
| "where wc_id = ?1 and local_relpath = ?2;", |
| |
| "select properties from base_node " |
| "where wc_id = ?1 and local_relpath = ?2;", |
| |
| "update actual_node set properties = ?3 " |
| "where wc_id = ?1 and local_relpath = ?2;", |
| |
| "select actual_node.properties, working_node.properties, " |
| " base_node.properties " |
| "from base_node " |
| "left outer join working_node on base_node.wc_id = working_node.wc_id " |
| " and base_node.local_relpath = working_node.local_relpath " |
| "left outer join actual_node on base_node.wc_id = actual_node.wc_id " |
| " and base_node.local_relpath = actual_node.local_relpath " |
| "where base_node.wc_id = ?1 and base_node.local_relpath = ?2;", |
| |
| "select working_node.properties, base_node.properties " |
| "from base_node " |
| "left outer join working_node on base_node.wc_id = working_node.wc_id " |
| " and base_node.local_relpath = working_node.local_relpath " |
| "where base_node.wc_id = ?1 and base_node.local_relpath = ?2;", |
| |
| "insert or replace into lock " |
| "(repos_id, repos_relpath, lock_token, lock_owner, lock_comment, " |
| " lock_date)" |
| "values (?1, ?2, ?3, ?4, ?5, ?6);", |
| |
| "insert into wcroot (local_abspath) " |
| "values (?1);", |
| |
| "update base_node set wc_props = ?3 " |
| "where wc_id = ?1 and local_relpath = ?2;", |
| |
| "select wc_props from base_node " |
| "where wc_id = ?1 and local_relpath = ?2;", |
| |
| "select base_node.presence, working_node.presence, moved_to " |
| "from working_node " |
| "left outer join base_node on base_node.wc_id = working_node.wc_id " |
| " and base_node.local_relpath = working_node.local_relpath " |
| "where working_node.wc_id = ?1 and working_node.local_relpath = ?2;", |
| |
| NULL |
| }; |
| |
| typedef struct { |
| /* common to all insertions into BASE */ |
| svn_wc__db_status_t status; |
| svn_wc__db_kind_t kind; |
| apr_int64_t wc_id; |
| const char *local_relpath; |
| apr_int64_t repos_id; |
| const char *repos_relpath; |
| svn_revnum_t revision; |
| |
| /* common to all "normal" presence insertions */ |
| const apr_hash_t *props; |
| svn_revnum_t changed_rev; |
| apr_time_t changed_date; |
| const char *changed_author; |
| |
| /* for inserting directories */ |
| const apr_array_header_t *children; |
| svn_depth_t depth; |
| |
| /* for inserting files */ |
| const svn_checksum_t *checksum; |
| svn_filesize_t translated_size; |
| |
| /* for inserting symlinks */ |
| const char *target; |
| |
| /* for inserting incomplete directories */ |
| /* ### this is a temp column, and this field will eventual disappear. */ |
| svn_boolean_t incomplete_children; |
| |
| /* for temporary allocations */ |
| apr_pool_t *scratch_pool; |
| |
| } insert_base_baton_t; |
| |
| |
| static svn_wc__db_kind_t |
| word_to_kind(const char *kind) |
| { |
| /* Let's be lazy and fast */ |
| switch (kind[0]) |
| { |
| case 'f': |
| return svn_wc__db_kind_file; |
| case 'd': |
| return svn_wc__db_kind_dir; |
| case 's': |
| return kind[1] == 'y' ? svn_wc__db_kind_symlink : svn_wc__db_kind_subdir; |
| default: |
| /* Given our laziness, do not MALFUNCTION here. */ |
| return svn_wc__db_kind_unknown; |
| } |
| } |
| |
| |
| static const char * |
| kind_to_word(svn_wc__db_kind_t kind) |
| { |
| switch (kind) |
| { |
| case svn_wc__db_kind_dir: |
| return "dir"; |
| case svn_wc__db_kind_file: |
| return "file"; |
| case svn_wc__db_kind_symlink: |
| return "symlink"; |
| case svn_wc__db_kind_unknown: |
| return "unknown"; |
| case svn_wc__db_kind_subdir: |
| return "subdir"; |
| default: |
| SVN_ERR_MALFUNCTION_NO_RETURN(); |
| } |
| } |
| |
| |
| /* Note: we only decode presence values from the databse. These are a subset |
| of all the status values. */ |
| static svn_wc__db_status_t |
| word_to_presence(const char *presence) |
| { |
| /* Be lazy and fast. */ |
| switch (presence[0]) |
| { |
| case 'a': |
| return svn_wc__db_status_absent; |
| case 'e': |
| return svn_wc__db_status_excluded; |
| case 'i': |
| return svn_wc__db_status_incomplete; |
| case 'b': |
| return svn_wc__db_status_base_deleted; |
| default: |
| if (strcmp(presence, "not-present") == 0) |
| return svn_wc__db_status_not_present; |
| /* Do not MALFUNCTION here if presence is not "normal". */ |
| return svn_wc__db_status_normal; |
| } |
| } |
| |
| |
| static const char * |
| presence_to_word(svn_wc__db_status_t presence) |
| { |
| switch (presence) |
| { |
| case svn_wc__db_status_normal: |
| return "normal"; |
| case svn_wc__db_status_absent: |
| return "absent"; |
| case svn_wc__db_status_excluded: |
| return "excluded"; |
| case svn_wc__db_status_not_present: |
| return "not-present"; |
| case svn_wc__db_status_incomplete: |
| return "incomplete"; |
| case svn_wc__db_status_base_deleted: |
| return "base-delete"; |
| default: |
| SVN_ERR_MALFUNCTION_NO_RETURN(); |
| } |
| } |
| |
| |
| static svn_filesize_t |
| get_translated_size(svn_sqlite__stmt_t *stmt, int slot) |
| { |
| if (svn_sqlite__column_is_null(stmt, slot)) |
| return SVN_INVALID_FILESIZE; |
| return svn_sqlite__column_int64(stmt, slot); |
| } |
| |
| |
| static svn_error_t * |
| get_pristine_fname(const char **path, |
| svn_wc__db_pdh_t *pdh, |
| const svn_checksum_t *checksum, |
| svn_boolean_t create_subdir, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| const char *hexdigest = svn_checksum_to_cstring(checksum, scratch_pool); |
| #ifndef SVN__SKIP_SUBDIR |
| char subdir[3] = { 0 }; |
| #endif |
| |
| /* We should have a valid checksum and (thus) a valid digest. */ |
| SVN_ERR_ASSERT(hexdigest != NULL); |
| |
| #ifndef SVN__SKIP_SUBDIR |
| /* Get the first two characters of the digest, for the subdir. */ |
| subdir[0] = hexdigest[0]; |
| subdir[1] = hexdigest[1]; |
| |
| if (create_subdir) |
| { |
| const char *subdir_path = svn_dirent_join(pdh->base_dir, subdir, |
| scratch_pool); |
| svn_error_t *err; |
| |
| err = svn_io_dir_make(subdir_path, APR_OS_DEFAULT, scratch_pool); |
| |
| /* Whatever error may have occurred... ignore it. Typically, this |
| will be "directory already exists", but if it is something |
| *different*, then presumably another error will follow when we |
| try to access the file within this (missing?) pristine subdir. */ |
| svn_error_clear(err); |
| } |
| #endif |
| |
| /* The file is located at DIR/.svn/pristine/XX/XXYYZZ... */ |
| *path = svn_dirent_join_many(result_pool, |
| pdh->base_dir, |
| #ifndef SVN__SKIP_SUBDIR |
| subdir, |
| #endif |
| hexdigest, |
| NULL); |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t * |
| open_one_directory(svn_wc__db_t *db, |
| const char *path, |
| apr_pool_t *scratch_pool) |
| { |
| svn_node_kind_t kind; |
| svn_boolean_t special; |
| svn_wc__db_pdh_t *pdh; |
| |
| SVN_ERR_ASSERT(svn_dirent_is_absolute(path)); |
| |
| /* If the file is special, then we need to refer to the encapsulating |
| directory instead, rather than resolving through a symlink to a |
| file or directory. */ |
| SVN_ERR(svn_io_check_special_path(path, &kind, &special, scratch_pool)); |
| |
| /* ### skip unknown and/or not-found paths? need to examine typical |
| ### caller usage. */ |
| |
| if (kind != svn_node_dir) |
| { |
| /* ### doesn't seem that we need to keep the original path */ |
| path = svn_dirent_dirname(path, scratch_pool); |
| } |
| |
| pdh = apr_hash_get(db->dir_data, path, APR_HASH_KEY_STRING); |
| if (pdh != NULL) |
| return SVN_NO_ERROR; /* seen this directory already! */ |
| |
| pdh = apr_palloc(db->state_pool, sizeof(*pdh)); |
| pdh->db = db; |
| |
| /* Make sure the key lasts as long as the hash. Note that if we did |
| not call dirname(), then this path is the provided path, but we |
| do not know its lifetime (nor does our API contract specify a |
| requirement for the lifetime). */ |
| pdh->local_abspath = apr_pstrdup(db->state_pool, path); |
| |
| /* ### local_relpath */ |
| /* ### sdb */ |
| /* ### wc_id */ |
| |
| /* ### for now, every directory still has a .svn subdir, and a |
| ### "pristine" subdir in there. later on, we'll alter the |
| ### storage location/strategy */ |
| |
| /* ### need to fix this to use a symbol for ".svn". we shouldn't need |
| ### to use join_many since we know "/" is the separator for |
| ### internal canonical paths */ |
| pdh->base_dir = svn_dirent_join(path, ".svn/pristine", db->state_pool); |
| |
| /* ### parent */ |
| |
| apr_hash_set(db->dir_data, pdh->local_abspath, APR_HASH_KEY_STRING, pdh); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_wc__db_t * |
| new_db_state(svn_wc__db_openmode_t mode, |
| svn_config_t *config, |
| apr_pool_t *result_pool) |
| { |
| svn_wc__db_t *db = apr_palloc(result_pool, sizeof(*db)); |
| |
| db->mode = mode; |
| db->config = config; |
| db->dir_data = apr_hash_make(result_pool); |
| db->state_pool = result_pool; |
| |
| return db; |
| } |
| |
| |
| static svn_error_t * |
| fetch_repos_info(const char **repos_root_url, |
| const char **repos_uuid, |
| svn_sqlite__db_t *sdb, |
| 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, sdb, |
| 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_CORRUPT, NULL, |
| _("No REPOSITORY table entry for id '%ld'"), |
| (long int)repos_id); |
| |
| if (repos_root_url) |
| *repos_root_url = svn_sqlite__column_text(stmt, 0, result_pool); |
| if (repos_uuid) |
| *repos_uuid = svn_sqlite__column_text(stmt, 1, result_pool); |
| |
| return svn_sqlite__reset(stmt); |
| } |
| |
| |
| /* Scan from RELPATH upwards through parent nodes until we find a parent |
| that has values in the 'repos_id' and 'repos_relpath' columns. Return |
| that information in REPOS_ID and REPOS_RELPATH (either may be NULL). */ |
| static svn_error_t * |
| scan_upwards_for_repos(apr_int64_t *repos_id, |
| const char **repos_relpath, |
| apr_int64_t wc_id, |
| const char *relpath, |
| svn_sqlite__db_t *sdb, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| const char *relpath_suffix = ""; |
| const char *current_relpath = relpath; |
| svn_sqlite__stmt_t *stmt; |
| |
| SVN_ERR_ASSERT(repos_id != NULL || repos_relpath != NULL); |
| |
| /* ### is it faster to fetch fewer columns? */ |
| SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_SELECT_BASE_NODE)); |
| |
| while (TRUE) |
| { |
| const char *current_basename; |
| svn_boolean_t have_row; |
| |
| /* Strip a path segment off the end, and append it to the suffix |
| that we'll use when we finally find a base relpath. */ |
| svn_dirent_split(current_relpath, ¤t_relpath, ¤t_basename, |
| scratch_pool); |
| relpath_suffix = svn_dirent_join(relpath_suffix, current_basename, |
| scratch_pool); |
| |
| /* ### strictly speaking, moving to the parent could send us to a |
| ### different SDB, and (thus) we would need to fetch STMT again. |
| ### but we happen to know the parent is *always* in the same db. */ |
| |
| /* Rebind the statement to fetch parent information. */ |
| SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, current_relpath)); |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| |
| if (!have_row) |
| return svn_error_createf( |
| SVN_ERR_WC_CORRUPT, NULL, |
| _("Parent(s) of '%s' should have been present."), |
| svn_dirent_local_style(relpath, scratch_pool)); |
| |
| /* Did we find some non-NULL repository columns? */ |
| if (!svn_sqlite__column_is_null(stmt, 2)) |
| { |
| /* If one is non-NULL, then so should the other. */ |
| SVN_ERR_ASSERT(!svn_sqlite__column_is_null(stmt, 3)); |
| |
| if (repos_id) |
| *repos_id = svn_sqlite__column_int64(stmt, 2); |
| |
| /* Given the parent's relpath, append all the segments that |
| we stripped as we scanned upwards. */ |
| if (repos_relpath) |
| *repos_relpath = svn_dirent_join(svn_sqlite__column_text(stmt, 3, |
| NULL), |
| relpath_suffix, |
| result_pool); |
| return svn_sqlite__reset(stmt); |
| } |
| SVN_ERR(svn_sqlite__reset(stmt)); |
| |
| if (*current_relpath == '\0') |
| { |
| /* We scanned all the way up, and did not find the information. |
| Something is corrupt in the database. */ |
| return svn_error_createf( |
| SVN_ERR_WC_CORRUPT, NULL, |
| _("Parent(s) of '%s' should have repository information."), |
| svn_dirent_local_style(relpath, scratch_pool)); |
| } |
| |
| /* Loop to move further upwards. */ |
| } |
| } |
| |
| |
| /* For a given LOCAL_ABSPATH, figure out what sqlite database (SDB) to use, |
| what WC_ID is implied, and the RELPATH within that wcroot. If a sqlite |
| database needs to be opened, then use SMODE for it. */ |
| static svn_error_t * |
| parse_local_abspath(svn_wc__db_pdh_t **pdh, |
| const char **local_relpath, |
| svn_wc__db_t *db, |
| const char *local_abspath, |
| svn_sqlite__mode_t smode, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| const char *original_abspath = local_abspath; |
| svn_node_kind_t kind; |
| svn_boolean_t special; |
| const char *build_relpath; |
| const char *pdh_relpath; |
| svn_wc__db_pdh_t *found_pdh = NULL; |
| svn_wc__db_pdh_t *child_pdh; |
| svn_boolean_t obstruction_possible = FALSE; |
| |
| /* ### we need more logic for finding the database (if it is located |
| ### outside of the wcroot) and then managing all of that within DB. |
| ### for now: play quick & dirty. */ |
| |
| /* ### for now, overwrite the provided mode. We currently cache the |
| ### sdb handles, which is great but for the occasion where we |
| ### initially open the sdb in readonly mode and then later want |
| ### to write to it. The solution is to reopen the db in readwrite |
| ### mode, but that assumes we can track the fact that it was |
| ### originally opened readonly. So for now, just punt and open |
| ### everything in readwrite mode. */ |
| smode = svn_sqlite__mode_readwrite; |
| |
| *pdh = apr_hash_get(db->dir_data, local_abspath, APR_HASH_KEY_STRING); |
| if (*pdh != NULL) |
| { |
| /* We got lucky. Just return the thing BEFORE performing any I/O. */ |
| /* ### validate SMODE against how we opened pdh->sdb? and against |
| ### DB->mode? (will we record per-dir mode?) */ |
| /* ### what if the whole structure is not (yet) filled in? */ |
| |
| *local_relpath = apr_pstrdup(result_pool, (*pdh)->local_relpath); |
| return SVN_NO_ERROR; |
| } |
| |
| /* ### at some point in the future, we may need to find a way to get |
| ### rid of this stat() call. it is going to happen for EVERY call |
| ### into wc_db which references a file. calls for directories could |
| ### get an early-exit in the hash lookup just above. */ |
| SVN_ERR(svn_io_check_special_path(local_abspath, &kind, |
| &special /* unused */, scratch_pool)); |
| if (kind != svn_node_dir) |
| { |
| /* If the node specified by the path is NOT present, then it cannot |
| possibly be a directory containing ".svn/wc.db". |
| |
| If it is a file, then it cannot contain ".svn/wc.db". |
| |
| For both of these cases, strip the basename off of the path and |
| move up one level. Keep record of what we strip, though, since |
| we'll need it later to construct local_relpath. */ |
| svn_dirent_split(local_abspath, &local_abspath, &build_relpath, |
| scratch_pool); |
| |
| /* Is this directory in our hash? */ |
| *pdh = apr_hash_get(db->dir_data, local_abspath, APR_HASH_KEY_STRING); |
| if (*pdh != NULL) |
| { |
| /* Stashed directory's local_relpath + basename. */ |
| *local_relpath = svn_dirent_join((*pdh)->local_relpath, |
| build_relpath, |
| result_pool); |
| return SVN_NO_ERROR; |
| } |
| } |
| else |
| { |
| /* Start the local_relpath empty. If *this* directory contains the |
| wc.db, then relpath will be the empty string. */ |
| build_relpath = ""; |
| |
| /* It is possible that LOCAL_ABSPATH was *intended* to be a file, |
| but we just found a directory in its place. After we build |
| the PDH, then we'll examine the parent to see how it describes |
| this particular path. |
| |
| ### this is only possible with per-dir wc.db databases. */ |
| obstruction_possible = TRUE; |
| } |
| |
| /* The local_relpath that we put into the PDH starts empty. */ |
| pdh_relpath = ""; |
| |
| /* The PDH corresponding to the directory LOCAL_ABSPATH is what we need |
| to return. At this point, we've determined that it is NOT in the DB's |
| hash table of wcdirs. Let's create it, and begin to populate it. */ |
| |
| *pdh = apr_pcalloc(db->state_pool, sizeof(**pdh)); |
| (*pdh)->db = db; |
| (*pdh)->local_abspath = apr_pstrdup(db->state_pool, local_abspath); |
| |
| /* Assume that LOCAL_ABSPATH is a directory, and look for the SQLite |
| database in the right place. If we find it... great! If not, then |
| peel off some components, and try again. */ |
| |
| while (TRUE) |
| { |
| svn_error_t *err; |
| const char *base; |
| |
| err = svn_sqlite__open(&(*pdh)->sdb, |
| svn_wc__adm_child(local_abspath, "wc.db", |
| scratch_pool), |
| smode, statements, |
| SVN_WC__VERSION_EXPERIMENTAL, upgrade_sql, |
| db->state_pool, scratch_pool); |
| if (err == NULL) |
| break; |
| if (err->apr_err != SVN_ERR_SQLITE_ERROR |
| && !APR_STATUS_IS_ENOENT(err->apr_err)) |
| return err; |
| svn_error_clear(err); |
| |
| /* We couldn't open the SDB within the specified directory, so |
| move up one more directory. */ |
| base = svn_dirent_basename(local_abspath, scratch_pool); |
| if (*base == '\0') |
| { |
| /* Hit the root without finding a wcroot. */ |
| return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL, |
| _("'%s' is not a working copy"), |
| svn_dirent_local_style(original_abspath, |
| scratch_pool)); |
| } |
| |
| build_relpath = svn_dirent_join(base, build_relpath, scratch_pool); |
| pdh_relpath = svn_dirent_join(base, pdh_relpath, scratch_pool); |
| local_abspath = svn_dirent_dirname(local_abspath, scratch_pool); |
| |
| /* An obstruction is no longer possible. |
| |
| Example: we were given "/some/file" and "file" turned out to be |
| a directory. We did not find an SDB at "/some/file/.svn/wc.db", |
| so we are now going to look at "/some/.svn/wc.db". That SDB will |
| contain the correct information for "file". |
| |
| ### obstruction is only possible with per-dir wc.db databases. */ |
| obstruction_possible = FALSE; |
| |
| /* Is the parent directory recorded in our hash? */ |
| found_pdh = apr_hash_get(db->dir_data, |
| local_abspath, APR_HASH_KEY_STRING); |
| if (found_pdh != NULL) |
| break; |
| } |
| |
| if (found_pdh != NULL) |
| { |
| /* We found a PDH with data in it. We can now construct the child |
| from this, rather than continuing to scan upwards. */ |
| |
| /* The subdirectory's relpath is a join of the parent's plus what |
| we've stripped off the input so far. */ |
| (*pdh)->local_relpath = svn_dirent_join(found_pdh->local_relpath, |
| pdh_relpath, |
| db->state_pool); |
| |
| /* And the result local_relpath may include a filename. */ |
| *local_relpath = svn_dirent_join(found_pdh->local_relpath, |
| build_relpath, |
| result_pool); |
| |
| /* The subdirectory uses the same SDB and WC_ID as the parent dir. */ |
| (*pdh)->sdb = found_pdh->sdb; |
| (*pdh)->wc_id = found_pdh->wc_id; |
| (*pdh)->wcroot_abspath = found_pdh->wcroot_abspath; |
| } |
| else |
| { |
| /* We finally found the database. Construct the PDH record. */ |
| |
| svn_sqlite__stmt_t *stmt; |
| svn_boolean_t have_row; |
| |
| (*pdh)->local_relpath = apr_pstrdup(db->state_pool, pdh_relpath); |
| *local_relpath = apr_pstrdup(result_pool, build_relpath); |
| |
| /* ### cheat. we know there is just one WORKING_COPY row, and it has a |
| ### NULL value for local_abspath. */ |
| SVN_ERR(svn_sqlite__get_statement(&stmt, (*pdh)->sdb, |
| STMT_SELECT_WCROOT_NULL)); |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| if (!have_row) |
| return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, |
| _("Missing a row in WCROOT for '%s'."), |
| svn_dirent_local_style(original_abspath, |
| scratch_pool)); |
| |
| SVN_ERR_ASSERT(!svn_sqlite__column_is_null(stmt, 0)); |
| (*pdh)->wc_id = svn_sqlite__column_int64(stmt, 0); |
| |
| /* WCROOT.local_abspath may be NULL when the database is stored |
| inside the wcroot, but we know the abspath is this directory |
| (ie. where we found it). */ |
| (*pdh)->wcroot_abspath = apr_pstrdup(db->state_pool, local_abspath); |
| |
| SVN_ERR(svn_sqlite__reset(stmt)); |
| } |
| |
| /* Check to see if this (versioned) directory is obstructing what should |
| be a file in the parent directory. |
| |
| ### obstruction is only possible with per-dir wc.db databases. */ |
| if (obstruction_possible) |
| { |
| const char *parent_dir; |
| svn_wc__db_pdh_t *parent_pdh; |
| |
| assert(strcmp((*pdh)->local_abspath, local_abspath) == 0); |
| assert(original_abspath == local_abspath); |
| |
| parent_dir = svn_dirent_dirname(local_abspath, scratch_pool); |
| parent_pdh = apr_hash_get(db->dir_data, parent_dir, APR_HASH_KEY_STRING); |
| if (parent_pdh == NULL) |
| { |
| svn_sqlite__db_t *sdb; |
| svn_error_t *err = svn_sqlite__open(&sdb, |
| svn_wc__adm_child(parent_dir, |
| "wc.db", |
| scratch_pool), |
| smode, statements, |
| SVN_WC__VERSION_EXPERIMENTAL, |
| upgrade_sql, |
| db->state_pool, scratch_pool); |
| if (err) |
| { |
| if (err->apr_err != SVN_ERR_SQLITE_ERROR |
| && !APR_STATUS_IS_ENOENT(err->apr_err)) |
| return err; |
| svn_error_clear(err); |
| |
| /* No parent, so we're at a wcroot apparently. An obstruction |
| is (therefore) not possible. */ |
| } |
| else |
| { |
| /* ### construct this according to per-dir semantics. */ |
| parent_pdh = apr_pcalloc(db->state_pool, sizeof(*parent_pdh)); |
| parent_pdh->db = db; |
| parent_pdh->local_abspath = apr_pstrdup(db->state_pool, |
| parent_dir); |
| parent_pdh->local_relpath = ""; |
| parent_pdh->sdb = sdb; |
| parent_pdh->wc_id = 1; /* ### we know sqlite assigns 1. */ |
| parent_pdh->wcroot_abspath = parent_pdh->local_abspath; |
| |
| apr_hash_set(db->dir_data, |
| parent_pdh->local_abspath, APR_HASH_KEY_STRING, |
| parent_pdh); |
| |
| (*pdh)->parent = parent_pdh; |
| } |
| } |
| |
| if (parent_pdh) |
| { |
| const char *lookfor_relpath = svn_dirent_basename(local_abspath, |
| scratch_pool); |
| svn_sqlite__stmt_t *stmt; |
| svn_boolean_t have_row; |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, parent_pdh->sdb, |
| STMT_SELECT_WORKING_IS_FILE)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "is", |
| parent_pdh->wc_id, lookfor_relpath)); |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| if (have_row) |
| { |
| (*pdh)->obstructed_file = svn_sqlite__column_boolean(stmt, 0); |
| } |
| else |
| { |
| SVN_ERR(svn_sqlite__reset(stmt)); |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, parent_pdh->sdb, |
| STMT_SELECT_BASE_IS_FILE)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "is", |
| parent_pdh->wc_id, lookfor_relpath)); |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| if (have_row) |
| (*pdh)->obstructed_file = svn_sqlite__column_boolean(stmt, 0); |
| } |
| SVN_ERR(svn_sqlite__reset(stmt)); |
| |
| /* If we determined that a file was supposed to be at the |
| LOCAL_ABSPATH requested, then return the PDH and LOCAL_RELPATH |
| which describes that file. */ |
| if ((*pdh)->obstructed_file) |
| { |
| *pdh = parent_pdh; |
| *local_relpath = apr_pstrdup(result_pool, lookfor_relpath); |
| return SVN_NO_ERROR; |
| } |
| } |
| } |
| |
| /* The PDH is complete. Stash it into DB. */ |
| apr_hash_set(db->dir_data, |
| (*pdh)->local_abspath, APR_HASH_KEY_STRING, |
| *pdh); |
| |
| /* Did we traverse up to parent directories? |
| |
| Note that if found_pdh is non-NULL, then the second part of this |
| condition is also true -- found_pdh is just a quick way to avoid |
| a string compare. */ |
| if (found_pdh == NULL && strcmp(local_abspath, (*pdh)->local_abspath) == 0) |
| { |
| /* We did not move to a parent of the original requested directory. |
| We've constructed and filled in a PDH for the request, so we |
| are done. */ |
| return SVN_NO_ERROR; |
| } |
| |
| /* The PDH that we just built was for the LOCAL_ABSPATH originally passed |
| into this function. We stepped *at least* one directory above that. |
| We should now create PDH records for each parent directory that does |
| not (yet) have one. */ |
| |
| child_pdh = *pdh; |
| |
| do |
| { |
| const char *parent_dir = svn_dirent_dirname(child_pdh->local_abspath, |
| scratch_pool); |
| svn_wc__db_pdh_t *parent_pdh; |
| |
| parent_pdh = apr_hash_get(db->dir_data, parent_dir, APR_HASH_KEY_STRING); |
| if (parent_pdh == NULL) |
| { |
| parent_pdh = apr_pcalloc(db->state_pool, sizeof(*parent_pdh)); |
| parent_pdh->db = db; |
| parent_pdh->local_abspath = apr_pstrdup(db->state_pool, parent_dir); |
| parent_pdh->local_relpath = |
| svn_dirent_dirname(child_pdh->local_relpath, db->state_pool); |
| parent_pdh->sdb = child_pdh->sdb; |
| parent_pdh->wc_id = child_pdh->wc_id; |
| parent_pdh->wcroot_abspath = child_pdh->wcroot_abspath; |
| |
| apr_hash_set(db->dir_data, |
| parent_pdh->local_abspath, APR_HASH_KEY_STRING, |
| parent_pdh); |
| } |
| |
| /* Point the child PDH at this (new) parent PDH. This will allow for |
| easy traversals without path munging. */ |
| child_pdh->parent = parent_pdh; |
| child_pdh = parent_pdh; |
| |
| /* Loop if we haven't reached the PDH we found, or the abspath |
| where we terminated the search (when we found wc.db). */ |
| } |
| while (child_pdh != found_pdh |
| && strcmp(child_pdh->local_abspath, local_abspath) != 0); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Get the statement given by STMT_IDX, and bind the appropriate wc_id and |
| local_relpath based upon LOCAL_ABSPATH. Store it in *STMT, and use |
| SCRATCH_POOL for temporary allocations. |
| |
| Note: WC_ID and LOCAL_RELPATH must be arguments 1 and 2 in the statement. */ |
| static svn_error_t * |
| get_statement_for_path(svn_sqlite__stmt_t **stmt, |
| svn_wc__db_t *db, |
| const char *local_abspath, |
| int stmt_idx, |
| apr_pool_t *scratch_pool) |
| { |
| svn_wc__db_pdh_t *pdh; |
| const char *local_relpath; |
| |
| SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); |
| |
| SVN_ERR(parse_local_abspath(&pdh, &local_relpath, db, local_abspath, |
| svn_sqlite__mode_readwrite, |
| scratch_pool, scratch_pool)); |
| |
| SVN_ERR(svn_sqlite__get_statement(stmt, pdh->sdb, stmt_idx)); |
| SVN_ERR(svn_sqlite__bindf(*stmt, "is", pdh->wc_id, local_relpath)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t * |
| navigate_to_parent(svn_wc__db_pdh_t **parent_pdh, |
| svn_wc__db_pdh_t *child_pdh, |
| svn_sqlite__mode_t smode, |
| apr_pool_t *scratch_pool) |
| { |
| const char *parent_abspath; |
| const char *local_relpath; |
| |
| if ((*parent_pdh = child_pdh->parent) != NULL) |
| return SVN_NO_ERROR; |
| |
| parent_abspath = svn_dirent_dirname(child_pdh->local_abspath, scratch_pool); |
| SVN_ERR(parse_local_abspath(parent_pdh, &local_relpath, child_pdh->db, |
| parent_abspath, smode, |
| scratch_pool, scratch_pool)); |
| child_pdh->parent = *parent_pdh; |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* For a given REPOS_ROOT_URL/REPOS_UUID pair, return the existing REPOS_ID |
| value. If one does not exist, then create a new one. */ |
| static svn_error_t * |
| create_repos_id(apr_int64_t *repos_id, const char *repos_root_url, |
| const char *repos_uuid, svn_sqlite__db_t *sdb, |
| apr_pool_t *scratch_pool) |
| { |
| svn_sqlite__stmt_t *stmt; |
| svn_boolean_t have_row; |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_SELECT_REPOSITORY)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "s", repos_uuid)); |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| if (have_row) |
| { |
| *repos_id = svn_sqlite__column_int64(stmt, 0); |
| return svn_sqlite__reset(stmt); |
| } |
| SVN_ERR(svn_sqlite__reset(stmt)); |
| |
| /* NOTE: strictly speaking, there is a race condition between the |
| above query and the insertion below. We're simply going to ignore |
| that, as it means two processes are *modifying* the working copy |
| at the same time, *and* new repositores are becoming visible. |
| This is rare enough, let alone the miniscule chance of hitting |
| this race condition. Further, simply failing out will leave the |
| database in a consistent state, and the user can just re-run the |
| failed operation. */ |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_INSERT_REPOSITORY)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "ss", repos_root_url, repos_uuid)); |
| return svn_sqlite__insert(repos_id, stmt); |
| } |
| |
| |
| static svn_error_t * |
| insert_base_node(void *baton, svn_sqlite__db_t *sdb) |
| { |
| const insert_base_baton_t *pibb = baton; |
| apr_pool_t *scratch_pool = pibb->scratch_pool; |
| svn_sqlite__stmt_t *stmt; |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_INSERT_BASE_NODE)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "is", pibb->wc_id, pibb->local_relpath)); |
| |
| if (TRUE /* maybe_bind_repos() */) |
| { |
| SVN_ERR(svn_sqlite__bind_int64(stmt, 3, pibb->repos_id)); |
| SVN_ERR(svn_sqlite__bind_text(stmt, 4, pibb->repos_relpath)); |
| } |
| |
| /* The directory at the WCROOT has a NULL parent_relpath. Otherwise, |
| bind the appropriate parent_relpath. */ |
| if (*pibb->local_relpath != '\0') |
| SVN_ERR(svn_sqlite__bind_text(stmt, 5, |
| svn_dirent_dirname(pibb->local_relpath, |
| scratch_pool))); |
| |
| SVN_ERR(svn_sqlite__bind_text(stmt, 6, presence_to_word(pibb->status))); |
| SVN_ERR(svn_sqlite__bind_text(stmt, 7, kind_to_word(pibb->kind))); |
| SVN_ERR(svn_sqlite__bind_int64(stmt, 8, pibb->revision)); |
| |
| SVN_ERR(svn_sqlite__bind_properties(stmt, 9, pibb->props, scratch_pool)); |
| |
| if (SVN_IS_VALID_REVNUM(pibb->changed_rev)) |
| SVN_ERR(svn_sqlite__bind_int64(stmt, 10, pibb->changed_rev)); |
| if (pibb->changed_date) |
| SVN_ERR(svn_sqlite__bind_int64(stmt, 11, pibb->changed_date)); |
| if (pibb->changed_author) |
| SVN_ERR(svn_sqlite__bind_text(stmt, 12, pibb->changed_author)); |
| |
| if (pibb->kind == svn_wc__db_kind_dir) |
| { |
| SVN_ERR(svn_sqlite__bind_text(stmt, 13, svn_depth_to_word(pibb->depth))); |
| } |
| else if (pibb->kind == svn_wc__db_kind_file) |
| { |
| SVN_ERR(svn_sqlite__bind_checksum(stmt, 14, pibb->checksum, |
| scratch_pool)); |
| if (pibb->translated_size != SVN_INVALID_FILESIZE) |
| SVN_ERR(svn_sqlite__bind_int64(stmt, 15, pibb->translated_size)); |
| } |
| else if (pibb->kind == svn_wc__db_kind_symlink) |
| { |
| if (pibb->target) |
| SVN_ERR(svn_sqlite__bind_text(stmt, 16, pibb->target)); |
| } |
| |
| SVN_ERR(svn_sqlite__bind_int64(stmt, 17, pibb->incomplete_children)); |
| |
| SVN_ERR(svn_sqlite__insert(NULL, stmt)); |
| |
| if (pibb->kind == svn_wc__db_kind_dir && pibb->children) |
| { |
| int i; |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, |
| STMT_INSERT_BASE_NODE_INCOMPLETE)); |
| |
| for (i = pibb->children->nelts; i--; ) |
| { |
| const char *name = APR_ARRAY_IDX(pibb->children, i, const char *); |
| |
| SVN_ERR(svn_sqlite__bindf(stmt, "issi", |
| pibb->wc_id, |
| svn_dirent_join(pibb->local_relpath, |
| name, |
| scratch_pool), |
| pibb->local_relpath, |
| pibb->revision)); |
| SVN_ERR(svn_sqlite__insert(NULL, stmt)); |
| } |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t * |
| gather_children(const apr_array_header_t **children, |
| enum statement_keys key, |
| svn_wc__db_t *db, |
| const char *local_abspath, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| svn_wc__db_pdh_t *pdh; |
| const char *local_relpath; |
| svn_sqlite__stmt_t *stmt; |
| apr_array_header_t *child_names; |
| svn_boolean_t have_row; |
| |
| SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); |
| |
| SVN_ERR(parse_local_abspath(&pdh, &local_relpath, db, local_abspath, |
| svn_sqlite__mode_readonly, |
| scratch_pool, scratch_pool)); |
| |
| /* ### should test the node to ensure it is a directory */ |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, pdh->sdb, key)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "is", pdh->wc_id, local_relpath)); |
| |
| child_names = apr_array_make(result_pool, 20, sizeof(const char *)); |
| |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| while (have_row) |
| { |
| const char *child_relpath = svn_sqlite__column_text(stmt, 0, NULL); |
| |
| APR_ARRAY_PUSH(child_names, const char *) = |
| svn_dirent_basename(child_relpath, result_pool); |
| |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| } |
| |
| *children = child_names; |
| |
| return svn_sqlite__reset(stmt); |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_open(svn_wc__db_t **db, |
| svn_wc__db_openmode_t mode, |
| const char *local_abspath, |
| svn_config_t *config, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| *db = new_db_state(mode, config, result_pool); |
| |
| /* ### open_one_directory() doesn't fill in SDB and other data. for now, |
| ### we want that in all structures, so we don't have to do on-demand |
| ### searching/opening when we already have a PDH. */ |
| #if 0 |
| return open_one_directory(*db, local_abspath, scratch_pool); |
| #else |
| return SVN_NO_ERROR; |
| #endif |
| } |
| |
| |
| 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 == NULL) |
| return SVN_NO_ERROR; |
| if (err->apr_err != SVN_ERR_SQLITE_ERROR |
| && !APR_STATUS_IS_ENOENT(err->apr_err)) |
| return err; |
| svn_error_clear(err); |
| |
| /* Hmm, that didn't work. Now try reading the format number from the |
| entries file. */ |
| 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 == NULL) |
| return SVN_NO_ERROR; |
| if (err->apr_err != SVN_ERR_BAD_VERSION_FILE_FORMAT) |
| return svn_error_createf(SVN_ERR_WC_MISSING, err, _("'%s' does not exist"), |
| svn_dirent_local_style(path, scratch_pool)); |
| svn_error_clear(err); |
| |
| /* Wow, another error; this must be a really old working copy! Fall back |
| to reading the format file. */ |
| /* 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 == NULL) |
| return SVN_NO_ERROR; |
| if (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_dirent_local_style(path, scratch_pool)); |
| svn_error_clear(err); |
| |
| /* 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_dirent_local_style(path, scratch_pool)); |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_close(svn_wc__db_t *db, |
| apr_pool_t *scratch_pool) |
| { |
| apr_hash_t *sdbs = apr_hash_make(scratch_pool); |
| apr_hash_index_t *hi; |
| |
| /* We may have sdbs shared between pdhs, so put them all in a hash to |
| collapse them, validating along the way. */ |
| for (hi = apr_hash_first(scratch_pool, db->dir_data); hi; |
| hi = apr_hash_next(hi)) |
| { |
| void *val; |
| svn_wc__db_pdh_t *pdh; |
| |
| apr_hash_this(hi, NULL, NULL, &val); |
| pdh = val; |
| |
| #ifdef SVN_DEBUG |
| /* If two PDH records have the same wcroot_abspath, then they should |
| be using the same SDB handle. */ |
| { |
| svn_sqlite__db_t *existing_sdb = apr_hash_get(sdbs, |
| pdh->wcroot_abspath, |
| APR_HASH_KEY_STRING); |
| if (existing_sdb) |
| SVN_ERR_ASSERT(existing_sdb == pdh->sdb); |
| } |
| #endif |
| |
| apr_hash_set(sdbs, pdh->wcroot_abspath, APR_HASH_KEY_STRING, pdh->sdb); |
| } |
| |
| /* ### it would also be nice to assert that two different wcroot_abspath |
| ### values are not sharing the same SDB. If they *do*, then we will |
| ### double-close below. That won't cause problems, but it does |
| ### represent an internal consistency error. */ |
| |
| /* Now close all of the non-duplicate databases. */ |
| for (hi = apr_hash_first(scratch_pool, sdbs); hi; hi = apr_hash_next(hi)) |
| { |
| void *val; |
| |
| apr_hash_this(hi, NULL, NULL, &val); |
| |
| SVN_ERR(svn_sqlite__close(val)); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_init(const char *local_abspath, |
| const char *repos_relpath, |
| const char *repos_root_url, |
| const char *repos_uuid, |
| svn_revnum_t initial_rev, |
| svn_depth_t depth, |
| apr_pool_t *scratch_pool) |
| { |
| svn_sqlite__db_t *sdb; |
| svn_sqlite__stmt_t *stmt; |
| apr_int64_t repos_id; |
| apr_int64_t wc_id; |
| insert_base_baton_t ibb; |
| |
| SVN_ERR(svn_sqlite__open(&sdb, |
| svn_wc__adm_child(local_abspath, "wc.db", |
| scratch_pool), |
| svn_sqlite__mode_rwcreate, statements, |
| SVN_WC__VERSION_EXPERIMENTAL, upgrade_sql, |
| scratch_pool, scratch_pool)); |
| |
| /* Insert the repository. */ |
| SVN_ERR(create_repos_id(&repos_id, repos_root_url, repos_uuid, sdb, |
| scratch_pool)); |
| |
| /* Insert the wcroot. */ |
| /* ### Right now, this just assumes wc metadata is being stored locally. */ |
| SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_INSERT_WCROOT)); |
| SVN_ERR(svn_sqlite__insert(&wc_id, stmt)); |
| |
| ibb.status = svn_wc__db_status_normal; |
| ibb.kind = svn_wc__db_kind_dir; |
| ibb.wc_id = wc_id; |
| ibb.local_relpath = ""; |
| ibb.repos_id = repos_id; |
| ibb.repos_relpath = repos_relpath; |
| ibb.revision = initial_rev; |
| |
| ibb.props = NULL; |
| ibb.changed_rev = SVN_INVALID_REVNUM; |
| ibb.changed_date = 0; |
| ibb.changed_author = NULL; |
| |
| ibb.children = NULL; |
| ibb.incomplete_children = initial_rev > 0; |
| ibb.depth = depth; |
| |
| ibb.scratch_pool = scratch_pool; |
| |
| SVN_ERR(insert_base_node(&ibb, sdb)); |
| |
| return svn_sqlite__close(sdb); |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_base_add_directory(svn_wc__db_t *db, |
| const char *local_abspath, |
| const char *repos_relpath, |
| const char *repos_root_url, |
| const char *repos_uuid, |
| svn_revnum_t revision, |
| const apr_hash_t *props, |
| svn_revnum_t changed_rev, |
| apr_time_t changed_date, |
| const char *changed_author, |
| const apr_array_header_t *children, |
| svn_depth_t depth, |
| apr_pool_t *scratch_pool) |
| { |
| svn_wc__db_pdh_t *pdh; |
| const char *local_relpath; |
| apr_int64_t repos_id; |
| insert_base_baton_t ibb; |
| |
| SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); |
| SVN_ERR_ASSERT(repos_relpath != NULL); |
| SVN_ERR_ASSERT(svn_uri_is_absolute(repos_root_url)); |
| SVN_ERR_ASSERT(repos_uuid != NULL); |
| SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision)); |
| SVN_ERR_ASSERT(props != NULL); |
| SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(changed_rev)); |
| SVN_ERR_ASSERT(children != NULL); |
| |
| SVN_ERR(parse_local_abspath(&pdh, &local_relpath, db, local_abspath, |
| svn_sqlite__mode_readwrite, |
| scratch_pool, scratch_pool)); |
| |
| SVN_ERR(create_repos_id(&repos_id, repos_root_url, repos_uuid, pdh->sdb, |
| scratch_pool)); |
| |
| ibb.status = svn_wc__db_status_normal; |
| ibb.kind = svn_wc__db_kind_dir; |
| ibb.wc_id = pdh->wc_id; |
| ibb.local_relpath = local_relpath; |
| ibb.repos_id = repos_id; |
| ibb.repos_relpath = repos_relpath; |
| ibb.revision = revision; |
| |
| ibb.props = props; |
| ibb.changed_rev = changed_rev; |
| ibb.changed_date = changed_date; |
| ibb.changed_author = changed_author; |
| |
| ibb.children = children; |
| ibb.depth = depth; |
| ibb.incomplete_children = FALSE; |
| |
| ibb.scratch_pool = scratch_pool; |
| |
| /* Insert the directory and all its children transactionally. |
| |
| Note: old children can stick around, even if they are no longer present |
| in this directory's revision. */ |
| return svn_sqlite__with_transaction(pdh->sdb, insert_base_node, &ibb); |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_base_add_file(svn_wc__db_t *db, |
| const char *local_abspath, |
| const char *repos_relpath, |
| const char *repos_root_url, |
| const char *repos_uuid, |
| svn_revnum_t revision, |
| const apr_hash_t *props, |
| svn_revnum_t changed_rev, |
| apr_time_t changed_date, |
| const char *changed_author, |
| const svn_checksum_t *checksum, |
| svn_filesize_t translated_size, |
| apr_pool_t *scratch_pool) |
| { |
| svn_wc__db_pdh_t *pdh; |
| const char *local_relpath; |
| apr_int64_t repos_id; |
| insert_base_baton_t ibb; |
| |
| SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); |
| SVN_ERR_ASSERT(repos_relpath != NULL); |
| SVN_ERR_ASSERT(svn_uri_is_absolute(repos_root_url)); |
| SVN_ERR_ASSERT(repos_uuid != NULL); |
| SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision)); |
| SVN_ERR_ASSERT(props != NULL); |
| SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(changed_rev)); |
| SVN_ERR_ASSERT(checksum != NULL); |
| |
| SVN_ERR(parse_local_abspath(&pdh, &local_relpath, db, local_abspath, |
| svn_sqlite__mode_readwrite, |
| scratch_pool, scratch_pool)); |
| |
| SVN_ERR(create_repos_id(&repos_id, repos_root_url, repos_uuid, pdh->sdb, |
| scratch_pool)); |
| |
| ibb.status = svn_wc__db_status_normal; |
| ibb.kind = svn_wc__db_kind_file; |
| ibb.wc_id = pdh->wc_id; |
| ibb.local_relpath = local_relpath; |
| ibb.repos_id = repos_id; |
| ibb.repos_relpath = repos_relpath; |
| ibb.revision = revision; |
| |
| ibb.props = props; |
| ibb.changed_rev = changed_rev; |
| ibb.changed_date = changed_date; |
| ibb.changed_author = changed_author; |
| |
| ibb.checksum = checksum; |
| ibb.translated_size = translated_size; |
| ibb.incomplete_children = FALSE; |
| |
| ibb.scratch_pool = scratch_pool; |
| |
| /* ### hmm. if this used to be a directory, we should remove children. |
| ### or maybe let caller deal with that, if there is a possibility |
| ### of a node kind change (rather than eat an extra lookup here). */ |
| |
| return insert_base_node(&ibb, pdh->sdb); |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_base_add_symlink(svn_wc__db_t *db, |
| const char *local_abspath, |
| const char *repos_relpath, |
| const char *repos_root_url, |
| const char *repos_uuid, |
| svn_revnum_t revision, |
| const apr_hash_t *props, |
| svn_revnum_t changed_rev, |
| apr_time_t changed_date, |
| const char *changed_author, |
| const char *target, |
| apr_pool_t *scratch_pool) |
| { |
| svn_wc__db_pdh_t *pdh; |
| const char *local_relpath; |
| apr_int64_t repos_id; |
| insert_base_baton_t ibb; |
| |
| SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); |
| SVN_ERR_ASSERT(repos_relpath != NULL); |
| SVN_ERR_ASSERT(svn_uri_is_absolute(repos_root_url)); |
| SVN_ERR_ASSERT(repos_uuid != NULL); |
| SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision)); |
| SVN_ERR_ASSERT(props != NULL); |
| SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(changed_rev)); |
| SVN_ERR_ASSERT(target != NULL); |
| |
| SVN_ERR(parse_local_abspath(&pdh, &local_relpath, db, local_abspath, |
| svn_sqlite__mode_readwrite, |
| scratch_pool, scratch_pool)); |
| |
| SVN_ERR(create_repos_id(&repos_id, repos_root_url, repos_uuid, pdh->sdb, |
| scratch_pool)); |
| |
| ibb.status = svn_wc__db_status_normal; |
| ibb.kind = svn_wc__db_kind_symlink; |
| ibb.wc_id = pdh->wc_id; |
| ibb.local_relpath = local_relpath; |
| ibb.repos_id = repos_id; |
| ibb.repos_relpath = repos_relpath; |
| ibb.revision = revision; |
| |
| ibb.props = props; |
| ibb.changed_rev = changed_rev; |
| ibb.changed_date = changed_date; |
| ibb.changed_author = changed_author; |
| |
| ibb.target = target; |
| ibb.incomplete_children = FALSE; |
| |
| ibb.scratch_pool = scratch_pool; |
| |
| /* ### hmm. if this used to be a directory, we should remove children. |
| ### or maybe let caller deal with that, if there is a possibility |
| ### of a node kind change (rather than eat an extra lookup here). */ |
| |
| return insert_base_node(&ibb, pdh->sdb); |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_base_add_absent_node(svn_wc__db_t *db, |
| const char *local_abspath, |
| const char *repos_relpath, |
| const char *repos_root_url, |
| const char *repos_uuid, |
| svn_revnum_t revision, |
| svn_wc__db_kind_t kind, |
| svn_wc__db_status_t status, |
| apr_pool_t *scratch_pool) |
| { |
| svn_wc__db_pdh_t *pdh; |
| const char *local_relpath; |
| apr_int64_t repos_id; |
| insert_base_baton_t ibb; |
| |
| SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); |
| SVN_ERR_ASSERT(repos_relpath != NULL); |
| SVN_ERR_ASSERT(svn_uri_is_absolute(repos_root_url)); |
| SVN_ERR_ASSERT(repos_uuid != NULL); |
| SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision)); |
| SVN_ERR_ASSERT(status == svn_wc__db_status_absent |
| || status == svn_wc__db_status_excluded |
| || status == svn_wc__db_status_not_present); |
| |
| SVN_ERR(parse_local_abspath(&pdh, &local_relpath, db, local_abspath, |
| svn_sqlite__mode_readwrite, |
| scratch_pool, scratch_pool)); |
| |
| SVN_ERR(create_repos_id(&repos_id, repos_root_url, repos_uuid, pdh->sdb, |
| scratch_pool)); |
| |
| ibb.status = status; |
| ibb.kind = kind; |
| ibb.wc_id = pdh->wc_id; |
| ibb.local_relpath = local_relpath; |
| ibb.repos_id = repos_id; |
| ibb.repos_relpath = repos_relpath; |
| ibb.revision = revision; |
| |
| ibb.props = NULL; |
| ibb.changed_rev = SVN_INVALID_REVNUM; |
| ibb.changed_date = 0; |
| ibb.changed_author = NULL; |
| |
| /* Depending upon KIND, any of these might get used. */ |
| ibb.children = NULL; |
| ibb.depth = svn_depth_unknown; |
| ibb.checksum = NULL; |
| ibb.translated_size = SVN_INVALID_FILESIZE; |
| ibb.target = NULL; |
| ibb.incomplete_children = FALSE; |
| |
| ibb.scratch_pool = scratch_pool; |
| |
| /* ### hmm. if this used to be a directory, we should remove children. |
| ### or maybe let caller deal with that, if there is a possibility |
| ### of a node kind change (rather than eat an extra lookup here). */ |
| |
| return insert_base_node(&ibb, pdh->sdb); |
| } |
| |
| |
| /* ### temp API. Remove before release. */ |
| svn_error_t * |
| svn_wc__db_temp_base_add_subdir(svn_wc__db_t *db, |
| const char *local_abspath, |
| const char *repos_relpath, |
| const char *repos_root_url, |
| const char *repos_uuid, |
| svn_revnum_t revision, |
| const apr_hash_t *props, |
| svn_revnum_t changed_rev, |
| apr_time_t changed_date, |
| const char *changed_author, |
| svn_depth_t depth, |
| apr_pool_t *scratch_pool) |
| { |
| svn_wc__db_pdh_t *pdh; |
| const char *local_relpath; |
| apr_int64_t repos_id; |
| insert_base_baton_t ibb; |
| |
| SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); |
| SVN_ERR_ASSERT(repos_relpath != NULL); |
| SVN_ERR_ASSERT(svn_uri_is_absolute(repos_root_url)); |
| SVN_ERR_ASSERT(repos_uuid != NULL); |
| SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision)); |
| SVN_ERR_ASSERT(props != NULL); |
| SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(changed_rev)); |
| |
| SVN_ERR(parse_local_abspath(&pdh, &local_relpath, db, local_abspath, |
| svn_sqlite__mode_readwrite, |
| scratch_pool, scratch_pool)); |
| |
| SVN_ERR(create_repos_id(&repos_id, repos_root_url, repos_uuid, pdh->sdb, |
| scratch_pool)); |
| |
| ibb.status = svn_wc__db_status_normal; |
| ibb.kind = svn_wc__db_kind_subdir; |
| ibb.wc_id = pdh->wc_id; |
| ibb.local_relpath = local_relpath; |
| ibb.repos_id = repos_id; |
| ibb.repos_relpath = repos_relpath; |
| ibb.revision = revision; |
| |
| ibb.props = NULL; |
| ibb.changed_rev = changed_rev; |
| ibb.changed_date = changed_date; |
| ibb.changed_author = changed_author; |
| ibb.incomplete_children = FALSE; |
| |
| ibb.children = NULL; |
| ibb.depth = depth; |
| |
| ibb.scratch_pool = scratch_pool; |
| |
| return insert_base_node(&ibb, pdh->sdb); |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_base_remove(svn_wc__db_t *db, |
| const char *local_abspath, |
| apr_pool_t *scratch_pool) |
| { |
| svn_wc__db_pdh_t *pdh; |
| const char *local_relpath; |
| |
| SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); |
| |
| SVN_ERR(parse_local_abspath(&pdh, &local_relpath, db, local_abspath, |
| svn_sqlite__mode_readwrite, |
| scratch_pool, scratch_pool)); |
| |
| NOT_IMPLEMENTED(); |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_base_get_info(svn_wc__db_status_t *status, |
| svn_wc__db_kind_t *kind, |
| svn_revnum_t *revision, |
| const char **repos_relpath, |
| const char **repos_root_url, |
| const char **repos_uuid, |
| svn_revnum_t *changed_rev, |
| apr_time_t *changed_date, |
| const char **changed_author, |
| apr_time_t *last_mod_time, |
| svn_depth_t *depth, |
| svn_checksum_t **checksum, |
| svn_filesize_t *translated_size, |
| const char **target, |
| svn_wc__db_lock_t **lock, |
| svn_wc__db_t *db, |
| const char *local_abspath, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| svn_wc__db_pdh_t *pdh; |
| const char *local_relpath; |
| svn_sqlite__stmt_t *stmt; |
| svn_boolean_t have_row; |
| svn_error_t *err = SVN_NO_ERROR; |
| |
| SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); |
| |
| SVN_ERR(parse_local_abspath(&pdh, &local_relpath, db, local_abspath, |
| svn_sqlite__mode_readonly, |
| scratch_pool, scratch_pool)); |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, pdh->sdb, |
| lock ? STMT_SELECT_BASE_NODE_WITH_LOCK |
| : STMT_SELECT_BASE_NODE)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "is", pdh->wc_id, local_relpath)); |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| |
| if (have_row) |
| { |
| const char *kind_str = svn_sqlite__column_text(stmt, 5, NULL); |
| svn_wc__db_kind_t node_kind; |
| |
| SVN_ERR_ASSERT(kind_str != NULL); |
| node_kind = word_to_kind(kind_str); |
| |
| if (kind) |
| { |
| if (node_kind == svn_wc__db_kind_subdir) |
| *kind = svn_wc__db_kind_dir; |
| else |
| *kind = node_kind; |
| } |
| if (status) |
| { |
| const char *presence = svn_sqlite__column_text(stmt, 4, NULL); |
| |
| SVN_ERR_ASSERT(presence != NULL); |
| *status = word_to_presence(presence); |
| |
| if (node_kind == svn_wc__db_kind_subdir |
| && *status == svn_wc__db_status_normal) |
| { |
| /* We're looking at the subdir record in the *parent* directory, |
| which implies per-dir .svn subdirs. We should be looking |
| at the subdir itself; therefore, it is missing or obstructed |
| in some way. Inform the caller. */ |
| *status = svn_wc__db_status_obstructed; |
| } |
| } |
| if (revision) |
| { |
| *revision = svn_sqlite__column_revnum(stmt, 6); |
| } |
| if (repos_relpath) |
| { |
| *repos_relpath = svn_sqlite__column_text(stmt, 3, result_pool); |
| } |
| if (lock) |
| { |
| if (svn_sqlite__column_is_null(stmt, 15)) |
| { |
| *lock = NULL; |
| } |
| else |
| { |
| *lock = apr_pcalloc(result_pool, sizeof(svn_wc__db_lock_t)); |
| (*lock)->token = svn_sqlite__column_text(stmt, 15, result_pool); |
| if (!svn_sqlite__column_is_null(stmt, 16)) |
| (*lock)->owner = svn_sqlite__column_text(stmt, 16, |
| result_pool); |
| if (!svn_sqlite__column_is_null(stmt, 17)) |
| (*lock)->comment = svn_sqlite__column_text(stmt, 17, |
| result_pool); |
| if (!svn_sqlite__column_is_null(stmt, 18)) |
| (*lock)->date = svn_sqlite__column_int64(stmt, 18); |
| } |
| } |
| if (repos_root_url || repos_uuid) |
| { |
| /* Fetch repository information via REPOS_ID. */ |
| if (svn_sqlite__column_is_null(stmt, 2)) |
| { |
| if (repos_root_url) |
| *repos_root_url = NULL; |
| if (repos_uuid) |
| *repos_uuid = NULL; |
| } |
| else |
| { |
| err = fetch_repos_info(repos_root_url, repos_uuid, pdh->sdb, |
| svn_sqlite__column_int64(stmt, 2), |
| result_pool); |
| } |
| } |
| if (changed_rev) |
| { |
| *changed_rev = svn_sqlite__column_revnum(stmt, 9); |
| } |
| if (changed_date) |
| { |
| *changed_date = svn_sqlite__column_int64(stmt, 10); |
| } |
| if (changed_author) |
| { |
| /* Result may be NULL. */ |
| *changed_author = svn_sqlite__column_text(stmt, 11, result_pool); |
| } |
| if (last_mod_time) |
| { |
| *last_mod_time = svn_sqlite__column_int64(stmt, 14); |
| } |
| if (depth) |
| { |
| if (node_kind != svn_wc__db_kind_dir) |
| { |
| *depth = svn_depth_unknown; |
| } |
| else |
| { |
| const char *depth_str = svn_sqlite__column_text(stmt, 12, NULL); |
| |
| if (depth_str == NULL) |
| *depth = svn_depth_unknown; |
| else |
| *depth = svn_depth_from_word(depth_str); |
| } |
| } |
| if (checksum) |
| { |
| if (node_kind != svn_wc__db_kind_file) |
| { |
| *checksum = NULL; |
| } |
| else |
| { |
| err = svn_sqlite__column_checksum(checksum, stmt, 7, |
| result_pool); |
| if (err != NULL) |
| err = svn_error_createf( |
| err->apr_err, err, |
| _("The node '%s' has a corrupt checksum value."), |
| svn_dirent_local_style(local_abspath, scratch_pool)); |
| } |
| } |
| if (translated_size) |
| { |
| *translated_size = get_translated_size(stmt, 8); |
| } |
| if (target) |
| { |
| if (node_kind != svn_wc__db_kind_symlink) |
| *target = NULL; |
| else |
| *target = svn_sqlite__column_text(stmt, 13, result_pool); |
| } |
| } |
| else |
| { |
| err = svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, |
| _("The node '%s' was not found."), |
| svn_dirent_local_style(local_abspath, |
| scratch_pool)); |
| } |
| |
| return svn_error_compose_create(err, svn_sqlite__reset(stmt)); |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_base_get_prop(const svn_string_t **propval, |
| svn_wc__db_t *db, |
| const char *local_abspath, |
| const char *propname, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| apr_hash_t *props; |
| |
| SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); |
| SVN_ERR_ASSERT(propname != NULL); |
| |
| /* Note: maybe one day, we'll have internal caches of this stuff, but |
| for now, we just grab all the props and pick out the requested prop. */ |
| |
| /* ### should: fetch into scratch_pool, then dup into result_pool. */ |
| SVN_ERR(svn_wc__db_base_get_props(&props, db, local_abspath, |
| result_pool, scratch_pool)); |
| |
| *propval = apr_hash_get(props, propname, APR_HASH_KEY_STRING); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_base_get_props(apr_hash_t **props, |
| svn_wc__db_t *db, |
| const char *local_abspath, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| svn_sqlite__stmt_t *stmt; |
| svn_boolean_t have_row; |
| |
| SVN_ERR(get_statement_for_path(&stmt, db, local_abspath, |
| STMT_SELECT_BASE_PROPS, scratch_pool)); |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| if (!have_row) |
| return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, |
| _("The node '%s' was not found."), |
| svn_dirent_local_style(local_abspath, |
| scratch_pool)); |
| |
| SVN_ERR(svn_sqlite__column_properties(props, stmt, 0, result_pool, |
| scratch_pool)); |
| return svn_sqlite__reset(stmt); |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_base_get_children(const apr_array_header_t **children, |
| svn_wc__db_t *db, |
| const char *local_abspath, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| return gather_children(children, STMT_SELECT_BASE_NODE_CHILDREN, |
| db, local_abspath, result_pool, scratch_pool); |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_base_set_wcprops(svn_wc__db_t *db, |
| const char *local_abspath, |
| const apr_hash_t *props, |
| apr_pool_t *scratch_pool) |
| { |
| svn_sqlite__stmt_t *stmt; |
| |
| SVN_ERR(get_statement_for_path(&stmt, db, local_abspath, |
| STMT_UPDATE_BASE_WCPROPS, scratch_pool)); |
| SVN_ERR(svn_sqlite__bind_properties(stmt, 3, props, scratch_pool)); |
| |
| return svn_sqlite__step_done(stmt); |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_base_get_wcprops(apr_hash_t **props, |
| svn_wc__db_t *db, |
| const char *local_abspath, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| svn_sqlite__stmt_t *stmt; |
| svn_boolean_t have_row; |
| |
| SVN_ERR(get_statement_for_path(&stmt, db, local_abspath, |
| STMT_SELECT_BASE_WCPROPS, scratch_pool)); |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| if (!have_row) |
| return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, |
| _("The node '%s' was not found."), |
| svn_dirent_local_style(local_abspath, |
| scratch_pool)); |
| |
| SVN_ERR(svn_sqlite__column_properties(props, stmt, 0, result_pool, |
| scratch_pool)); |
| return svn_sqlite__reset(stmt); |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_pristine_get_handle(svn_wc__db_pdh_t **pdh, |
| svn_wc__db_t *db, |
| const char *local_dir_abspath, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| SVN_ERR_ASSERT(svn_dirent_is_absolute(local_dir_abspath)); |
| |
| /* ### need to fix this up. we'll probably get called with a subdirectory |
| ### of the path that we opened originally. that means we probably |
| ### won't have the subdir in the hash table. need to be able to |
| ### incrementally grow the hash of per-dir structures. */ |
| |
| *pdh = apr_hash_get(db->dir_data, local_dir_abspath, APR_HASH_KEY_STRING); |
| |
| if (*pdh == NULL) |
| { |
| /* Oops. We haven't seen this WC directory before. Let's get it into |
| our hash of per-directory information. */ |
| SVN_ERR(open_one_directory(db, local_dir_abspath, scratch_pool)); |
| |
| *pdh = apr_hash_get(db->dir_data, local_dir_abspath, APR_HASH_KEY_STRING); |
| |
| SVN_ERR_ASSERT(*pdh != NULL); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_pristine_read(svn_stream_t **contents, |
| svn_wc__db_pdh_t *pdh, |
| const svn_checksum_t *checksum, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| const char *path; |
| |
| SVN_ERR(get_pristine_fname(&path, pdh, checksum, FALSE /* create_subdir */, |
| scratch_pool, scratch_pool)); |
| |
| return svn_stream_open_readonly(contents, path, result_pool, scratch_pool); |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_pristine_write(svn_stream_t **contents, |
| svn_wc__db_pdh_t *pdh, |
| const svn_checksum_t *checksum, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| const char *path; |
| |
| SVN_ERR(get_pristine_fname(&path, pdh, checksum, TRUE /* create_subdir */, |
| scratch_pool, scratch_pool)); |
| |
| SVN_ERR(svn_stream_open_writable(contents, path, result_pool, scratch_pool)); |
| |
| /* ### we should wrap the stream. count the bytes. at close, then we |
| ### should write the count into the sqlite database. */ |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_pristine_get_tempdir(const char **temp_dir, |
| svn_wc__db_pdh_t *pdh, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| NOT_IMPLEMENTED(); |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_pristine_install(svn_wc__db_pdh_t *pdh, |
| const char *local_abspath, |
| const svn_checksum_t *checksum, |
| apr_pool_t *scratch_pool) |
| { |
| SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); |
| |
| NOT_IMPLEMENTED(); |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_pristine_check(svn_boolean_t *present, |
| int *refcount, |
| svn_wc__db_pdh_t *pdh, |
| const svn_checksum_t *checksum, |
| svn_wc__db_checkmode_t mode, |
| apr_pool_t *scratch_pool) |
| { |
| NOT_IMPLEMENTED(); |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_pristine_repair(svn_wc__db_pdh_t *pdh, |
| const svn_checksum_t *checksum, |
| apr_pool_t *scratch_pool) |
| { |
| NOT_IMPLEMENTED(); |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_pristine_incref(int *new_refcount, |
| svn_wc__db_pdh_t *pdh, |
| const svn_checksum_t *checksum, |
| apr_pool_t *scratch_pool) |
| { |
| NOT_IMPLEMENTED(); |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_pristine_decref(int *new_refcount, |
| svn_wc__db_pdh_t *pdh, |
| const svn_checksum_t *checksum, |
| apr_pool_t *scratch_pool) |
| { |
| NOT_IMPLEMENTED(); |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_op_copy(svn_wc__db_t *db, |
| const char *src_abspath, |
| const char *dst_abspath, |
| apr_pool_t *scratch_pool) |
| { |
| SVN_ERR_ASSERT(svn_dirent_is_absolute(src_abspath)); |
| SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath)); |
| |
| NOT_IMPLEMENTED(); |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_op_copy_url(svn_wc__db_t *db, |
| const char *local_abspath, |
| const char *copyfrom_repos_relpath, |
| const char *copyfrom_root_url, |
| const char *copyfrom_uuid, |
| svn_revnum_t copyfrom_revision, |
| apr_pool_t *scratch_pool) |
| { |
| SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); |
| SVN_ERR_ASSERT(copyfrom_repos_relpath != NULL); |
| SVN_ERR_ASSERT(svn_uri_is_absolute(copyfrom_root_url)); |
| SVN_ERR_ASSERT(copyfrom_uuid != NULL); |
| SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(copyfrom_revision)); |
| |
| NOT_IMPLEMENTED(); |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_op_add_directory(svn_wc__db_t *db, |
| const char *local_abspath, |
| apr_pool_t *scratch_pool) |
| { |
| SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); |
| |
| NOT_IMPLEMENTED(); |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_op_add_file(svn_wc__db_t *db, |
| const char *local_abspath, |
| apr_pool_t *scratch_pool) |
| { |
| SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); |
| |
| NOT_IMPLEMENTED(); |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_op_add_symlink(svn_wc__db_t *db, |
| const char *local_abspath, |
| const char *target, |
| apr_pool_t *scratch_pool) |
| { |
| SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); |
| SVN_ERR_ASSERT(target != NULL); |
| |
| NOT_IMPLEMENTED(); |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_op_set_props(svn_wc__db_t *db, |
| const char *local_abspath, |
| const apr_hash_t *props, |
| apr_pool_t *scratch_pool) |
| { |
| svn_sqlite__stmt_t *stmt; |
| |
| SVN_ERR(get_statement_for_path(&stmt, db, local_abspath, |
| STMT_UPDATE_ACTUAL_PROPS, scratch_pool)); |
| SVN_ERR(svn_sqlite__bind_properties(stmt, 3, props, scratch_pool)); |
| |
| return svn_sqlite__step_done(stmt); |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_op_delete(svn_wc__db_t *db, |
| const char *local_abspath, |
| apr_pool_t *scratch_pool) |
| { |
| SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); |
| |
| NOT_IMPLEMENTED(); |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_op_move(svn_wc__db_t *db, |
| const char *src_abspath, |
| const char *dst_abspath, |
| apr_pool_t *scratch_pool) |
| { |
| SVN_ERR_ASSERT(svn_dirent_is_absolute(src_abspath)); |
| SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath)); |
| |
| NOT_IMPLEMENTED(); |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_op_modified(svn_wc__db_t *db, |
| const char *local_abspath, |
| apr_pool_t *scratch_pool) |
| { |
| SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); |
| |
| NOT_IMPLEMENTED(); |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_op_set_changelist(svn_wc__db_t *db, |
| const char *local_abspath, |
| const char *changelist, |
| apr_pool_t *scratch_pool) |
| { |
| SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); |
| |
| NOT_IMPLEMENTED(); |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_op_mark_conflict(svn_wc__db_t *db, |
| const char *local_abspath, |
| apr_pool_t *scratch_pool) |
| { |
| SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); |
| |
| NOT_IMPLEMENTED(); |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_op_mark_resolved(svn_wc__db_t *db, |
| const char *local_abspath, |
| apr_pool_t *scratch_pool) |
| { |
| SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); |
| |
| NOT_IMPLEMENTED(); |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_op_revert(svn_wc__db_t *db, |
| const char *local_abspath, |
| svn_depth_t depth, |
| apr_pool_t *scratch_pool) |
| { |
| SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); |
| |
| NOT_IMPLEMENTED(); |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_read_info(svn_wc__db_status_t *status, |
| svn_wc__db_kind_t *kind, |
| svn_revnum_t *revision, |
| const char **repos_relpath, |
| const char **repos_root_url, |
| const char **repos_uuid, |
| svn_revnum_t *changed_rev, |
| apr_time_t *changed_date, |
| const char **changed_author, |
| apr_time_t *last_mod_time, |
| svn_depth_t *depth, |
| svn_checksum_t **checksum, |
| svn_filesize_t *translated_size, |
| const char **target, |
| const char **changelist, |
| const char **original_repos_relpath, |
| const char **original_root_url, |
| const char **original_uuid, |
| svn_revnum_t *original_revision, |
| svn_boolean_t *text_mod, |
| svn_boolean_t *props_mod, |
| svn_boolean_t *base_shadowed, |
| svn_wc__db_lock_t **lock, |
| svn_wc__db_t *db, |
| const char *local_abspath, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| svn_wc__db_pdh_t *pdh; |
| const char *local_relpath; |
| svn_sqlite__stmt_t *stmt_base; |
| svn_sqlite__stmt_t *stmt_work; |
| svn_sqlite__stmt_t *stmt_act; |
| svn_boolean_t have_base; |
| svn_boolean_t have_work; |
| svn_boolean_t have_act; |
| svn_error_t *err = NULL; |
| |
| SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); |
| |
| SVN_ERR(parse_local_abspath(&pdh, &local_relpath, db, local_abspath, |
| svn_sqlite__mode_readonly, |
| scratch_pool, scratch_pool)); |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt_base, pdh->sdb, |
| lock ? STMT_SELECT_BASE_NODE_WITH_LOCK |
| : STMT_SELECT_BASE_NODE)); |
| SVN_ERR(svn_sqlite__bindf(stmt_base, "is", pdh->wc_id, local_relpath)); |
| SVN_ERR(svn_sqlite__step(&have_base, stmt_base)); |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt_work, pdh->sdb, |
| STMT_SELECT_WORKING_NODE)); |
| SVN_ERR(svn_sqlite__bindf(stmt_work, "is", pdh->wc_id, local_relpath)); |
| SVN_ERR(svn_sqlite__step(&have_work, stmt_work)); |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt_act, pdh->sdb, |
| STMT_SELECT_ACTUAL_NODE)); |
| SVN_ERR(svn_sqlite__bindf(stmt_act, "is", pdh->wc_id, local_relpath)); |
| SVN_ERR(svn_sqlite__step(&have_act, stmt_act)); |
| |
| if (have_base || have_work) |
| { |
| const char *kind_str; |
| svn_wc__db_kind_t node_kind; |
| |
| if (have_work) |
| kind_str = svn_sqlite__column_text(stmt_work, 1, NULL); |
| else |
| kind_str = svn_sqlite__column_text(stmt_base, 5, NULL); |
| |
| SVN_ERR_ASSERT(kind_str != NULL); |
| node_kind = word_to_kind(kind_str); |
| |
| if (status) |
| { |
| const char *presence_str; |
| |
| if (have_base) |
| { |
| presence_str = svn_sqlite__column_text(stmt_base, 4, NULL); |
| *status = word_to_presence(presence_str); |
| |
| /* We have a presence that allows a WORKING_NODE override |
| (normal or not-present), or we don't have an override. */ |
| SVN_ERR_ASSERT((*status != svn_wc__db_status_absent |
| && *status != svn_wc__db_status_excluded |
| && *status != svn_wc__db_status_incomplete) |
| || !have_work); |
| |
| if (node_kind == svn_wc__db_kind_subdir |
| && *status == svn_wc__db_status_normal) |
| { |
| /* We should have read a row from the subdir wc.db. It |
| must be obstructed in some way. |
| |
| It is also possible that a WORKING node will override |
| this value with a proper status. */ |
| *status = svn_wc__db_status_obstructed; |
| } |
| } |
| |
| if (have_work) |
| { |
| svn_wc__db_status_t work_status; |
| |
| presence_str = svn_sqlite__column_text(stmt_work, 0, NULL); |
| work_status = word_to_presence(presence_str); |
| SVN_ERR_ASSERT(work_status == svn_wc__db_status_normal |
| || work_status == svn_wc__db_status_not_present |
| || work_status == svn_wc__db_status_base_deleted |
| || work_status == svn_wc__db_status_incomplete); |
| |
| if (work_status == svn_wc__db_status_incomplete) |
| { |
| *status = svn_wc__db_status_incomplete; |
| } |
| else if (work_status == svn_wc__db_status_not_present |
| || work_status == svn_wc__db_status_base_deleted) |
| { |
| /* The caller should scan upwards to detect whether this |
| deletion has occurred because this node has been moved |
| away, or it is a regular deletion. Also note that the |
| deletion could be of the BASE tree, or a child of |
| something that has been copied/moved here. |
| |
| If we're looking at the data in the parent, then |
| something has obstructed the child data. Inform |
| the caller. */ |
| if (node_kind == svn_wc__db_kind_subdir) |
| *status = svn_wc__db_status_obstructed_delete; |
| else |
| *status = svn_wc__db_status_deleted; |
| } |
| else /* normal */ |
| { |
| /* The caller should scan upwards to detect whether this |
| addition has occurred because of a simple addition, |
| a copy, or is the destination of a move. |
| |
| If we're looking at the data in the parent, then |
| something has obstructed the child data. Inform |
| the caller. */ |
| if (node_kind == svn_wc__db_kind_subdir) |
| *status = svn_wc__db_status_obstructed_add; |
| else |
| *status = svn_wc__db_status_added; |
| } |
| } |
| } |
| if (kind) |
| { |
| if (node_kind == svn_wc__db_kind_subdir) |
| *kind = svn_wc__db_kind_dir; |
| else |
| *kind = node_kind; |
| } |
| if (revision) |
| { |
| if (have_work) |
| *revision = SVN_INVALID_REVNUM; |
| else |
| *revision = svn_sqlite__column_revnum(stmt_base, 6); |
| } |
| if (repos_relpath) |
| { |
| if (have_work) |
| { |
| /* Our path is implied by our parent somewhere up the tree. |
| With the NULL value and status, the caller will know to |
| search up the tree for the base of our path. */ |
| *repos_relpath = NULL; |
| } |
| else |
| *repos_relpath = svn_sqlite__column_text(stmt_base, 3, |
| result_pool); |
| } |
| if (repos_root_url || repos_uuid) |
| { |
| /* Fetch repository information via REPOS_ID. If we have a |
| WORKING_NODE (and have been added), then the repository |
| we're being added to will be dependent upon a parent. The |
| caller can scan upwards to locate the repository. */ |
| if (have_work || svn_sqlite__column_is_null(stmt_base, 2)) |
| { |
| if (repos_root_url) |
| *repos_root_url = NULL; |
| if (repos_uuid) |
| *repos_uuid = NULL; |
| } |
| else |
| err = fetch_repos_info(repos_root_url, repos_uuid, pdh->sdb, |
| svn_sqlite__column_int64(stmt_base, 2), |
| result_pool); |
| } |
| if (changed_rev) |
| { |
| if (have_work) |
| *changed_rev = svn_sqlite__column_revnum(stmt_work, 4); |
| else |
| *changed_rev = svn_sqlite__column_revnum(stmt_base, 9); |
| } |
| if (changed_date) |
| { |
| if (have_work) |
| *changed_date = svn_sqlite__column_int64(stmt_work, 5); |
| else |
| *changed_date = svn_sqlite__column_int64(stmt_base, 10); |
| } |
| if (changed_author) |
| { |
| if (have_work) |
| *changed_author = svn_sqlite__column_text(stmt_work, 6, |
| result_pool); |
| else |
| *changed_author = svn_sqlite__column_text(stmt_base, 11, |
| result_pool); |
| } |
| if (last_mod_time) |
| { |
| if (have_work) |
| *last_mod_time = svn_sqlite__column_int64(stmt_work, 14); |
| else |
| *last_mod_time = svn_sqlite__column_int64(stmt_base, 14); |
| } |
| if (depth) |
| { |
| if (node_kind != svn_wc__db_kind_dir |
| && node_kind != svn_wc__db_kind_subdir) |
| { |
| *depth = svn_depth_unknown; |
| } |
| else |
| { |
| const char *depth_str; |
| |
| if (have_work) |
| depth_str = svn_sqlite__column_text(stmt_work, 7, NULL); |
| else |
| depth_str = svn_sqlite__column_text(stmt_base, 12, NULL); |
| |
| if (depth_str == NULL) |
| *depth = svn_depth_unknown; |
| else |
| *depth = svn_depth_from_word(depth_str); |
| } |
| } |
| if (checksum) |
| { |
| if (node_kind != svn_wc__db_kind_file) |
| { |
| *checksum = NULL; |
| } |
| else |
| { |
| if (have_work) |
| err = svn_sqlite__column_checksum(checksum, stmt_work, 2, |
| result_pool); |
| else |
| err = svn_sqlite__column_checksum(checksum, stmt_base, 7, |
| result_pool); |
| if (err != NULL) |
| err = svn_error_createf( |
| err->apr_err, err, |
| _("The node '%s' has a corrupt checksum value."), |
| svn_dirent_local_style(local_abspath, scratch_pool)); |
| } |
| } |
| if (translated_size) |
| { |
| if (have_work) |
| *translated_size = get_translated_size(stmt_work, 3); |
| else |
| *translated_size = get_translated_size(stmt_base, 8); |
| } |
| if (target) |
| { |
| if (node_kind != svn_wc__db_kind_symlink) |
| *target = NULL; |
| else if (have_work) |
| *target = svn_sqlite__column_text(stmt_work, 8, result_pool); |
| else |
| *target = svn_sqlite__column_text(stmt_base, 13, result_pool); |
| } |
| if (changelist) |
| { |
| if (have_act) |
| *changelist = svn_sqlite__column_text(stmt_act, 0, result_pool); |
| else |
| *changelist = NULL; |
| } |
| if (original_repos_relpath) |
| { |
| if (have_work) |
| *original_repos_relpath = svn_sqlite__column_text(stmt_work, 10, |
| result_pool); |
| else |
| *original_repos_relpath = NULL; |
| } |
| if (!have_work || svn_sqlite__column_is_null(stmt_work, 9)) |
| { |
| if (original_root_url) |
| *original_root_url = NULL; |
| if (original_uuid) |
| *original_uuid = NULL; |
| } |
| else if (original_root_url || original_uuid) |
| { |
| /* Fetch repository information via COPYFROM_REPOS_ID. */ |
| err = fetch_repos_info(original_root_url, original_uuid, pdh->sdb, |
| svn_sqlite__column_int64(stmt_work, 9), |
| result_pool); |
| } |
| if (original_revision) |
| { |
| if (have_work) |
| *original_revision = svn_sqlite__column_revnum(stmt_work, 11); |
| else |
| *original_revision = SVN_INVALID_REVNUM; |
| } |
| if (text_mod) |
| { |
| /* ### fix this */ |
| *text_mod = FALSE; |
| } |
| if (props_mod) |
| { |
| /* ### fix this */ |
| *props_mod = FALSE; |
| } |
| if (base_shadowed) |
| { |
| *base_shadowed = have_base && have_work; |
| } |
| if (lock) |
| { |
| if (svn_sqlite__column_is_null(stmt_base, 15)) |
| *lock = NULL; |
| else |
| { |
| *lock = apr_pcalloc(result_pool, sizeof(svn_wc__db_lock_t)); |
| (*lock)->token = svn_sqlite__column_text(stmt_base, 15, |
| result_pool); |
| if (!svn_sqlite__column_is_null(stmt_base, 16)) |
| (*lock)->owner = svn_sqlite__column_text(stmt_base, 16, |
| result_pool); |
| if (!svn_sqlite__column_is_null(stmt_base, 17)) |
| (*lock)->comment = svn_sqlite__column_text(stmt_base, 17, |
| result_pool); |
| if (!svn_sqlite__column_is_null(stmt_base, 18)) |
| (*lock)->date = svn_sqlite__column_int64(stmt_base, 18); |
| } |
| } |
| } |
| else if (have_act) |
| { |
| /* A row in ACTUAL_NODE should never exist without a corresponding |
| node in BASE_NODE and/or WORKING_NODE. */ |
| err = svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, |
| _("Corrupt data for '%s'"), |
| svn_dirent_local_style(local_abspath, |
| scratch_pool)); |
| } |
| else |
| { |
| err = svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, |
| _("The node '%s' was not found."), |
| svn_dirent_local_style(local_abspath, |
| scratch_pool)); |
| } |
| |
| err = svn_error_compose_create(err, svn_sqlite__reset(stmt_base)); |
| err = svn_error_compose_create(err, svn_sqlite__reset(stmt_work)); |
| return svn_error_compose_create(err, svn_sqlite__reset(stmt_act)); |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_read_prop(const svn_string_t **propval, |
| svn_wc__db_t *db, |
| const char *local_abspath, |
| const char *propname, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| apr_hash_t *props; |
| |
| SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); |
| SVN_ERR_ASSERT(propname != NULL); |
| |
| /* Note: maybe one day, we'll have internal caches of this stuff, but |
| for now, we just grab all the props and pick out the requested prop. */ |
| |
| /* ### should: fetch into scratch_pool, then dup into result_pool. */ |
| SVN_ERR(svn_wc__db_read_props(&props, db, local_abspath, |
| result_pool, scratch_pool)); |
| |
| *propval = apr_hash_get(props, propname, APR_HASH_KEY_STRING); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_read_props(apr_hash_t **props, |
| svn_wc__db_t *db, |
| const char *local_abspath, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| svn_sqlite__stmt_t *stmt; |
| svn_boolean_t have_row; |
| |
| SVN_ERR(get_statement_for_path(&stmt, db, local_abspath, |
| STMT_SELECT_ALL_PROPS, scratch_pool)); |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| if (!have_row) |
| return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, |
| _("The node '%s' was not found."), |
| svn_dirent_local_style(local_abspath, |
| scratch_pool)); |
| |
| /* Try ACTUAL, then WORKING and finally BASE. */ |
| if (!svn_sqlite__column_is_null(stmt, 0)) |
| SVN_ERR(svn_sqlite__column_properties(props, stmt, 0, result_pool, |
| scratch_pool)); |
| else if (!svn_sqlite__column_is_null(stmt, 1)) |
| SVN_ERR(svn_sqlite__column_properties(props, stmt, 1, result_pool, |
| scratch_pool)); |
| else |
| SVN_ERR(svn_sqlite__column_properties(props, stmt, 2, result_pool, |
| scratch_pool)); |
| |
| return svn_sqlite__reset(stmt); |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_read_pristine_props(apr_hash_t **props, |
| svn_wc__db_t *db, |
| const char *local_abspath, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| svn_sqlite__stmt_t *stmt; |
| svn_boolean_t have_row; |
| |
| SVN_ERR(get_statement_for_path(&stmt, db, local_abspath, |
| STMT_SELECT_PRISTINE_PROPS, scratch_pool)); |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| if (!have_row) |
| return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, |
| _("The node '%s' was not found."), |
| svn_dirent_local_style(local_abspath, |
| scratch_pool)); |
| |
| /* Try WORKING, then BASE. */ |
| if (!svn_sqlite__column_is_null(stmt, 0)) |
| SVN_ERR(svn_sqlite__column_properties(props, stmt, 0, result_pool, |
| scratch_pool)); |
| else |
| SVN_ERR(svn_sqlite__column_properties(props, stmt, 1, result_pool, |
| scratch_pool)); |
| |
| return svn_sqlite__reset(stmt); |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_read_children(const apr_array_header_t **children, |
| svn_wc__db_t *db, |
| const char *local_abspath, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| return gather_children(children, STMT_SELECT_WORKING_CHILDREN, |
| db, local_abspath, result_pool, scratch_pool); |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_global_relocate(svn_wc__db_t *db, |
| const char *local_dir_abspath, |
| const char *from_url, |
| const char *to_url, |
| svn_depth_t depth, |
| apr_pool_t *scratch_pool) |
| { |
| SVN_ERR_ASSERT(svn_dirent_is_absolute(local_dir_abspath)); |
| |
| NOT_IMPLEMENTED(); |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_global_commit(svn_wc__db_t *db, |
| const char *local_abspath, |
| svn_revnum_t new_revision, |
| apr_time_t new_date, |
| const char *new_author, |
| apr_pool_t *scratch_pool) |
| { |
| SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); |
| SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(new_revision)); |
| SVN_ERR_ASSERT(new_date > 0); |
| SVN_ERR_ASSERT(new_author != NULL); |
| |
| NOT_IMPLEMENTED(); |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_lock_add(svn_wc__db_t *db, |
| const char *local_abspath, |
| const svn_wc__db_lock_t *lock, |
| apr_pool_t *scratch_pool) |
| { |
| svn_wc__db_pdh_t *pdh; |
| const char *local_relpath; |
| svn_sqlite__stmt_t *stmt; |
| const char *repos_root_url; |
| const char *repos_relpath; |
| const char *repos_uuid; |
| apr_int64_t repos_id; |
| |
| SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); |
| |
| /* ### the next three calls could/should be optimized to reduce scanning |
| ### for a wc.db and row fetches within that. */ |
| |
| /* Fetch the repos root and repos uuid from the base node, we we can |
| then create or get the repos id. |
| ### is there a better way to do this? */ |
| SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, NULL, &repos_relpath, |
| &repos_root_url, &repos_uuid, |
| NULL, NULL, NULL, NULL, NULL, NULL, |
| NULL, NULL, NULL, |
| db, local_abspath, scratch_pool, |
| scratch_pool)); |
| |
| SVN_ERR(parse_local_abspath(&pdh, &local_relpath, db, local_abspath, |
| svn_sqlite__mode_readwrite, |
| scratch_pool, scratch_pool)); |
| SVN_ERR(create_repos_id(&repos_id, repos_root_url, repos_uuid, pdh->sdb, |
| scratch_pool)); |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, pdh->sdb, STMT_INSERT_LOCK)); |
| SVN_ERR(svn_sqlite__bind_int64(stmt, 1, repos_id)); |
| SVN_ERR(svn_sqlite__bind_text(stmt, 2, repos_relpath)); |
| SVN_ERR(svn_sqlite__bind_text(stmt, 3, lock->token)); |
| |
| if (lock->owner != NULL) |
| SVN_ERR(svn_sqlite__bind_text(stmt, 4, lock->owner)); |
| |
| if (lock->comment != NULL) |
| SVN_ERR(svn_sqlite__bind_text(stmt, 5, lock->comment)); |
| |
| if (lock->date != 0) |
| SVN_ERR(svn_sqlite__bind_int64(stmt, 6, lock->date)); |
| |
| return svn_sqlite__insert(NULL, stmt); |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_scan_base_repos(const char **repos_relpath, |
| const char **repos_root_url, |
| const char **repos_uuid, |
| svn_wc__db_t *db, |
| const char *local_abspath, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| svn_wc__db_pdh_t *pdh; |
| const char *local_relpath; |
| apr_int64_t repos_id; |
| |
| SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); |
| |
| SVN_ERR(parse_local_abspath(&pdh, &local_relpath, db, local_abspath, |
| svn_sqlite__mode_readonly, |
| scratch_pool, scratch_pool)); |
| |
| SVN_ERR(scan_upwards_for_repos(&repos_id, repos_relpath, |
| pdh->wc_id, local_relpath, pdh->sdb, |
| result_pool, scratch_pool)); |
| |
| if (repos_root_url || repos_uuid) |
| return fetch_repos_info(repos_root_url, repos_uuid, pdh->sdb, repos_id, |
| result_pool); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_scan_working(svn_wc__db_status_t *status, |
| const char **op_root_abspath, |
| const char **repos_relpath, |
| const char **repos_root_url, |
| const char **repos_uuid, |
| const char **original_repos_relpath, |
| const char **original_root_url, |
| const char **original_uuid, |
| svn_revnum_t *original_revision, |
| const char **moved_to_abspath, |
| svn_wc__db_t *db, |
| const char *local_abspath, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| svn_wc__db_status_t start_status; |
| const char *current_abspath = local_abspath; |
| const char *current_relpath; |
| const char *child_abspath = NULL; |
| const char *build_relpath = ""; |
| svn_wc__db_pdh_t *pdh; |
| svn_boolean_t found_info = FALSE; |
| |
| SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); |
| |
| /* Initialize all the OUT parameters. Generally, we'll only be filling |
| in a subset of these, so it is easier to init all up front. Note that |
| the STATUS parameter will be initialized once we read the status of |
| the specified node. */ |
| if (op_root_abspath) |
| *op_root_abspath = NULL; |
| if (repos_relpath) |
| *repos_relpath = NULL; |
| if (repos_root_url) |
| *repos_root_url = NULL; |
| if (repos_uuid) |
| *repos_uuid = NULL; |
| if (original_repos_relpath) |
| *original_repos_relpath = NULL; |
| if (original_root_url) |
| *original_root_url = NULL; |
| if (original_uuid) |
| *original_uuid = NULL; |
| if (original_revision) |
| *original_revision = SVN_INVALID_REVNUM; |
| if (moved_to_abspath) |
| *moved_to_abspath = NULL; |
| |
| SVN_ERR(parse_local_abspath(&pdh, ¤t_relpath, db, current_abspath, |
| svn_sqlite__mode_readonly, |
| scratch_pool, scratch_pool)); |
| |
| while (TRUE) |
| { |
| svn_sqlite__stmt_t *stmt; |
| svn_boolean_t have_row; |
| svn_boolean_t presence_is_normal; |
| |
| /* ### is it faster to fetch fewer columns? */ |
| SVN_ERR(svn_sqlite__get_statement(&stmt, pdh->sdb, |
| STMT_SELECT_WORKING_NODE)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "is", pdh->wc_id, current_relpath)); |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| |
| if (!have_row) |
| { |
| if (current_abspath == local_abspath) |
| { |
| svn_error_clear(svn_sqlite__reset(stmt)); |
| |
| return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, |
| _("The node '%s' was not found."), |
| svn_dirent_local_style(local_abspath, |
| scratch_pool)); |
| } |
| SVN_ERR(svn_sqlite__reset(stmt)); |
| |
| /* We just fell off the top of the WORKING tree. If we haven't |
| found the operation root, then the child node that we just |
| left was that root. */ |
| if (op_root_abspath && *op_root_abspath == NULL) |
| { |
| SVN_ERR_ASSERT(child_abspath != NULL); |
| *op_root_abspath = apr_pstrdup(result_pool, child_abspath); |
| } |
| |
| /* If the subtree was deleted, then we can exit since there is no |
| need to continue scanning BASE nodes upwards to determine a |
| repository location. */ |
| if (start_status == svn_wc__db_status_deleted) |
| return SVN_NO_ERROR; |
| |
| /* Otherwise, this node was added/copied/moved and has an implicit |
| location in the repository. We now need to traverse BASE nodes |
| looking for repository info. */ |
| break; |
| } |
| |
| presence_is_normal = strcmp("normal", |
| svn_sqlite__column_text(stmt, 0, NULL)) == 0; |
| |
| /* Record information from the starting node. */ |
| if (current_abspath == local_abspath) |
| { |
| start_status = word_to_presence(svn_sqlite__column_text(stmt, 0, |
| NULL)); |
| if (start_status == svn_wc__db_status_normal) |
| start_status = svn_wc__db_status_added; |
| else |
| start_status = svn_wc__db_status_deleted; |
| /* ### assert valid presence values? what if it is (say) |
| ### 'incomplete' ? probably return an error. */ |
| |
| /* Provide the default status; we'll override as appropriate. */ |
| if (status) |
| *status = start_status; |
| } |
| else if (start_status == svn_wc__db_status_deleted |
| && presence_is_normal) |
| { |
| /* We have moved upwards at least one node, the start node |
| was deleted, but we have now run into a not-deleted node. |
| Thus, the node we just left was the root of a delete. |
| Record that and exit, as we have no further information |
| to discover. */ |
| if (op_root_abspath) |
| *op_root_abspath = apr_pstrdup(result_pool, child_abspath); |
| |
| return svn_sqlite__reset(stmt); |
| } |
| |
| if (!svn_sqlite__column_is_null(stmt, 13 /* moved_to */)) |
| { |
| /* ### assert that presence == not-present ? */ |
| SVN_ERR_ASSERT(start_status == svn_wc__db_status_deleted); |
| |
| if (status) |
| *status = svn_wc__db_status_moved_away; |
| if (op_root_abspath) |
| *op_root_abspath = apr_pstrdup(result_pool, current_abspath); |
| if (moved_to_abspath) |
| *moved_to_abspath = svn_dirent_join( |
| pdh->wcroot_abspath, |
| svn_sqlite__column_text(stmt, 13, NULL), |
| result_pool); |
| |
| /* There is no other information to retrieve. We're done. */ |
| return svn_sqlite__reset(stmt); |
| } |
| |
| /* We want the operation closest to the start node, and then we |
| ignore any operations on its ancestors. */ |
| if (!found_info |
| && presence_is_normal |
| && !svn_sqlite__column_is_null(stmt, 9 /* copyfrom_repos_id */)) |
| { |
| SVN_ERR_ASSERT(start_status == svn_wc__db_status_added); |
| |
| if (status) |
| { |
| if (svn_sqlite__column_boolean(stmt, 12 /* moved_here */)) |
| *status = svn_wc__db_status_moved_here; |
| else |
| *status = svn_wc__db_status_copied; |
| } |
| if (op_root_abspath) |
| *op_root_abspath = apr_pstrdup(result_pool, current_abspath); |
| if (original_repos_relpath) |
| *original_repos_relpath = svn_sqlite__column_text(stmt, 10, |
| result_pool); |
| if (original_root_url || original_uuid) |
| SVN_ERR(fetch_repos_info(original_root_url, original_uuid, |
| pdh->sdb, |
| svn_sqlite__column_int64(stmt, 9), |
| result_pool)); |
| if (original_revision) |
| *original_revision = svn_sqlite__column_revnum(stmt, 11); |
| |
| /* We may have to keep tracking upwards for REPOS_* values. |
| If they're not needed, then just return. */ |
| if (repos_relpath == NULL |
| && repos_root_url == NULL |
| && repos_uuid == NULL) |
| return svn_sqlite__reset(stmt); |
| |
| /* We've found the info we needed. Scan for the top of the |
| WORKING tree, and then the REPOS_* information. */ |
| found_info = TRUE; |
| } |
| |
| SVN_ERR(svn_sqlite__reset(stmt)); |
| |
| /* If the caller wants to know the starting node's REPOS_RELPATH, |
| then keep track of what we're stripping off the ABSPATH as we |
| traverse up the tree. */ |
| if (repos_relpath) |
| { |
| build_relpath = svn_dirent_join(svn_dirent_basename(current_abspath, |
| scratch_pool), |
| build_relpath, |
| scratch_pool); |
| } |
| |
| /* Move to the parent node. Remember the abspath to this node, since |
| it could be the root of an add/delete. */ |
| child_abspath = current_abspath; |
| if (strcmp(current_relpath, pdh->local_relpath) == 0) |
| { |
| /* The current node is a directory, so move to the parent dir. */ |
| SVN_ERR(navigate_to_parent(&pdh, pdh, svn_sqlite__mode_readonly, |
| scratch_pool)); |
| } |
| current_abspath = pdh->local_abspath; |
| current_relpath = pdh->local_relpath; |
| } |
| |
| /* If we're here, then we have an added/copied/moved (start) node, and |
| CURRENT_ABSPATH now points to a BASE node. Figure out the repository |
| information for the current node, and use that to compute the start |
| node's repository information. */ |
| if (repos_relpath || repos_root_url || repos_uuid) |
| { |
| const char *base_relpath; |
| |
| /* ### unwrap this. we can optimize away the parse_local_abspath. */ |
| SVN_ERR(svn_wc__db_scan_base_repos(&base_relpath, repos_root_url, |
| repos_uuid, db, current_abspath, |
| result_pool, scratch_pool)); |
| |
| if (repos_relpath) |
| *repos_relpath = svn_dirent_join(base_relpath, build_relpath, |
| result_pool); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_error_t * |
| svn_wc__db_scan_deletion(const char **base_del_abspath, |
| svn_boolean_t *base_replaced, |
| const char **moved_to_abspath, |
| const char **work_del_abspath, |
| svn_wc__db_t *db, |
| const char *local_abspath, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| const char *current_abspath = local_abspath; |
| const char *current_relpath; |
| const char *child_abspath = NULL; |
| svn_wc__db_status_t child_presence; |
| svn_boolean_t child_has_base = FALSE; |
| svn_wc__db_pdh_t *pdh; |
| |
| SVN_ERR_ASSERT(base_del_abspath != NULL); |
| SVN_ERR_ASSERT(base_replaced != NULL); |
| SVN_ERR_ASSERT(moved_to_abspath != NULL); |
| SVN_ERR_ASSERT(work_del_abspath != NULL); |
| SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); |
| |
| /* Initialize all the OUT parameters. */ |
| *base_del_abspath = NULL; |
| *base_replaced = FALSE; /* becomes TRUE when we know for sure. */ |
| *moved_to_abspath = NULL; |
| *work_del_abspath = NULL; |
| |
| /* Initialize to something that won't denote an important parent/child |
| transition. */ |
| child_presence = svn_wc__db_status_base_deleted; |
| |
| SVN_ERR(parse_local_abspath(&pdh, ¤t_relpath, db, local_abspath, |
| svn_sqlite__mode_readonly, |
| scratch_pool, scratch_pool)); |
| |
| while (TRUE) |
| { |
| svn_sqlite__stmt_t *stmt; |
| svn_boolean_t have_row; |
| svn_boolean_t have_base; |
| svn_wc__db_status_t work_presence; |
| |
| SVN_ERR(svn_sqlite__get_statement(&stmt, pdh->sdb, |
| STMT_SELECT_DELETION_INFO)); |
| SVN_ERR(svn_sqlite__bindf(stmt, "is", pdh->wc_id, current_relpath)); |
| SVN_ERR(svn_sqlite__step(&have_row, stmt)); |
| |
| if (!have_row) |
| { |
| /* There better be a row for the starting node! */ |
| if (current_abspath == local_abspath) |
| { |
| svn_error_clear(svn_sqlite__reset(stmt)); |
| |
| return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, |
| _("The node '%s' was not found."), |
| svn_dirent_local_style(local_abspath, |
| scratch_pool)); |
| } |
| |
| /* There are no values, so go ahead and reset the stmt now. */ |
| SVN_ERR(svn_sqlite__reset(stmt)); |
| |
| /* No row means no WORKING node at this path, which means we just |
| fell off the top of the WORKING tree. |
| |
| The child cannot be not-present, as that would imply the |
| root of the (added) WORKING subtree was deleted. */ |
| SVN_ERR_ASSERT(child_presence != svn_wc__db_status_not_present); |
| |
| /* If the child did not have a BASE node associated with it, then |
| we're looking at a deletion that occurred within an added tree. |
| There is no root of a deleted/replaced BASE tree. |
| |
| If the child was base-deleted, then the whole tree is a |
| simple (explicit) deletion of the BASE tree. |
| |
| If the child was normal, then it is the root of a replacement, |
| which means an (implicit) deletion of the BASE tree. |
| |
| In both cases, set the root of the operation (if we have not |
| already set it as part of a moved-away). */ |
| if (child_has_base && *base_del_abspath == NULL) |
| *base_del_abspath = apr_pstrdup(result_pool, child_abspath); |
| |
| /* We found whatever roots we needed. This BASE node and its |
| ancestors are unchanged, so we're done. */ |
| break; |
| } |
| |
| /* We need the presence of the WORKING node. */ |
| work_presence = word_to_presence(svn_sqlite__column_text(stmt, 1, NULL)); |
| /* ### check for allowable presence values for this algorithm. |
| ### LEGAL: normal, not-present, base-deleted. */ |
| |
| /* If we're on the starting node, then we have a bit of extra work. */ |
| if (current_abspath == local_abspath) |
| { |
| /* The starting node better be deleted! */ |
| SVN_ERR_ASSERT(work_presence != svn_wc__db_status_normal); |
| } |
| |
| have_base = !svn_sqlite__column_is_null(stmt, |
| 0 /* BASE_NODE.presence */); |
| if (have_base) |
| { |
| svn_wc__db_status_t base_presence |
| = word_to_presence(svn_sqlite__column_text(stmt, 0, NULL)); |
| |
| /* ### check for allowable presence values for this algorithm. |
| ### LEGAL: normal, not-present. */ |
| |
| /* If a BASE node is marked as not-present, then we'll ignore |
| it within this function. That status is simply a bookkeeping |
| gimmick, not a real node that may have been deleted. */ |
| |
| /* If we're looking at a present BASE node, *and* there is a |
| WORKING node (present or deleted), then a replacement has |
| occurred here or in an ancestor. */ |
| if (base_presence == svn_wc__db_status_normal |
| && work_presence != svn_wc__db_status_base_deleted) |
| { |
| *base_replaced = TRUE; |
| } |
| } |
| |
| /* Only grab the nearest ancestor. */ |
| if (*moved_to_abspath == NULL |
| && !svn_sqlite__column_is_null(stmt, 2 /* moved_to */)) |
| { |
| /* There better be a BASE_NODE (that was moved-away). */ |
| SVN_ERR_ASSERT(have_base); |
| |
| /* This makes things easy. It's the BASE_DEL_ABSPATH! */ |
| *base_del_abspath = apr_pstrdup(result_pool, current_abspath); |
| *moved_to_abspath = svn_dirent_join( |
| pdh->wcroot_abspath, |
| svn_sqlite__column_text(stmt, 2, NULL), |
| result_pool); |
| } |
| |
| if (work_presence == svn_wc__db_status_normal |
| && child_presence == svn_wc__db_status_not_present) |
| { |
| /* Parent is normal, but child was deleted. Therefore, the child |
| is the root of a WORKING subtree deletion. */ |
| *work_del_abspath = apr_pstrdup(result_pool, child_abspath); |
| } |
| |
| /* We're all done examining the return values. */ |
| SVN_ERR(svn_sqlite__reset(stmt)); |
| |
| /* Move to the parent node. Remember the information about this node |
| for our parent to use. */ |
| child_abspath = current_abspath; |
| child_presence = work_presence; |
| child_has_base = have_base; |
| if (strcmp(current_relpath, pdh->local_relpath) == 0) |
| { |
| /* The current node is a directory, so move to the parent dir. */ |
| SVN_ERR(navigate_to_parent(&pdh, pdh, svn_sqlite__mode_readonly, |
| scratch_pool)); |
| } |
| current_abspath = pdh->local_abspath; |
| current_relpath = pdh->local_relpath; |
| } |
| |
| return SVN_NO_ERROR; |
| } |