| /* |
| * adm_ops.c: routines for affecting working copy administrative |
| * information. NOTE: this code doesn't know where the adm |
| * info is actually stored. Instead, generic handles to |
| * adm data are requested via a reference to some PATH |
| * (PATH being a regular, non-administrative directory or |
| * file in the working copy). |
| * |
| * ==================================================================== |
| * Copyright (c) 2000-2006 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 <string.h> |
| |
| #include <apr_pools.h> |
| #include <apr_hash.h> |
| #include <apr_md5.h> |
| #include <apr_file_io.h> |
| #include <apr_time.h> |
| |
| #include "svn_types.h" |
| #include "svn_pools.h" |
| #include "svn_string.h" |
| #include "svn_error.h" |
| #include "svn_path.h" |
| #include "svn_wc.h" |
| #include "svn_io.h" |
| #include "svn_md5.h" |
| #include "svn_xml.h" |
| #include "svn_time.h" |
| |
| #include "wc.h" |
| #include "log.h" |
| #include "adm_files.h" |
| #include "adm_ops.h" |
| #include "entries.h" |
| #include "lock.h" |
| #include "props.h" |
| #include "translate.h" |
| |
| #include "svn_private_config.h" |
| |
| |
| /*** Finishing updates and commits. ***/ |
| |
| |
| /* The main body of svn_wc__do_update_cleanup. */ |
| static svn_error_t * |
| tweak_entries(svn_wc_adm_access_t *dirpath, |
| const char *base_url, |
| const char *repos, |
| svn_revnum_t new_rev, |
| svn_wc_notify_func2_t notify_func, |
| void *notify_baton, |
| svn_boolean_t remove_missing_dirs, |
| svn_boolean_t recurse, |
| apr_pool_t *pool) |
| { |
| apr_hash_t *entries; |
| apr_hash_index_t *hi; |
| apr_pool_t *subpool = svn_pool_create(pool); |
| svn_boolean_t write_required = FALSE; |
| svn_wc_notify_t *notify; |
| |
| /* Read DIRPATH's entries. */ |
| SVN_ERR(svn_wc_entries_read(&entries, dirpath, TRUE, pool)); |
| |
| /* Tweak "this_dir" */ |
| SVN_ERR(svn_wc__tweak_entry(entries, SVN_WC_ENTRY_THIS_DIR, |
| base_url, repos, new_rev, FALSE, |
| &write_required, |
| svn_wc_adm_access_pool(dirpath))); |
| |
| /* Recursively loop over all children. */ |
| for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi)) |
| { |
| const void *key; |
| void *val; |
| const char *name; |
| svn_wc_entry_t *current_entry; |
| const char *child_url = NULL; |
| |
| svn_pool_clear(subpool); |
| |
| apr_hash_this(hi, &key, NULL, &val); |
| name = key; |
| current_entry = val; |
| |
| /* Ignore the "this dir" entry. */ |
| if (! strcmp(name, SVN_WC_ENTRY_THIS_DIR)) |
| continue; |
| |
| /* Derive the new URL for the current (child) entry */ |
| if (base_url) |
| child_url = svn_path_url_add_component(base_url, name, subpool); |
| |
| /* If a file, or deleted or absent dir in recursive mode, then tweak the |
| entry but don't recurse. */ |
| if ((current_entry->kind == svn_node_file) |
| || (recurse && (current_entry->deleted || current_entry->absent))) |
| { |
| SVN_ERR(svn_wc__tweak_entry(entries, name, |
| child_url, repos, new_rev, TRUE, |
| &write_required, |
| svn_wc_adm_access_pool(dirpath))); |
| } |
| |
| /* If a directory and recursive... */ |
| else if (recurse && (current_entry->kind == svn_node_dir)) |
| { |
| const char *child_path |
| = svn_path_join(svn_wc_adm_access_path(dirpath), name, subpool); |
| |
| /* If the directory is 'missing', remove it. This is safe as |
| long as this function is only called as a helper to |
| svn_wc__do_update_cleanup, since the update will already have |
| restored any missing items that it didn't want to delete. */ |
| if (remove_missing_dirs |
| && svn_wc__adm_missing(dirpath, child_path)) |
| { |
| if (current_entry->schedule != svn_wc_schedule_add) |
| { |
| svn_wc__entry_remove(entries, name); |
| if (notify_func) |
| { |
| notify = svn_wc_create_notify(child_path, |
| svn_wc_notify_delete, |
| subpool); |
| notify->kind = current_entry->kind; |
| (* notify_func)(notify_baton, notify, subpool); |
| } |
| } |
| /* Else if missing item is schedule-add, do nothing. */ |
| } |
| |
| /* Not missing, deleted, or absent, so recurse. */ |
| else |
| { |
| svn_wc_adm_access_t *child_access; |
| SVN_ERR(svn_wc_adm_retrieve(&child_access, dirpath, child_path, |
| subpool)); |
| SVN_ERR(tweak_entries |
| (child_access, child_url, repos, new_rev, notify_func, |
| notify_baton, remove_missing_dirs, recurse, subpool)); |
| } |
| } |
| } |
| |
| /* Write a shiny new entries file to disk. */ |
| if (write_required) |
| SVN_ERR(svn_wc__entries_write(entries, dirpath, subpool)); |
| |
| /* Cleanup */ |
| svn_pool_destroy(subpool); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Helper for svn_wc_process_committed2. */ |
| static svn_error_t * |
| remove_revert_file(svn_stringbuf_t **logtags, |
| svn_wc_adm_access_t *adm_access, |
| const char *base_name, |
| svn_boolean_t is_prop, |
| apr_pool_t * pool) |
| { |
| const char * revert_file; |
| const svn_wc_entry_t *entry; |
| svn_node_kind_t kind; |
| |
| SVN_ERR(svn_wc_entry(&entry, base_name, adm_access, FALSE, pool)); |
| |
| /*### Maybe assert (entry); calling remove_revert_file |
| for an unversioned file is bogus */ |
| if (! entry) |
| return SVN_NO_ERROR; |
| |
| if (is_prop) |
| SVN_ERR(svn_wc__prop_revert_path(&revert_file, base_name, entry->kind, |
| FALSE, pool)); |
| else |
| revert_file = svn_wc__text_revert_path(base_name, FALSE, pool); |
| |
| SVN_ERR(svn_io_check_path |
| (svn_path_join(svn_wc_adm_access_path(adm_access), |
| revert_file, pool), |
| &kind, pool)); |
| |
| if (kind == svn_node_file) |
| SVN_ERR(svn_wc__loggy_remove(logtags, adm_access, revert_file, pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| svn_error_t * |
| svn_wc__do_update_cleanup(const char *path, |
| svn_wc_adm_access_t *adm_access, |
| svn_boolean_t recursive, |
| const char *base_url, |
| const char *repos, |
| svn_revnum_t new_revision, |
| svn_wc_notify_func2_t notify_func, |
| void *notify_baton, |
| svn_boolean_t remove_missing_dirs, |
| apr_pool_t *pool) |
| { |
| apr_hash_t *entries; |
| const svn_wc_entry_t *entry; |
| |
| SVN_ERR(svn_wc_entry(&entry, path, adm_access, TRUE, pool)); |
| if (entry == NULL) |
| return SVN_NO_ERROR; |
| |
| if (entry->kind == svn_node_file |
| || (entry->kind == svn_node_dir && (entry->deleted || entry->absent))) |
| { |
| const char *parent, *base_name; |
| svn_wc_adm_access_t *dir_access; |
| svn_boolean_t write_required = FALSE; |
| svn_path_split(path, &parent, &base_name, pool); |
| SVN_ERR(svn_wc_adm_retrieve(&dir_access, adm_access, parent, pool)); |
| SVN_ERR(svn_wc_entries_read(&entries, dir_access, TRUE, pool)); |
| SVN_ERR(svn_wc__tweak_entry(entries, base_name, |
| base_url, repos, new_revision, |
| FALSE, /* Parent not updated so don't |
| remove PATH entry */ |
| &write_required, |
| svn_wc_adm_access_pool(dir_access))); |
| if (write_required) |
| SVN_ERR(svn_wc__entries_write(entries, dir_access, pool)); |
| } |
| |
| else if (entry->kind == svn_node_dir) |
| { |
| svn_wc_adm_access_t *dir_access; |
| SVN_ERR(svn_wc_adm_retrieve(&dir_access, adm_access, path, pool)); |
| |
| SVN_ERR(tweak_entries(dir_access, base_url, repos, new_revision, |
| notify_func, notify_baton, remove_missing_dirs, |
| recursive, pool)); |
| } |
| |
| else |
| return svn_error_createf(SVN_ERR_NODE_UNKNOWN_KIND, NULL, |
| _("Unrecognized node kind: '%s'"), |
| svn_path_local_style(path, pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| svn_error_t * |
| svn_wc_maybe_set_repos_root(svn_wc_adm_access_t *adm_access, |
| const char *path, |
| const char *repos, |
| apr_pool_t *pool) |
| { |
| apr_hash_t *entries; |
| svn_boolean_t write_required = FALSE; |
| const svn_wc_entry_t *entry; |
| const char *base_name; |
| svn_wc_adm_access_t *dir_access; |
| |
| SVN_ERR(svn_wc_entry(&entry, path, adm_access, FALSE, pool)); |
| if (! entry) |
| return SVN_NO_ERROR; |
| |
| if (entry->kind == svn_node_file) |
| { |
| const char *parent; |
| |
| svn_path_split(path, &parent, &base_name, pool); |
| SVN_ERR(svn_wc__adm_retrieve_internal(&dir_access, adm_access, |
| parent, pool)); |
| } |
| else |
| { |
| base_name = SVN_WC_ENTRY_THIS_DIR; |
| SVN_ERR(svn_wc__adm_retrieve_internal(&dir_access, adm_access, |
| path, pool)); |
| } |
| |
| if (! dir_access) |
| return SVN_NO_ERROR; |
| |
| SVN_ERR(svn_wc_entries_read(&entries, dir_access, TRUE, pool)); |
| |
| SVN_ERR(svn_wc__tweak_entry(entries, base_name, |
| NULL, repos, SVN_INVALID_REVNUM, FALSE, |
| &write_required, |
| svn_wc_adm_access_pool(dir_access))); |
| |
| if (write_required) |
| SVN_ERR(svn_wc__entries_write(entries, dir_access, pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_error_t * |
| svn_wc_process_committed3(const char *path, |
| svn_wc_adm_access_t *adm_access, |
| svn_boolean_t recurse, |
| svn_revnum_t new_revnum, |
| const char *rev_date, |
| const char *rev_author, |
| apr_array_header_t *wcprop_changes, |
| svn_boolean_t remove_lock, |
| const unsigned char *digest, |
| apr_pool_t *pool) |
| { |
| const char *base_name; |
| svn_stringbuf_t *logtags; |
| const char *hex_digest = NULL; |
| svn_wc_entry_t tmp_entry; |
| apr_uint32_t modify_flags = 0; |
| |
| logtags = svn_stringbuf_create("", pool); |
| |
| SVN_ERR(svn_wc__adm_write_check(adm_access)); |
| |
| /* Set PATH's working revision to NEW_REVNUM; if REV_DATE and |
| REV_AUTHOR are both non-NULL, then set the 'committed-rev', |
| 'committed-date', and 'last-author' entry values; and set the |
| checksum if a file. */ |
| |
| base_name = svn_path_is_child(svn_wc_adm_access_path(adm_access), path, |
| pool); |
| if (base_name) |
| { |
| /* PATH must be some sort of file */ |
| const char *latest_base; |
| svn_node_kind_t kind; |
| |
| /* If the props or text revert file exists it needs to be deleted when |
| * the file is committed. */ |
| SVN_ERR(remove_revert_file(&logtags, adm_access, base_name, |
| FALSE, pool)); |
| SVN_ERR(remove_revert_file(&logtags, adm_access, base_name, |
| TRUE, pool)); |
| |
| if (digest) |
| hex_digest = svn_md5_digest_to_cstring(digest, pool); |
| else |
| { |
| /* There may be a new text base sitting in the adm tmp area |
| by now, because the commit succeeded. A file that is |
| copied, but not otherwise modified, doesn't have a new |
| text base, so we use the unmodified text base. |
| |
| ### Does this mean that a file committed with only prop mods |
| ### will still get its text base checksum recomputed? Yes it |
| ### does, sadly. But it's not enough to just check for that |
| ### condition, because in the case of an added file, there |
| ### may not be a pre-existing checksum in the entry. |
| ### Probably the best solution is to compute (or copy) the |
| ### checksum at 'svn add' (or 'svn cp') time, instead of |
| ### waiting until commit time. |
| */ |
| latest_base = svn_wc__text_base_path(path, TRUE, pool); |
| SVN_ERR(svn_io_check_path(latest_base, &kind, pool)); |
| if (kind == svn_node_none) |
| { |
| latest_base = svn_wc__text_base_path(path, FALSE, pool); |
| SVN_ERR(svn_io_check_path(latest_base, &kind, pool)); |
| } |
| |
| if (kind == svn_node_file) |
| { |
| unsigned char local_digest[APR_MD5_DIGESTSIZE]; |
| SVN_ERR(svn_io_file_checksum(local_digest, latest_base, pool)); |
| hex_digest = svn_md5_digest_to_cstring(local_digest, pool); |
| } |
| } |
| |
| /* Oh, and recursing at this point isn't really sensible. */ |
| recurse = FALSE; |
| } |
| else |
| { |
| /* PATH must be a dir */ |
| base_name = SVN_WC_ENTRY_THIS_DIR; |
| } |
| |
| |
| /* Append a log command to set (overwrite) the 'committed-rev', |
| 'committed-date', 'last-author', and possibly 'checksum' |
| attributes in the entry. |
| |
| Note: it's important that this log command come *before* the |
| LOG_COMMITTED command, because log_do_committed() might actually |
| remove the entry! */ |
| if (rev_date) |
| { |
| tmp_entry.cmt_rev = new_revnum; |
| SVN_ERR(svn_time_from_cstring(&tmp_entry.cmt_date, rev_date, pool)); |
| modify_flags |= SVN_WC__ENTRY_MODIFY_CMT_REV |
| | SVN_WC__ENTRY_MODIFY_CMT_DATE; |
| } |
| |
| if (rev_author) |
| { |
| tmp_entry.cmt_rev = new_revnum; |
| tmp_entry.cmt_author = rev_author; |
| modify_flags |= SVN_WC__ENTRY_MODIFY_CMT_REV |
| | SVN_WC__ENTRY_MODIFY_CMT_AUTHOR; |
| } |
| |
| if (hex_digest) |
| { |
| tmp_entry.checksum = hex_digest; |
| modify_flags |= SVN_WC__ENTRY_MODIFY_CHECKSUM; |
| } |
| |
| SVN_ERR(svn_wc__loggy_entry_modify(&logtags, adm_access, |
| base_name, &tmp_entry, modify_flags, |
| pool)); |
| |
| if (remove_lock) |
| SVN_ERR(svn_wc__loggy_delete_lock(&logtags, adm_access, |
| base_name, pool)); |
| |
| /* Regardless of whether it's a file or dir, the "main" logfile |
| contains a command to bump the revision attribute (and |
| timestamp). */ |
| SVN_ERR(svn_wc__loggy_committed(&logtags, adm_access, |
| base_name, new_revnum, pool)); |
| |
| |
| /* Do wcprops in the same log txn as revision, etc. */ |
| if (wcprop_changes && (wcprop_changes->nelts > 0)) |
| { |
| int i; |
| |
| for (i = 0; i < wcprop_changes->nelts; i++) |
| { |
| svn_prop_t *prop = APR_ARRAY_IDX(wcprop_changes, i, svn_prop_t *); |
| |
| SVN_ERR(svn_wc__loggy_modify_wcprop |
| (&logtags, adm_access, |
| base_name, prop->name, |
| prop->value ? prop->value->data : NULL, |
| pool)); |
| } |
| } |
| |
| /* Write our accumulation of log entries into a log file */ |
| SVN_ERR(svn_wc__write_log(adm_access, 0, logtags, pool)); |
| |
| /* Run the log file we just created. */ |
| SVN_ERR(svn_wc__run_log(adm_access, NULL, pool)); |
| |
| if (recurse) |
| { |
| apr_hash_t *entries; |
| apr_hash_index_t *hi; |
| apr_pool_t *subpool = svn_pool_create(pool); |
| |
| /* Read PATH's entries; this is the absolute path. */ |
| SVN_ERR(svn_wc_entries_read(&entries, adm_access, TRUE, pool)); |
| |
| /* Recursively loop over all children. */ |
| for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi)) |
| { |
| const void *key; |
| void *val; |
| const char *name; |
| const svn_wc_entry_t *current_entry; |
| const char *this_path; |
| svn_wc_adm_access_t *child_access; |
| |
| svn_pool_clear(subpool); |
| |
| apr_hash_this(hi, &key, NULL, &val); |
| name = key; |
| current_entry = val; |
| |
| /* Ignore the "this dir" entry. */ |
| if (! strcmp(name, SVN_WC_ENTRY_THIS_DIR)) |
| continue; |
| |
| /* Create child path by telescoping the main path. */ |
| this_path = svn_path_join(path, name, subpool); |
| |
| if (current_entry->kind == svn_node_dir) |
| SVN_ERR(svn_wc_adm_retrieve(&child_access, adm_access, this_path, |
| subpool)); |
| else |
| child_access = adm_access; |
| |
| /* Recurse, but only allow further recursion if the child is |
| a directory. Pass null for wcprop_changes, because the |
| ones present in the current call are only applicable to |
| this one committed item. */ |
| SVN_ERR(svn_wc_process_committed2 |
| (this_path, child_access, |
| (current_entry->kind == svn_node_dir) ? TRUE : FALSE, |
| new_revnum, rev_date, rev_author, NULL, FALSE, subpool)); |
| } |
| |
| svn_pool_destroy(subpool); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| svn_error_t * |
| svn_wc_process_committed2(const char *path, |
| svn_wc_adm_access_t *adm_access, |
| svn_boolean_t recurse, |
| svn_revnum_t new_revnum, |
| const char *rev_date, |
| const char *rev_author, |
| apr_array_header_t *wcprop_changes, |
| svn_boolean_t remove_lock, |
| apr_pool_t *pool) |
| { |
| return svn_wc_process_committed3(path, adm_access, recurse, new_revnum, |
| rev_date, rev_author, wcprop_changes, |
| remove_lock, NULL, pool); |
| } |
| |
| svn_error_t * |
| svn_wc_process_committed(const char *path, |
| svn_wc_adm_access_t *adm_access, |
| svn_boolean_t recurse, |
| svn_revnum_t new_revnum, |
| const char *rev_date, |
| const char *rev_author, |
| apr_array_header_t *wcprop_changes, |
| apr_pool_t *pool) |
| { |
| return svn_wc_process_committed2(path, adm_access, recurse, new_revnum, |
| rev_date, rev_author, wcprop_changes, |
| FALSE, pool); |
| } |
| |
| /* Remove FILE if it exists and is a file. If it does not exist, do |
| nothing. If it is not a file, error. */ |
| static svn_error_t * |
| remove_file_if_present(const char *file, apr_pool_t *pool) |
| { |
| svn_error_t *err; |
| |
| /* Try to remove the file. */ |
| err = svn_io_remove_file(file, pool); |
| |
| /* Ignore file not found error. */ |
| if (err && APR_STATUS_IS_ENOENT(err->apr_err)) |
| { |
| svn_error_clear(err); |
| err = SVN_NO_ERROR; |
| } |
| |
| return err; |
| } |
| |
| |
| /* Recursively mark a tree ADM_ACCESS with a SCHEDULE and/or COPIED |
| flag, depending on the state of MODIFY_FLAGS. */ |
| static svn_error_t * |
| mark_tree(svn_wc_adm_access_t *adm_access, |
| apr_uint32_t modify_flags, |
| svn_wc_schedule_t schedule, |
| svn_boolean_t copied, |
| svn_cancel_func_t cancel_func, |
| void *cancel_baton, |
| svn_wc_notify_func2_t notify_func, |
| void *notify_baton, |
| apr_pool_t *pool) |
| { |
| apr_pool_t *subpool = svn_pool_create(pool); |
| apr_hash_t *entries; |
| apr_hash_index_t *hi; |
| const svn_wc_entry_t *entry; |
| |
| /* Read the entries file for this directory. */ |
| SVN_ERR(svn_wc_entries_read(&entries, adm_access, FALSE, pool)); |
| |
| /* Mark each entry in the entries file. */ |
| for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi)) |
| { |
| const char *fullpath; |
| const void *key; |
| void *val; |
| const char *base_name; |
| svn_wc_entry_t *dup_entry; |
| |
| /* Clear our per-iteration pool. */ |
| svn_pool_clear(subpool); |
| |
| /* Get the next entry */ |
| apr_hash_this(hi, &key, NULL, &val); |
| entry = val; |
| |
| /* Skip "this dir". */ |
| if (! strcmp((const char *)key, SVN_WC_ENTRY_THIS_DIR)) |
| continue; |
| |
| base_name = key; |
| fullpath = svn_path_join(svn_wc_adm_access_path(adm_access), base_name, |
| subpool); |
| |
| /* If this is a directory, recurse. */ |
| if (entry->kind == svn_node_dir) |
| { |
| svn_wc_adm_access_t *child_access; |
| SVN_ERR(svn_wc_adm_retrieve(&child_access, adm_access, fullpath, |
| subpool)); |
| SVN_ERR(mark_tree(child_access, modify_flags, |
| schedule, copied, |
| cancel_func, cancel_baton, |
| notify_func, notify_baton, |
| subpool)); |
| } |
| |
| /* Need to duplicate the entry because we are changing the scheduling */ |
| dup_entry = svn_wc_entry_dup(entry, subpool); |
| dup_entry->schedule = schedule; |
| dup_entry->copied = copied; |
| SVN_ERR(svn_wc__entry_modify(adm_access, base_name, dup_entry, |
| modify_flags, TRUE, subpool)); |
| |
| /* Tell someone what we've done. */ |
| if (schedule == svn_wc_schedule_delete && notify_func != NULL) |
| (*notify_func)(notify_baton, |
| svn_wc_create_notify(fullpath, svn_wc_notify_delete, |
| subpool), pool); |
| } |
| |
| /* Handle "this dir" for states that need it done post-recursion. */ |
| entry = apr_hash_get(entries, SVN_WC_ENTRY_THIS_DIR, APR_HASH_KEY_STRING); |
| |
| /* Uncommitted directories (schedule add) that are to be scheduled for |
| deletion are a special case, they don't need to be changed as they |
| will be removed from their parent's entry list. */ |
| if (! (entry->schedule == svn_wc_schedule_add |
| && schedule == svn_wc_schedule_delete)) |
| { |
| /* Need to duplicate the entry because we are changing the scheduling */ |
| svn_wc_entry_t *dup_entry = svn_wc_entry_dup(entry, subpool); |
| if (modify_flags & SVN_WC__ENTRY_MODIFY_SCHEDULE) |
| dup_entry->schedule = schedule; |
| if (modify_flags & SVN_WC__ENTRY_MODIFY_COPIED) |
| dup_entry->copied = copied; |
| SVN_ERR(svn_wc__entry_modify(adm_access, NULL, dup_entry, modify_flags, |
| TRUE, subpool)); |
| } |
| |
| /* Destroy our per-iteration pool. */ |
| svn_pool_destroy(subpool); |
| return SVN_NO_ERROR; |
| } |
| |
| /* Remove/erase PATH from the working copy. This involves deleting PATH |
| * from the physical filesystem. PATH is assumed to be an unversioned file |
| * or directory. |
| * |
| * If CANCEL_FUNC is non-null, invoke it with CANCEL_BATON at various |
| * points, return any error immediately. |
| */ |
| static svn_error_t * |
| erase_unversioned_from_wc(const char *path, |
| svn_cancel_func_t cancel_func, |
| void *cancel_baton, |
| apr_pool_t *pool) |
| { |
| svn_node_kind_t kind; |
| |
| SVN_ERR(svn_io_check_path(path, &kind, pool)); |
| switch (kind) |
| { |
| case svn_node_none: |
| return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL, |
| _("'%s' does not exist"), |
| svn_path_local_style(path, pool)); |
| break; |
| |
| default: |
| /* ### TODO: what do we do here? To handle Unix symlinks we |
| fallthrough to svn_node_file... gulp! */ |
| |
| case svn_node_file: |
| SVN_ERR(svn_io_remove_file(path, pool)); |
| break; |
| |
| case svn_node_dir: |
| /* ### It would be more in the spirit of things to feed the |
| ### cancellation check through to svn_io_remove_dir()... */ |
| if (cancel_func) |
| SVN_ERR(cancel_func(cancel_baton)); |
| |
| SVN_ERR(svn_io_remove_dir(path, pool)); |
| |
| if (cancel_func) |
| SVN_ERR(cancel_func(cancel_baton)); |
| |
| break; |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Remove/erase PATH from the working copy. For files this involves |
| * deletion from the physical filesystem. For directories it involves the |
| * deletion from the filesystem of all unversioned children, and all |
| * versioned children that are files. By the time we get here, added but |
| * not committed items will have been scheduled for deletion which means |
| * they have become unversioned. |
| * |
| * The result is that all that remains are versioned directories, each with |
| * its .svn directory and .svn contents. |
| * |
| * If CANCEL_FUNC is non-null, invoke it with CANCEL_BATON at various |
| * points, return any error immediately. |
| * |
| * KIND is the node kind appropriate for PATH |
| */ |
| static svn_error_t * |
| erase_from_wc(const char *path, |
| svn_wc_adm_access_t *adm_access, |
| svn_node_kind_t kind, |
| svn_cancel_func_t cancel_func, |
| void *cancel_baton, |
| apr_pool_t *pool) |
| { |
| /* Check that the item exists in the wc. */ |
| svn_node_kind_t wc_kind; |
| SVN_ERR(svn_io_check_path(path, &wc_kind, pool)); |
| if (wc_kind == svn_node_none) |
| return SVN_NO_ERROR; |
| |
| if (cancel_func) |
| SVN_ERR(cancel_func(cancel_baton)); |
| |
| switch (kind) |
| { |
| default: |
| /* ### TODO: what do we do here? */ |
| break; |
| |
| case svn_node_file: |
| SVN_ERR(svn_io_remove_file(path, pool)); |
| break; |
| |
| case svn_node_dir: |
| { |
| apr_hash_t *ver, *unver; |
| apr_hash_index_t *hi; |
| svn_wc_adm_access_t *dir_access; |
| |
| /* ### Suspect that an iteration or recursion subpool would be |
| good here. */ |
| |
| /* First handle the versioned items, this is better (probably) than |
| simply using svn_io_get_dirents2 for everything as it avoids the |
| need to do svn_io_check_path on each versioned item */ |
| SVN_ERR(svn_wc_adm_retrieve(&dir_access, adm_access, path, pool)); |
| SVN_ERR(svn_wc_entries_read(&ver, dir_access, FALSE, pool)); |
| for (hi = apr_hash_first(pool, ver); hi; hi = apr_hash_next(hi)) |
| { |
| const void *key; |
| void *val; |
| const char *name; |
| const svn_wc_entry_t *entry; |
| const char *down_path; |
| |
| apr_hash_this(hi, &key, NULL, &val); |
| name = key; |
| entry = val; |
| |
| if (!strcmp(name, SVN_WC_ENTRY_THIS_DIR)) |
| continue; |
| |
| down_path = svn_path_join(path, name, pool); |
| SVN_ERR(erase_from_wc(down_path, dir_access, entry->kind, |
| cancel_func, cancel_baton, pool)); |
| } |
| |
| /* Now handle any remaining unversioned items */ |
| SVN_ERR(svn_io_get_dirents2(&unver, path, pool)); |
| for (hi = apr_hash_first(pool, unver); hi; hi = apr_hash_next(hi)) |
| { |
| const void *key; |
| const char *name; |
| const char *down_path; |
| |
| apr_hash_this(hi, &key, NULL, NULL); |
| name = key; |
| |
| /* The admin directory will show up, we don't want to delete it */ |
| if (svn_wc_is_adm_dir(name, pool)) |
| continue; |
| |
| /* Versioned directories will show up, don't delete those either */ |
| if (apr_hash_get(ver, name, APR_HASH_KEY_STRING)) |
| continue; |
| |
| down_path = svn_path_join(path, name, pool); |
| SVN_ERR(erase_unversioned_from_wc |
| (down_path, cancel_func, cancel_baton, pool)); |
| } |
| } |
| /* ### TODO: move this dir into parent's .svn area */ |
| break; |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_error_t * |
| svn_wc_delete2(const char *path, |
| svn_wc_adm_access_t *adm_access, |
| svn_cancel_func_t cancel_func, |
| void *cancel_baton, |
| svn_wc_notify_func2_t notify_func, |
| void *notify_baton, |
| apr_pool_t *pool) |
| { |
| svn_wc_adm_access_t *dir_access; |
| const svn_wc_entry_t *entry; |
| const char *parent, *base_name; |
| svn_boolean_t was_schedule; |
| svn_node_kind_t was_kind; |
| svn_boolean_t was_copied; |
| svn_boolean_t was_deleted = FALSE; /* Silence a gcc uninitialized warning */ |
| |
| SVN_ERR(svn_wc_adm_probe_try3(&dir_access, adm_access, path, |
| TRUE, -1, cancel_func, cancel_baton, pool)); |
| if (dir_access) |
| SVN_ERR(svn_wc_entry(&entry, path, dir_access, FALSE, pool)); |
| else |
| entry = NULL; |
| |
| if (!entry) |
| return erase_unversioned_from_wc(path, cancel_func, cancel_baton, pool); |
| |
| /* Note: Entries caching? What happens to this entry when the entries |
| file is updated? Lets play safe and copy the values */ |
| was_schedule = entry->schedule; |
| was_kind = entry->kind; |
| was_copied = entry->copied; |
| |
| svn_path_split(path, &parent, &base_name, pool); |
| |
| if (was_kind == svn_node_dir) |
| { |
| svn_wc_adm_access_t *parent_access; |
| apr_hash_t *entries; |
| const svn_wc_entry_t *entry_in_parent; |
| |
| /* The deleted state is only available in the entry in parent's |
| entries file */ |
| SVN_ERR(svn_wc_adm_retrieve(&parent_access, adm_access, parent, pool)); |
| SVN_ERR(svn_wc_entries_read(&entries, parent_access, TRUE, pool)); |
| entry_in_parent = apr_hash_get(entries, base_name, APR_HASH_KEY_STRING); |
| was_deleted = entry_in_parent ? entry_in_parent->deleted : FALSE; |
| |
| if (was_schedule == svn_wc_schedule_add && !was_deleted) |
| { |
| /* Deleting a directory that has been added but not yet |
| committed is easy, just remove the administrative dir. */ |
| |
| if (dir_access != adm_access) |
| { |
| SVN_ERR(svn_wc_remove_from_revision_control |
| (dir_access, SVN_WC_ENTRY_THIS_DIR, FALSE, FALSE, |
| cancel_func, cancel_baton, pool)); |
| } |
| else |
| { |
| /* adm_probe_retrieve returned the parent access baton, |
| which is the same access baton that we came in here |
| with! this means we're dealing with a missing item |
| that's scheduled for addition. Easiest to just |
| remove the entry. */ |
| svn_wc__entry_remove(entries, base_name); |
| SVN_ERR(svn_wc__entries_write(entries, parent_access, pool)); |
| } |
| } |
| else |
| { |
| /* if adm_probe_retrieve returned the parent access baton, |
| (which is the same access baton that we came in here |
| with), this means we're dealing with a missing directory. |
| So there's no tree to mark for deletion. Instead, the |
| next phase of code will simply schedule the directory for |
| deletion in its parent. */ |
| if (dir_access != adm_access) |
| { |
| /* Recursively mark a whole tree for deletion. */ |
| SVN_ERR(mark_tree(dir_access, SVN_WC__ENTRY_MODIFY_SCHEDULE, |
| svn_wc_schedule_delete, FALSE, |
| cancel_func, cancel_baton, |
| notify_func, notify_baton, |
| pool)); |
| } |
| } |
| } |
| |
| if (!(was_kind == svn_node_dir && was_schedule == svn_wc_schedule_add |
| && !was_deleted)) |
| { |
| /* We need to mark this entry for deletion in its parent's entries |
| file, so we split off base_name from the parent path, then fold in |
| the addition of a delete flag. */ |
| svn_stringbuf_t *log_accum = svn_stringbuf_create("", pool); |
| svn_wc_entry_t tmp_entry; |
| |
| /* Edit the entry to reflect the now deleted state. |
| entries.c:fold_entry() clears the values of copied, copyfrom_rev |
| and copyfrom_url. */ |
| tmp_entry.schedule = svn_wc_schedule_delete; |
| SVN_ERR(svn_wc__loggy_entry_modify(&log_accum, adm_access, |
| base_name, &tmp_entry, |
| SVN_WC__ENTRY_MODIFY_SCHEDULE, |
| pool)); |
| |
| /* is it a replacement with history? */ |
| if (was_schedule == svn_wc_schedule_replace && was_copied) |
| { |
| const char *text_base = |
| svn_wc__text_base_path(base_name, FALSE, pool); |
| const char *text_revert = |
| svn_wc__text_revert_path(base_name, FALSE, pool); |
| const char *prop_base, *prop_revert; |
| |
| SVN_ERR(svn_wc__prop_base_path(&prop_base, base_name, |
| was_kind, FALSE, pool)); |
| SVN_ERR(svn_wc__prop_revert_path(&prop_revert, base_name, |
| was_kind, FALSE, pool)); |
| |
| if (was_kind != svn_node_dir) /* Dirs don't have text-bases */ |
| /* Restore the original text-base */ |
| SVN_ERR(svn_wc__loggy_move(&log_accum, NULL, adm_access, |
| text_revert, text_base, |
| FALSE, pool)); |
| |
| SVN_ERR(svn_wc__loggy_move(&log_accum, NULL, |
| adm_access, prop_revert, prop_base, |
| TRUE, pool)); |
| } |
| if (was_schedule == svn_wc_schedule_add) |
| { |
| /* remove the properties file */ |
| const char *svn_prop_file_path; |
| SVN_ERR(svn_wc__prop_path(&svn_prop_file_path, base_name, |
| was_kind, FALSE, pool)); |
| SVN_ERR(svn_wc__loggy_remove(&log_accum, adm_access, |
| svn_prop_file_path, pool)); |
| } |
| |
| |
| SVN_ERR(svn_wc__write_log(adm_access, 0, log_accum, pool)); |
| |
| SVN_ERR(svn_wc__run_log(adm_access, NULL, pool)); |
| |
| } |
| |
| /* Report the deletion to the caller. */ |
| if (notify_func != NULL) |
| (*notify_func)(notify_baton, |
| svn_wc_create_notify(path, svn_wc_notify_delete, |
| pool), pool); |
| |
| /* By the time we get here, anything that was scheduled to be added has |
| become unversioned */ |
| if (was_schedule == svn_wc_schedule_add) |
| SVN_ERR(erase_unversioned_from_wc |
| (path, cancel_func, cancel_baton, pool)); |
| else |
| SVN_ERR(erase_from_wc(path, adm_access, was_kind, |
| cancel_func, cancel_baton, pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| svn_error_t * |
| svn_wc_delete(const char *path, |
| svn_wc_adm_access_t *adm_access, |
| svn_cancel_func_t cancel_func, |
| void *cancel_baton, |
| svn_wc_notify_func_t notify_func, |
| void *notify_baton, |
| apr_pool_t *pool) |
| { |
| svn_wc__compat_notify_baton_t nb; |
| |
| nb.func = notify_func; |
| nb.baton = notify_baton; |
| |
| return svn_wc_delete2(path, adm_access, cancel_func, cancel_baton, |
| svn_wc__compat_call_notify_func, &nb, pool); |
| } |
| |
| svn_error_t * |
| svn_wc_get_ancestry(char **url, |
| svn_revnum_t *rev, |
| const char *path, |
| svn_wc_adm_access_t *adm_access, |
| apr_pool_t *pool) |
| { |
| const svn_wc_entry_t *ent; |
| |
| SVN_ERR(svn_wc_entry(&ent, path, adm_access, FALSE, pool)); |
| if (! ent) |
| return svn_error_createf(SVN_ERR_UNVERSIONED_RESOURCE, NULL, |
| _("'%s' is not under version control"), |
| svn_path_local_style(path, pool)); |
| |
| if (url) |
| *url = apr_pstrdup(pool, ent->url); |
| |
| if (rev) |
| *rev = ent->revision; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_error_t * |
| svn_wc_add2(const char *path, |
| svn_wc_adm_access_t *parent_access, |
| const char *copyfrom_url, |
| svn_revnum_t copyfrom_rev, |
| svn_cancel_func_t cancel_func, |
| void *cancel_baton, |
| svn_wc_notify_func2_t notify_func, |
| void *notify_baton, |
| apr_pool_t *pool) |
| { |
| const char *parent_dir, *base_name; |
| const svn_wc_entry_t *orig_entry, *parent_entry; |
| svn_wc_entry_t tmp_entry; |
| svn_boolean_t is_replace = FALSE; |
| svn_node_kind_t kind; |
| apr_uint32_t modify_flags = 0; |
| svn_wc_adm_access_t *adm_access; |
| |
| SVN_ERR(svn_path_check_valid(path, pool)); |
| |
| /* Make sure something's there. */ |
| SVN_ERR(svn_io_check_path(path, &kind, pool)); |
| if (kind == svn_node_none) |
| return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, |
| _("'%s' not found"), |
| svn_path_local_style(path, pool)); |
| if (kind == svn_node_unknown) |
| return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, |
| _("Unsupported node kind for path '%s'"), |
| svn_path_local_style(path, pool)); |
| |
| /* Get the original entry for this path if one exists (perhaps |
| this is actually a replacement of a previously deleted thing). |
| |
| Note that this is one of the few functions that is allowed to see |
| 'deleted' entries; it's totally fine to have an entry that is |
| scheduled for addition and still previously 'deleted'. */ |
| SVN_ERR(svn_wc_adm_probe_try3(&adm_access, parent_access, path, |
| TRUE, copyfrom_url != NULL ? -1 : 0, |
| cancel_func, cancel_baton, pool)); |
| if (adm_access) |
| SVN_ERR(svn_wc_entry(&orig_entry, path, adm_access, TRUE, pool)); |
| else |
| orig_entry = NULL; |
| |
| /* You can only add something that is not in revision control, or |
| that is slated for deletion from revision control, or has been |
| previously 'deleted', unless, of course, you're specifying an |
| addition with -history-; then it's okay for the object to be |
| under version control already; it's not really new. */ |
| if (orig_entry) |
| { |
| if ((! copyfrom_url) |
| && (orig_entry->schedule != svn_wc_schedule_delete) |
| && (! orig_entry->deleted)) |
| { |
| return svn_error_createf |
| (SVN_ERR_ENTRY_EXISTS, NULL, |
| _("'%s' is already under version control"), |
| svn_path_local_style(path, pool)); |
| } |
| else if (orig_entry->kind != kind) |
| { |
| /* ### todo: At some point, we obviously don't want to block |
| replacements where the node kind changes. When this |
| happens, svn_wc_revert2() needs to learn how to revert |
| this situation. At present we are using a specific node-change |
| error so that clients can detect it. */ |
| return svn_error_createf |
| (SVN_ERR_WC_NODE_KIND_CHANGE, NULL, |
| _("Can't replace '%s' with a node of a differing type; " |
| "the deletion must be committed and the parent updated " |
| "before adding '%s'"), |
| svn_path_local_style(path, pool), |
| svn_path_local_style(path, pool)); |
| } |
| if (orig_entry->schedule == svn_wc_schedule_delete) |
| is_replace = TRUE; |
| } |
| |
| /* Split off the base_name from the parent directory. */ |
| svn_path_split(path, &parent_dir, &base_name, pool); |
| SVN_ERR(svn_wc_entry(&parent_entry, parent_dir, parent_access, FALSE, |
| pool)); |
| if (! parent_entry) |
| return svn_error_createf |
| (SVN_ERR_ENTRY_NOT_FOUND, NULL, |
| _("Can't find parent directory's entry while trying to add '%s'"), |
| svn_path_local_style(path, pool)); |
| if (parent_entry->schedule == svn_wc_schedule_delete) |
| return svn_error_createf |
| (SVN_ERR_WC_SCHEDULE_CONFLICT, NULL, |
| _("Can't add '%s' to a parent directory scheduled for deletion"), |
| svn_path_local_style(path, pool)); |
| |
| /* Init the modify flags. */ |
| modify_flags = SVN_WC__ENTRY_MODIFY_SCHEDULE | SVN_WC__ENTRY_MODIFY_KIND; |
| if (! (is_replace || copyfrom_url)) |
| modify_flags |= SVN_WC__ENTRY_MODIFY_REVISION; |
| |
| /* If a copy ancestor was given, make sure the copyfrom URL is in the same |
| repository (if possible) and put the proper ancestry info in the new |
| entry */ |
| if (copyfrom_url) |
| { |
| if (parent_entry->repos |
| && ! svn_path_is_ancestor(parent_entry->repos, copyfrom_url)) |
| return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, |
| _("The URL '%s' has a different repository " |
| "root than its parent"), copyfrom_url); |
| tmp_entry.copyfrom_url = copyfrom_url; |
| tmp_entry.copyfrom_rev = copyfrom_rev; |
| tmp_entry.copied = TRUE; |
| modify_flags |= SVN_WC__ENTRY_MODIFY_COPYFROM_URL; |
| modify_flags |= SVN_WC__ENTRY_MODIFY_COPYFROM_REV; |
| modify_flags |= SVN_WC__ENTRY_MODIFY_COPIED; |
| } |
| |
| /* If this is a replacement we want to remove the checksum so it is not set |
| * to the old value. */ |
| if (is_replace) |
| { |
| tmp_entry.checksum = NULL; |
| modify_flags |= SVN_WC__ENTRY_MODIFY_CHECKSUM; |
| } |
| |
| tmp_entry.revision = 0; |
| tmp_entry.kind = kind; |
| tmp_entry.schedule = svn_wc_schedule_add; |
| |
| /* Now, add the entry for this item to the parent_dir's |
| entries file, marking it for addition. */ |
| SVN_ERR(svn_wc__entry_modify(parent_access, base_name, &tmp_entry, |
| modify_flags, TRUE, pool)); |
| |
| |
| /* If this is a replacement without history, we need to reset the |
| properties for PATH. */ |
| if (orig_entry && (! copyfrom_url)) |
| { |
| const char *prop_path; |
| SVN_ERR(svn_wc__prop_path(&prop_path, path, |
| orig_entry->kind, FALSE, pool)); |
| SVN_ERR(remove_file_if_present(prop_path, pool)); |
| } |
| |
| if (kind == svn_node_dir) /* scheduling a directory for addition */ |
| { |
| if (! copyfrom_url) |
| { |
| const svn_wc_entry_t *p_entry; /* ### why not use parent_entry? */ |
| const char *new_url; |
| |
| /* Get the entry for this directory's parent. We need to snatch |
| the ancestor path out of there. */ |
| SVN_ERR(svn_wc_entry(&p_entry, parent_dir, parent_access, FALSE, |
| pool)); |
| |
| /* Derive the parent path for our new addition here. */ |
| new_url = svn_path_url_add_component(p_entry->url, base_name, pool); |
| |
| /* Make sure this new directory has an admistrative subdirectory |
| created inside of it */ |
| SVN_ERR(svn_wc_ensure_adm2(path, NULL, new_url, p_entry->repos, |
| 0, pool)); |
| } |
| else |
| { |
| /* When we are called with the copyfrom arguments set and with |
| the admin directory already in existence, then the dir will |
| contain the copyfrom settings. So we need to pass the |
| copyfrom arguments to the ensure call. */ |
| SVN_ERR(svn_wc_ensure_adm2(path, NULL, copyfrom_url, |
| parent_entry->repos, copyfrom_rev, |
| pool)); |
| } |
| |
| /* We want the locks to persist, so use the access baton's pool */ |
| if (! orig_entry || orig_entry->deleted) |
| { |
| apr_pool_t* access_pool = svn_wc_adm_access_pool(parent_access); |
| SVN_ERR(svn_wc_adm_open3(&adm_access, parent_access, path, |
| TRUE, copyfrom_url != NULL ? -1 : 0, |
| cancel_func, cancel_baton, |
| access_pool)); |
| } |
| |
| /* We're making the same mods we made above, but this time we'll |
| force the scheduling. Also make sure to undo the |
| 'incomplete' flag which svn_wc_ensure_adm2 sets by default. */ |
| modify_flags |= SVN_WC__ENTRY_MODIFY_FORCE; |
| modify_flags |= SVN_WC__ENTRY_MODIFY_INCOMPLETE; |
| tmp_entry.schedule = is_replace |
| ? svn_wc_schedule_replace |
| : svn_wc_schedule_add; |
| tmp_entry.incomplete = FALSE; |
| SVN_ERR(svn_wc__entry_modify(adm_access, NULL, &tmp_entry, |
| modify_flags, TRUE, pool)); |
| |
| if (copyfrom_url) |
| { |
| /* If this new directory has ancestry, it's not enough to |
| schedule it for addition with copyfrom args. We also |
| need to rewrite its ancestor-url, and rewrite the |
| ancestor-url of ALL its children! |
| |
| We're doing this because our current commit model (for |
| hysterical raisins, presumably) assumes an entry's URL is |
| correct before commit -- i.e. the URL is not tweaked in |
| the post-commit bumping process. We might want to change |
| this model someday. */ |
| |
| /* Figure out what the new url should be. */ |
| const char *new_url = |
| svn_path_url_add_component(parent_entry->url, base_name, pool); |
| |
| /* Change the entry urls recursively (but not the working rev). */ |
| SVN_ERR(svn_wc__do_update_cleanup(path, adm_access, TRUE, new_url, |
| parent_entry->repos, |
| SVN_INVALID_REVNUM, NULL, |
| NULL, FALSE, pool)); |
| |
| /* Recursively add the 'copied' existence flag as well! */ |
| SVN_ERR(mark_tree(adm_access, SVN_WC__ENTRY_MODIFY_COPIED, |
| svn_wc_schedule_normal, TRUE, |
| cancel_func, |
| cancel_baton, |
| NULL, NULL, /* N/A cuz we aren't deleting */ |
| pool)); |
| |
| /* Clean out the now-obsolete wcprops. */ |
| SVN_ERR(svn_wc__remove_wcprops(adm_access, NULL, TRUE, pool)); |
| } |
| } |
| |
| /* Report the addition to the caller. */ |
| if (notify_func != NULL) |
| { |
| svn_wc_notify_t *notify = svn_wc_create_notify(path, svn_wc_notify_add, |
| pool); |
| notify->kind = kind; |
| (*notify_func)(notify_baton, notify, pool); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_error_t * |
| svn_wc_add(const char *path, |
| svn_wc_adm_access_t *parent_access, |
| const char *copyfrom_url, |
| svn_revnum_t copyfrom_rev, |
| svn_cancel_func_t cancel_func, |
| void *cancel_baton, |
| svn_wc_notify_func_t notify_func, |
| void *notify_baton, |
| apr_pool_t *pool) |
| { |
| svn_wc__compat_notify_baton_t nb; |
| |
| nb.func = notify_func; |
| nb.baton = notify_baton; |
| |
| return svn_wc_add2(path, parent_access, copyfrom_url, copyfrom_rev, |
| cancel_func, cancel_baton, |
| svn_wc__compat_call_notify_func, &nb, pool); |
| } |
| /* Thoughts on Reversion. |
| |
| What does is mean to revert a given PATH in a tree? We'll |
| consider things by their modifications. |
| |
| Adds |
| |
| - For files, svn_wc_remove_from_revision_control(), baby. |
| |
| - Added directories may contain nothing but added children, and |
| reverting the addition of a directory necessarily means reverting |
| the addition of all the directory's children. Again, |
| svn_wc_remove_from_revision_control() should do the trick. |
| |
| Deletes |
| |
| - Restore properties to their unmodified state. |
| |
| - For files, restore the pristine contents, and reset the schedule |
| to 'normal'. |
| |
| - For directories, reset the schedule to 'normal'. All children |
| of a directory marked for deletion must also be marked for |
| deletion, but it's okay for those children to remain deleted even |
| if their parent directory is restored. That's what the |
| recursive flag is for. |
| |
| Replaces |
| |
| - Restore properties to their unmodified state. |
| |
| - For files, restore the pristine contents, and reset the schedule |
| to 'normal'. |
| |
| - For directories, reset the schedule to normal. A replaced |
| directory can have deleted children (left over from the initial |
| deletion), replaced children (children of the initial deletion |
| now re-added), and added children (new entries under the |
| replaced directory). Since this is technically an addition, it |
| necessitates recursion. |
| |
| Modifications |
| |
| - Restore properties and, for files, contents to their unmodified |
| state. |
| |
| */ |
| |
| |
| /* Revert ENTRY for NAME in directory represented by ADM_ACCESS. Sets |
| *REVERTED to TRUE if something actually is reverted. |
| |
| Use SVN_WC_ENTRY_THIS_DIR as NAME for reverting ADM_ACCESS directory |
| itself. |
| |
| Use POOL for any temporary allocations.*/ |
| static svn_error_t * |
| revert_admin_things(svn_wc_adm_access_t *adm_access, |
| const char *name, |
| const svn_wc_entry_t *entry, |
| svn_boolean_t *reverted, |
| svn_boolean_t use_commit_times, |
| apr_pool_t *pool) |
| { |
| const char *fullpath; |
| /* If true, force reinstallation of working file. */ |
| svn_boolean_t reinstall_working = FALSE; |
| svn_wc_entry_t tmp_entry; |
| apr_uint32_t flags = 0; |
| svn_stringbuf_t *log_accum = svn_stringbuf_create("", pool); |
| apr_hash_t *baseprops = NULL; |
| const char *adm_path = svn_wc_adm_access_path(adm_access); |
| |
| /* Build the full path of the thing we're reverting. */ |
| fullpath = svn_wc_adm_access_path(adm_access); |
| if (strcmp(name, SVN_WC_ENTRY_THIS_DIR) != 0) |
| fullpath = svn_path_join(fullpath, name, pool); |
| |
| /* Deal with properties. */ |
| |
| if (entry->schedule == svn_wc_schedule_replace) |
| { |
| const char *rprop; |
| svn_node_kind_t kind; |
| |
| /* Use the revertpath as the new propsbase if it exists. */ |
| SVN_ERR(svn_wc__prop_revert_path(&rprop, fullpath, entry->kind, FALSE, |
| pool)); |
| SVN_ERR(svn_io_check_path(rprop, &kind, pool)); |
| if (kind == svn_node_file) |
| { |
| baseprops = apr_hash_make(pool); |
| SVN_ERR(svn_wc__load_prop_file(rprop, baseprops, pool)); |
| /* Ensure the revert propfile gets removed. */ |
| SVN_ERR(svn_wc__loggy_remove |
| (&log_accum, adm_access, |
| svn_path_is_child(adm_path, rprop, pool), pool)); |
| *reverted = TRUE; |
| } |
| } |
| |
| /* If not schedule replace, or no revert props, use the normal |
| base-props and working props. */ |
| if (! baseprops) |
| { |
| svn_boolean_t modified; |
| |
| /* Check for prop changes. */ |
| SVN_ERR(svn_wc_props_modified_p(&modified, fullpath, adm_access, |
| pool)); |
| if (modified) |
| { |
| apr_array_header_t *propchanges; |
| |
| /* Get the full list of property changes and see if any magic |
| properties were changed. */ |
| SVN_ERR(svn_wc_get_prop_diffs(&propchanges, &baseprops, fullpath, |
| adm_access, pool)); |
| |
| /* Determine if any of the propchanges are the "magic" ones that |
| might require changing the working file. */ |
| reinstall_working = svn_wc__has_magic_property(propchanges); |
| |
| } |
| } |
| |
| /* Reinstall props if we need to. Only rewrite the baseprops, |
| if we're reverting a replacement. This is just an optimization. */ |
| if (baseprops) |
| SVN_ERR(svn_wc__install_props(&log_accum, adm_access, name, baseprops, |
| baseprops, |
| entry->schedule == svn_wc_schedule_replace, |
| pool)); |
| |
| /* Clean up the copied state if this is a replacement. */ |
| if (entry->schedule == svn_wc_schedule_replace |
| && entry->copied) |
| { |
| flags |= SVN_WC__ENTRY_MODIFY_COPIED; |
| tmp_entry.copied = FALSE; |
| *reverted = TRUE; |
| } |
| |
| /* Deal with the contents. */ |
| |
| if (entry->kind == svn_node_file) |
| { |
| svn_node_kind_t base_kind; |
| const char *base_thing; |
| svn_boolean_t tgt_modified; |
| |
| /* If the working file is missing, we need to reinstall it. */ |
| if (! reinstall_working) |
| { |
| svn_node_kind_t kind; |
| SVN_ERR(svn_io_check_path(fullpath, &kind, pool)); |
| if (kind == svn_node_none) |
| reinstall_working = TRUE; |
| } |
| |
| base_thing = svn_wc__text_base_path(name, FALSE, pool); |
| |
| /* Check for text base presence. */ |
| SVN_ERR(svn_io_check_path(svn_path_join(adm_path, |
| base_thing, pool), |
| &base_kind, pool)); |
| |
| if (base_kind != svn_node_file) |
| return svn_error_createf(APR_ENOENT, NULL, |
| _("Error restoring text for '%s'"), |
| svn_path_local_style(fullpath, pool)); |
| |
| /* Look for a revert base file. If it exists use it for |
| * the text base for the file. If it doesn't use the normal |
| * text base. */ |
| SVN_ERR(svn_wc__loggy_move |
| (&log_accum, &tgt_modified, adm_access, |
| svn_wc__text_revert_path(name, FALSE, pool), base_thing, |
| FALSE, pool)); |
| reinstall_working = reinstall_working || tgt_modified; |
| |
| /* A shortcut: since we will translate when reinstall_working, we |
| don't need to check if the working file is modified |
| */ |
| if (! reinstall_working) |
| SVN_ERR(svn_wc__text_modified_internal_p(&reinstall_working, |
| fullpath, FALSE, adm_access, |
| FALSE, pool)); |
| |
| if (reinstall_working) |
| { |
| /* If there are textual mods (or if the working file is |
| missing altogether), copy the text-base out into |
| the working copy, and update the timestamp in the entries |
| file. */ |
| SVN_ERR(svn_wc__loggy_copy(&log_accum, NULL, adm_access, |
| svn_wc__copy_translate, |
| base_thing, name, FALSE, pool)); |
| |
| /* Possibly set the timestamp to last-commit-time, rather |
| than the 'now' time that already exists. */ |
| if (use_commit_times && entry->cmt_date) |
| SVN_ERR(svn_wc__loggy_set_timestamp |
| (&log_accum, adm_access, |
| name, svn_time_to_cstring(entry->cmt_date, pool), |
| pool)); |
| |
| SVN_ERR(svn_wc__loggy_set_entry_timestamp_from_wc |
| (&log_accum, adm_access, name, SVN_WC__ENTRY_ATTR_TEXT_TIME, |
| pool)); |
| |
| *reverted = TRUE; |
| } |
| } |
| |
| /* Remove conflict state (and conflict files), if any. |
| Handle the three possible text conflict files. */ |
| if (entry->conflict_old) |
| { |
| flags |= SVN_WC__ENTRY_MODIFY_CONFLICT_OLD; |
| tmp_entry.conflict_old = NULL; |
| SVN_ERR(svn_wc__loggy_remove(&log_accum, adm_access, |
| entry->conflict_old, pool)); |
| } |
| |
| if (entry->conflict_new) |
| { |
| flags |= SVN_WC__ENTRY_MODIFY_CONFLICT_NEW; |
| tmp_entry.conflict_new = NULL; |
| SVN_ERR(svn_wc__loggy_remove(&log_accum, adm_access, |
| entry->conflict_new, pool)); |
| } |
| |
| if (entry->conflict_wrk) |
| { |
| flags |= SVN_WC__ENTRY_MODIFY_CONFLICT_WRK; |
| tmp_entry.conflict_wrk = NULL; |
| SVN_ERR(svn_wc__loggy_remove(&log_accum, adm_access, |
| entry->conflict_wrk, pool)); |
| } |
| |
| /* Remove the prej-file if the entry lists one (and it exists) */ |
| if (entry->prejfile) |
| { |
| flags |= SVN_WC__ENTRY_MODIFY_PREJFILE; |
| tmp_entry.prejfile = NULL; |
| SVN_ERR(svn_wc__loggy_remove(&log_accum, adm_access, |
| entry->prejfile, pool)); |
| } |
| |
| /* Reset schedule attribute to svn_wc_schedule_normal. */ |
| if (entry->schedule != svn_wc_schedule_normal) |
| { |
| flags |= SVN_WC__ENTRY_MODIFY_SCHEDULE; |
| tmp_entry.schedule = svn_wc_schedule_normal; |
| *reverted = TRUE; |
| } |
| |
| SVN_ERR(svn_wc__loggy_entry_modify(&log_accum, adm_access, name, |
| &tmp_entry, flags, pool)); |
| |
| /* Don't run log if nothing to change. */ |
| if (!svn_stringbuf_isempty(log_accum)) |
| { |
| SVN_ERR(svn_wc__write_log(adm_access, 0, log_accum, pool)); |
| SVN_ERR(svn_wc__run_log(adm_access, NULL, pool)); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_error_t * |
| svn_wc_revert2(const char *path, |
| svn_wc_adm_access_t *parent_access, |
| svn_boolean_t recursive, |
| svn_boolean_t use_commit_times, |
| svn_cancel_func_t cancel_func, |
| void *cancel_baton, |
| svn_wc_notify_func2_t notify_func, |
| void *notify_baton, |
| apr_pool_t *pool) |
| { |
| svn_node_kind_t kind; |
| const char *p_dir = NULL, *bname = NULL; |
| const svn_wc_entry_t *entry; |
| svn_boolean_t wc_root = FALSE, reverted = FALSE; |
| svn_wc_adm_access_t *dir_access; |
| |
| /* Check cancellation here, so recursive calls get checked early. */ |
| if (cancel_func) |
| SVN_ERR(cancel_func(cancel_baton)); |
| |
| SVN_ERR(svn_wc_adm_probe_retrieve(&dir_access, parent_access, path, pool)); |
| |
| /* Safeguard 1: is this a versioned resource? */ |
| SVN_ERR(svn_wc_entry(&entry, path, dir_access, FALSE, pool)); |
| if (! entry) |
| return svn_error_createf |
| (SVN_ERR_UNVERSIONED_RESOURCE, NULL, |
| _("Cannot revert: '%s' is not under version control"), |
| svn_path_local_style(path, pool)); |
| |
| /* Safeguard 1.5: is this a missing versioned directory? */ |
| if (entry->kind == svn_node_dir) |
| { |
| svn_node_kind_t disk_kind; |
| SVN_ERR(svn_io_check_path(path, &disk_kind, pool)); |
| if ((disk_kind != svn_node_dir) |
| && (entry->schedule != svn_wc_schedule_add)) |
| { |
| /* When the directory itself is missing, we can't revert |
| without hitting the network. Someday a '--force' option |
| will make this happen. For now, send notification of the |
| failure. */ |
| if (notify_func != NULL) |
| (*notify_func) |
| (notify_baton, |
| svn_wc_create_notify(path, svn_wc_notify_failed_revert, |
| pool), pool); |
| return SVN_NO_ERROR; |
| } |
| } |
| |
| /* Safeguard 2: can we handle this node kind? */ |
| if ((entry->kind != svn_node_file) && (entry->kind != svn_node_dir)) |
| return svn_error_createf |
| (SVN_ERR_UNSUPPORTED_FEATURE, NULL, |
| _("Cannot revert '%s': unsupported entry node kind"), |
| svn_path_local_style(path, pool)); |
| |
| /* Safeguard 3: can we deal with the node kind of PATH current in |
| the working copy? */ |
| SVN_ERR(svn_io_check_path(path, &kind, pool)); |
| if ((kind != svn_node_none) |
| && (kind != svn_node_file) |
| && (kind != svn_node_dir)) |
| return svn_error_createf |
| (SVN_ERR_UNSUPPORTED_FEATURE, NULL, |
| _("Cannot revert '%s': unsupported node kind in working copy"), |
| svn_path_local_style(path, pool)); |
| |
| /* For directories, determine if PATH is a WC root so that we can |
| tell if it is safe to split PATH into a parent directory and |
| basename. For files, we always do this split. */ |
| if (kind == svn_node_dir) |
| SVN_ERR(svn_wc_is_wc_root(&wc_root, path, dir_access, pool)); |
| if (! wc_root) |
| { |
| /* Split the base_name from the parent path. */ |
| svn_path_split(path, &p_dir, &bname, pool); |
| } |
| |
| |
| /* Additions. */ |
| if (entry->schedule == svn_wc_schedule_add) |
| { |
| /* Before removing item from revision control, notice if the |
| entry is in a 'deleted' state; this is critical for |
| directories, where this state only exists in its parent's |
| entry. */ |
| svn_boolean_t was_deleted = FALSE; |
| const char *parent, *basey; |
| |
| svn_path_split(path, &parent, &basey, pool); |
| if (entry->kind == svn_node_file) |
| { |
| was_deleted = entry->deleted; |
| SVN_ERR(svn_wc_remove_from_revision_control(parent_access, bname, |
| FALSE, FALSE, |
| cancel_func, |
| cancel_baton, |
| pool)); |
| } |
| else if (entry->kind == svn_node_dir) |
| { |
| apr_hash_t *entries; |
| const svn_wc_entry_t *parents_entry; |
| |
| /* |
| * We are trying to revert the current directory which is |
| * scheduled for addition. This is supposed to fail (Issue #854) |
| * */ |
| if (path[0] == '\0') |
| { |
| return svn_error_create(SVN_ERR_WC_INVALID_OP_ON_CWD, NULL, |
| _("Cannot revert addition of current directory; " |
| "please try again from the parent directory")); |
| } |
| |
| SVN_ERR(svn_wc_entries_read(&entries, parent_access, TRUE, pool)); |
| parents_entry = apr_hash_get(entries, basey, APR_HASH_KEY_STRING); |
| if (parents_entry) |
| was_deleted = parents_entry->deleted; |
| if (kind == svn_node_none) |
| { |
| /* Schedule add but missing, just remove the entry */ |
| svn_wc__entry_remove(entries, basey); |
| SVN_ERR(svn_wc__entries_write(entries, parent_access, pool)); |
| } |
| else |
| SVN_ERR(svn_wc_remove_from_revision_control |
| (dir_access, SVN_WC_ENTRY_THIS_DIR, FALSE, FALSE, |
| cancel_func, cancel_baton, pool)); |
| } |
| else /* Else it's `none', or something exotic like a symlink... */ |
| return svn_error_createf |
| (SVN_ERR_NODE_UNKNOWN_KIND, NULL, |
| _("Unknown or unexpected kind for path '%s'"), |
| svn_path_local_style(path, pool)); |
| |
| /* Recursivity is taken care of by svn_wc_remove_from_revision_control, |
| and we've definitely reverted PATH at this point. */ |
| recursive = FALSE; |
| reverted = TRUE; |
| |
| /* If the removed item was *also* in a 'deleted' state, make |
| sure we leave just a plain old 'deleted' entry behind in the |
| parent. */ |
| if (was_deleted) |
| { |
| svn_wc_entry_t *tmpentry; |
| tmpentry = apr_pcalloc(pool, sizeof(*tmpentry)); |
| tmpentry->kind = entry->kind; |
| tmpentry->deleted = TRUE; |
| |
| if (entry->kind == svn_node_dir) |
| SVN_ERR(svn_wc__entry_modify(parent_access, basey, tmpentry, |
| SVN_WC__ENTRY_MODIFY_KIND |
| | SVN_WC__ENTRY_MODIFY_DELETED, |
| TRUE, pool)); |
| else |
| SVN_ERR(svn_wc__entry_modify(parent_access, bname, tmpentry, |
| SVN_WC__ENTRY_MODIFY_KIND |
| | SVN_WC__ENTRY_MODIFY_DELETED, |
| TRUE, pool)); |
| } |
| } |
| /* Regular prop and text edit. */ |
| /* Deletions and replacements. */ |
| else if (entry->schedule == svn_wc_schedule_normal |
| || entry->schedule == svn_wc_schedule_delete |
| || entry->schedule == svn_wc_schedule_replace) |
| { |
| /* Revert the prop and text mods (if any). */ |
| if (entry->kind == svn_node_file) |
| SVN_ERR(revert_admin_things(parent_access, bname, |
| entry, &reverted, |
| use_commit_times, pool)); |
| |
| if (entry->kind == svn_node_dir) |
| SVN_ERR(revert_admin_things(dir_access, SVN_WC_ENTRY_THIS_DIR, entry, |
| &reverted, use_commit_times, pool)); |
| |
| /* Force recursion on replaced directories. */ |
| if ((entry->kind == svn_node_dir) |
| && (entry->schedule == svn_wc_schedule_replace)) |
| recursive = TRUE; |
| } |
| |
| /* If PATH was reverted, tell our client that. */ |
| if ((notify_func != NULL) && reverted) |
| (*notify_func)(notify_baton, |
| svn_wc_create_notify(path, svn_wc_notify_revert, pool), |
| pool); |
| |
| /* Finally, recurse if requested. */ |
| if (recursive && (entry->kind == svn_node_dir)) |
| { |
| apr_hash_t *entries; |
| apr_hash_index_t *hi; |
| apr_pool_t *subpool = svn_pool_create(pool); |
| |
| SVN_ERR(svn_wc_entries_read(&entries, dir_access, FALSE, pool)); |
| for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi)) |
| { |
| const void *key; |
| const char *keystring; |
| const char *full_entry_path; |
| |
| svn_pool_clear(subpool); |
| |
| /* Get the next entry */ |
| apr_hash_this(hi, &key, NULL, NULL); |
| keystring = key; |
| |
| /* Skip "this dir" */ |
| if (! strcmp(keystring, SVN_WC_ENTRY_THIS_DIR)) |
| continue; |
| |
| /* Add the entry name to FULL_ENTRY_PATH. */ |
| full_entry_path = svn_path_join(path, keystring, subpool); |
| |
| /* Revert the entry. */ |
| SVN_ERR(svn_wc_revert2(full_entry_path, dir_access, TRUE, |
| use_commit_times, |
| cancel_func, cancel_baton, |
| notify_func, notify_baton, subpool)); |
| } |
| |
| svn_pool_destroy(subpool); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| svn_error_t * |
| svn_wc_revert(const char *path, |
| svn_wc_adm_access_t *parent_access, |
| svn_boolean_t recursive, |
| svn_boolean_t use_commit_times, |
| svn_cancel_func_t cancel_func, |
| void *cancel_baton, |
| svn_wc_notify_func_t notify_func, |
| void *notify_baton, |
| apr_pool_t *pool) |
| { |
| svn_wc__compat_notify_baton_t nb; |
| |
| nb.func = notify_func; |
| nb.baton = notify_baton; |
| |
| return svn_wc_revert2(path, parent_access, recursive, use_commit_times, |
| cancel_func, cancel_baton, |
| svn_wc__compat_call_notify_func, &nb, pool); |
| } |
| |
| svn_error_t * |
| svn_wc_get_pristine_copy_path(const char *path, |
| const char **pristine_path, |
| apr_pool_t *pool) |
| { |
| *pristine_path = svn_wc__text_base_path(path, FALSE, pool); |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_error_t * |
| svn_wc_remove_from_revision_control(svn_wc_adm_access_t *adm_access, |
| const char *name, |
| svn_boolean_t destroy_wf, |
| svn_boolean_t instant_error, |
| svn_cancel_func_t cancel_func, |
| void *cancel_baton, |
| apr_pool_t *pool) |
| { |
| svn_error_t *err; |
| svn_boolean_t is_file; |
| svn_boolean_t left_something = FALSE; |
| apr_hash_t *entries = NULL; |
| const char *full_path = apr_pstrdup(pool, |
| svn_wc_adm_access_path(adm_access)); |
| |
| /* Check cancellation here, so recursive calls get checked early. */ |
| if (cancel_func) |
| SVN_ERR(cancel_func(cancel_baton)); |
| |
| /* NAME is either a file's basename or SVN_WC_ENTRY_THIS_DIR. */ |
| is_file = (strcmp(name, SVN_WC_ENTRY_THIS_DIR)) ? TRUE : FALSE; |
| |
| if (is_file) |
| { |
| svn_boolean_t text_modified_p; |
| full_path = svn_path_join(full_path, name, pool); |
| |
| /* Check for local mods. before removing entry */ |
| SVN_ERR(svn_wc_text_modified_p(&text_modified_p, full_path, |
| FALSE, adm_access, pool)); |
| if (text_modified_p && instant_error) |
| return svn_error_createf(SVN_ERR_WC_LEFT_LOCAL_MOD, NULL, |
| _("File '%s' has local modifications"), |
| svn_path_local_style(full_path, pool)); |
| |
| /* Remove the wcprops. */ |
| SVN_ERR(svn_wc__remove_wcprops(adm_access, name, FALSE, pool)); |
| |
| /* Remove NAME from PATH's entries file: */ |
| SVN_ERR(svn_wc_entries_read(&entries, adm_access, TRUE, pool)); |
| svn_wc__entry_remove(entries, name); |
| SVN_ERR(svn_wc__entries_write(entries, adm_access, pool)); |
| |
| /* Remove text-base/NAME.svn-base, prop/NAME, prop-base/NAME.svn-base. */ |
| { |
| const char *svn_thang; |
| |
| /* Text base. */ |
| svn_thang = svn_wc__text_base_path(full_path, 0, pool); |
| SVN_ERR(remove_file_if_present(svn_thang, pool)); |
| |
| /* Working prop file. */ |
| SVN_ERR(svn_wc__prop_path(&svn_thang, full_path, |
| is_file ? svn_node_file : svn_node_dir, |
| FALSE, pool)); |
| SVN_ERR(remove_file_if_present(svn_thang, pool)); |
| |
| /* Prop base file. */ |
| SVN_ERR(svn_wc__prop_base_path(&svn_thang, full_path, |
| is_file ? svn_node_file |
| : svn_node_dir, |
| FALSE, pool)); |
| SVN_ERR(remove_file_if_present(svn_thang, pool)); |
| |
| } |
| |
| /* If we were asked to destroy the working file, do so unless |
| it has local mods. */ |
| if (destroy_wf) |
| { |
| if (text_modified_p) /* Don't kill local mods. */ |
| return svn_error_create(SVN_ERR_WC_LEFT_LOCAL_MOD, NULL, NULL); |
| else /* The working file is still present; remove it. */ |
| SVN_ERR(remove_file_if_present(full_path, pool)); |
| } |
| |
| } /* done with file case */ |
| |
| else /* looking at THIS_DIR */ |
| { |
| apr_pool_t *subpool = svn_pool_create(pool); |
| apr_hash_index_t *hi; |
| svn_wc_entry_t incomplete_entry; |
| |
| /* ### sanity check: check 2 places for DELETED flag? */ |
| |
| /* Before we start removing entries from this dir's entries |
| file, mark this directory as "incomplete". This allows this |
| function to be interruptible and the wc recoverable by 'svn |
| up' later on. */ |
| incomplete_entry.incomplete = TRUE; |
| SVN_ERR(svn_wc__entry_modify(adm_access, |
| SVN_WC_ENTRY_THIS_DIR, |
| &incomplete_entry, |
| SVN_WC__ENTRY_MODIFY_INCOMPLETE, |
| TRUE, /* sync to disk immediately */ |
| pool)); |
| |
| /* Get rid of all the wcprops in this directory. This avoids rewriting |
| the wcprops file over and over (meaning O(n^2) complexity) |
| below. */ |
| SVN_ERR(svn_wc__remove_wcprops(adm_access, NULL, FALSE, pool)); |
| |
| /* Walk over every entry. */ |
| SVN_ERR(svn_wc_entries_read(&entries, adm_access, FALSE, pool)); |
| |
| for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi)) |
| { |
| const void *key; |
| void *val; |
| const char *current_entry_name; |
| const svn_wc_entry_t *current_entry; |
| |
| svn_pool_clear(subpool); |
| |
| apr_hash_this(hi, &key, NULL, &val); |
| current_entry = val; |
| if (! strcmp(key, SVN_WC_ENTRY_THIS_DIR)) |
| current_entry_name = NULL; |
| else |
| current_entry_name = key; |
| |
| if (current_entry->kind == svn_node_file) |
| { |
| err = svn_wc_remove_from_revision_control |
| (adm_access, current_entry_name, destroy_wf, instant_error, |
| cancel_func, cancel_baton, subpool); |
| |
| if (err && (err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD)) |
| { |
| if (instant_error) |
| { |
| return err; |
| } |
| else |
| { |
| svn_error_clear(err); |
| left_something = TRUE; |
| } |
| } |
| else if (err) |
| return err; |
| } |
| else if (current_entry_name && (current_entry->kind == svn_node_dir)) |
| { |
| svn_wc_adm_access_t *entry_access; |
| const char *entrypath |
| = svn_path_join(svn_wc_adm_access_path(adm_access), |
| current_entry_name, |
| subpool); |
| |
| if (svn_wc__adm_missing(adm_access, entrypath)) |
| { |
| /* The directory is already missing, so don't try to |
| recurse, just delete the entry in the parent |
| directory. */ |
| svn_wc__entry_remove(entries, current_entry_name); |
| } |
| else |
| { |
| SVN_ERR(svn_wc_adm_retrieve(&entry_access, adm_access, |
| entrypath, subpool)); |
| |
| err = svn_wc_remove_from_revision_control |
| (entry_access, SVN_WC_ENTRY_THIS_DIR, destroy_wf, |
| instant_error, cancel_func, cancel_baton, subpool); |
| |
| if (err && (err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD)) |
| { |
| if (instant_error) |
| { |
| return err; |
| } |
| else |
| { |
| svn_error_clear(err); |
| left_something = TRUE; |
| } |
| } |
| else if (err) |
| return err; |
| } |
| } |
| } |
| |
| /* At this point, every directory below this one has been |
| removed from revision control. */ |
| |
| /* Remove self from parent's entries file, but only if parent is |
| a working copy. If it's not, that's fine, we just move on. */ |
| { |
| const char *parent_dir, *base_name; |
| svn_boolean_t is_root; |
| |
| SVN_ERR(svn_wc_is_wc_root(&is_root, full_path, adm_access, pool)); |
| |
| /* If full_path is not the top of a wc, then its parent |
| directory is also a working copy and has an entry for |
| full_path. We need to remove that entry: */ |
| if (! is_root) |
| { |
| svn_wc_adm_access_t *parent_access; |
| |
| svn_path_split(full_path, &parent_dir, &base_name, pool); |
| |
| SVN_ERR(svn_wc_adm_retrieve(&parent_access, adm_access, |
| parent_dir, pool)); |
| SVN_ERR(svn_wc_entries_read(&entries, parent_access, TRUE, |
| pool)); |
| svn_wc__entry_remove(entries, base_name); |
| SVN_ERR(svn_wc__entries_write(entries, parent_access, pool)); |
| } |
| } |
| |
| /* Remove the entire administrative .svn area, thereby removing |
| _this_ dir from revision control too. */ |
| SVN_ERR(svn_wc__adm_destroy(adm_access, subpool)); |
| |
| /* If caller wants us to recursively nuke everything on disk, go |
| ahead, provided that there are no dangling local-mod files |
| below */ |
| if (destroy_wf && (! left_something)) |
| { |
| /* If the dir is *truly* empty (i.e. has no unversioned |
| resources, all versioned files are gone, all .svn dirs are |
| gone, and contains nothing but empty dirs), then a |
| *non*-recursive dir_remove should work. If it doesn't, |
| no big deal. Just assume there are unversioned items in |
| there and set "left_something" */ |
| err = svn_io_dir_remove_nonrecursive |
| (svn_wc_adm_access_path(adm_access), subpool); |
| if (err) |
| { |
| left_something = TRUE; |
| svn_error_clear(err); |
| } |
| } |
| |
| svn_pool_destroy(subpool); |
| |
| } /* end of directory case */ |
| |
| if (left_something) |
| return svn_error_create(SVN_ERR_WC_LEFT_LOCAL_MOD, NULL, NULL); |
| else |
| return SVN_NO_ERROR; |
| } |
| |
| |
| |
| /*** Resolving a conflict automatically ***/ |
| |
| |
| /* Helper for resolve_conflict_on_entry. Delete the file BASE_NAME in |
| PARENT_DIR if it exists. Set WAS_PRESENT to TRUE if the file existed, |
| and to FALSE otherwise. */ |
| static svn_error_t * |
| attempt_deletion(const char *parent_dir, |
| const char *base_name, |
| svn_boolean_t *was_present, |
| apr_pool_t *pool) |
| { |
| const char *full_path = svn_path_join(parent_dir, base_name, pool); |
| svn_node_kind_t kind; |
| |
| SVN_ERR(svn_io_check_path(full_path, &kind, pool)); |
| *was_present = kind != svn_node_none; |
| if (! *was_present) |
| return SVN_NO_ERROR; |
| |
| return svn_io_remove_file(full_path, pool); |
| } |
| |
| |
| /* Conflict resolution involves removing the conflict files, if they exist, |
| and clearing the conflict filenames from the entry. The latter needs to |
| be done whether or not the conflict files exist. |
| |
| PATH is the path to the item to be resolved, BASE_NAME is the basename |
| of PATH, and CONFLICT_DIR is the access baton for PATH. ORIG_ENTRY is |
| the entry prior to resolution. RESOLVE_TEXT and RESOLVE_PROPS are TRUE |
| if text and property conficts respectively are to be resolved. */ |
| static svn_error_t * |
| resolve_conflict_on_entry(const char *path, |
| const svn_wc_entry_t *orig_entry, |
| svn_wc_adm_access_t *conflict_dir, |
| const char *base_name, |
| svn_boolean_t resolve_text, |
| svn_boolean_t resolve_props, |
| svn_wc_notify_func2_t notify_func, |
| void *notify_baton, |
| apr_pool_t *pool) |
| { |
| svn_boolean_t was_present, need_feedback = FALSE; |
| apr_uint32_t modify_flags = 0; |
| svn_wc_entry_t *entry = svn_wc_entry_dup(orig_entry, pool); |
| |
| /* Yes indeed, being able to map a function over a list would be nice. */ |
| if (resolve_text && entry->conflict_old) |
| { |
| SVN_ERR(attempt_deletion(svn_wc_adm_access_path(conflict_dir), |
| entry->conflict_old, &was_present, pool)); |
| modify_flags |= SVN_WC__ENTRY_MODIFY_CONFLICT_OLD; |
| entry->conflict_old = NULL; |
| need_feedback |= was_present; |
| } |
| if (resolve_text && entry->conflict_new) |
| { |
| SVN_ERR(attempt_deletion(svn_wc_adm_access_path(conflict_dir), |
| entry->conflict_new, &was_present, pool)); |
| modify_flags |= SVN_WC__ENTRY_MODIFY_CONFLICT_NEW; |
| entry->conflict_new = NULL; |
| need_feedback |= was_present; |
| } |
| if (resolve_text && entry->conflict_wrk) |
| { |
| SVN_ERR(attempt_deletion(svn_wc_adm_access_path(conflict_dir), |
| entry->conflict_wrk, &was_present, pool)); |
| modify_flags |= SVN_WC__ENTRY_MODIFY_CONFLICT_WRK; |
| entry->conflict_wrk = NULL; |
| need_feedback |= was_present; |
| } |
| if (resolve_props && entry->prejfile) |
| { |
| SVN_ERR(attempt_deletion(svn_wc_adm_access_path(conflict_dir), |
| entry->prejfile, &was_present, pool)); |
| modify_flags |= SVN_WC__ENTRY_MODIFY_PREJFILE; |
| entry->prejfile = NULL; |
| need_feedback |= was_present; |
| } |
| |
| if (modify_flags) |
| { |
| /* Although removing the files is sufficient to indicate that the |
| conflict is resolved, if we update the entry as well future checks |
| for conflict state will be more efficient. */ |
| SVN_ERR(svn_wc__entry_modify |
| (conflict_dir, |
| (entry->kind == svn_node_dir ? NULL : base_name), |
| entry, modify_flags, TRUE, pool)); |
| |
| /* No feedback if no files were deleted and all we did was change the |
| entry, such a file did not appear as a conflict */ |
| if (need_feedback && notify_func) |
| { |
| /* Sanity check: see if libsvn_wc *still* thinks this item is in a |
| state of conflict that we have asked to resolve. If not, report |
| the successful resolution. */ |
| svn_boolean_t text_conflict, prop_conflict; |
| SVN_ERR(svn_wc_conflicted_p(&text_conflict, &prop_conflict, |
| svn_wc_adm_access_path(conflict_dir), |
| entry, pool)); |
| if ((! (resolve_text && text_conflict)) |
| && (! (resolve_props && prop_conflict))) |
| (*notify_func)(notify_baton, |
| svn_wc_create_notify(path, svn_wc_notify_resolved, |
| pool), pool); |
| } |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Machinery for an automated entries walk... */ |
| |
| struct resolve_callback_baton |
| { |
| /* TRUE if text conflicts are to be resolved. */ |
| svn_boolean_t resolve_text; |
| /* TRUE if property conflicts are to be resolved. */ |
| svn_boolean_t resolve_props; |
| /* An access baton for the tree, with write access */ |
| svn_wc_adm_access_t *adm_access; |
| /* Notification function and baton */ |
| svn_wc_notify_func2_t notify_func; |
| void *notify_baton; |
| }; |
| |
| static svn_error_t * |
| resolve_found_entry_callback(const char *path, |
| const svn_wc_entry_t *entry, |
| void *walk_baton, |
| apr_pool_t *pool) |
| { |
| struct resolve_callback_baton *baton = walk_baton; |
| const char *conflict_dir, *base_name = NULL; |
| svn_wc_adm_access_t *adm_access; |
| |
| /* We're going to receive dirents twice; we want to ignore the |
| first one (where it's a child of a parent dir), and only print |
| the second one (where we're looking at THIS_DIR). */ |
| if ((entry->kind == svn_node_dir) |
| && (strcmp(entry->name, SVN_WC_ENTRY_THIS_DIR))) |
| return SVN_NO_ERROR; |
| |
| /* Figger out the directory in which the conflict resides. */ |
| if (entry->kind == svn_node_dir) |
| conflict_dir = path; |
| else |
| svn_path_split(path, &conflict_dir, &base_name, pool); |
| SVN_ERR(svn_wc_adm_retrieve(&adm_access, baton->adm_access, conflict_dir, |
| pool)); |
| |
| return resolve_conflict_on_entry(path, entry, adm_access, base_name, |
| baton->resolve_text, baton->resolve_props, |
| baton->notify_func, baton->notify_baton, |
| pool); |
| } |
| |
| static const svn_wc_entry_callbacks_t |
| resolve_walk_callbacks = |
| { |
| resolve_found_entry_callback |
| }; |
| |
| |
| /* The public function */ |
| svn_error_t * |
| svn_wc_resolved_conflict(const char *path, |
| svn_wc_adm_access_t *adm_access, |
| svn_boolean_t resolve_text, |
| svn_boolean_t resolve_props, |
| svn_boolean_t recurse, |
| svn_wc_notify_func_t notify_func, |
| void *notify_baton, |
| apr_pool_t *pool) |
| { |
| svn_wc__compat_notify_baton_t nb; |
| |
| nb.func = notify_func; |
| nb.baton = notify_baton; |
| |
| return svn_wc_resolved_conflict2(path, adm_access, |
| resolve_text, resolve_props, recurse, |
| svn_wc__compat_call_notify_func, &nb, |
| NULL, NULL, pool); |
| |
| } |
| |
| svn_error_t * |
| svn_wc_resolved_conflict2(const char *path, |
| svn_wc_adm_access_t *adm_access, |
| svn_boolean_t resolve_text, |
| svn_boolean_t resolve_props, |
| svn_boolean_t recurse, |
| svn_wc_notify_func2_t notify_func, |
| void *notify_baton, |
| svn_cancel_func_t cancel_func, |
| void *cancel_baton, |
| apr_pool_t *pool) |
| { |
| struct resolve_callback_baton *baton = apr_pcalloc(pool, sizeof(*baton)); |
| |
| baton->resolve_text = resolve_text; |
| baton->resolve_props = resolve_props; |
| baton->adm_access = adm_access; |
| baton->notify_func = notify_func; |
| baton->notify_baton = notify_baton; |
| |
| if (! recurse) |
| { |
| const svn_wc_entry_t *entry; |
| SVN_ERR(svn_wc_entry(&entry, path, adm_access, FALSE, pool)); |
| if (! entry) |
| return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL, |
| _("'%s' is not under version control"), |
| svn_path_local_style(path, pool)); |
| |
| SVN_ERR(resolve_found_entry_callback(path, entry, baton, pool)); |
| } |
| else |
| { |
| SVN_ERR(svn_wc_walk_entries2(path, adm_access, |
| &resolve_walk_callbacks, baton, |
| FALSE, cancel_func, cancel_baton, pool)); |
| |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| svn_error_t *svn_wc_add_lock(const char *path, const svn_lock_t *lock, |
| svn_wc_adm_access_t *adm_access, apr_pool_t *pool) |
| { |
| const svn_wc_entry_t *entry; |
| svn_wc_entry_t newentry; |
| |
| SVN_ERR(svn_wc_entry(&entry, path, adm_access, FALSE, pool)); |
| |
| if (! entry) |
| return svn_error_createf(SVN_ERR_UNVERSIONED_RESOURCE, NULL, |
| _("'%s' is not under version control"), path); |
| |
| newentry.lock_token = lock->token; |
| newentry.lock_owner = lock->owner; |
| newentry.lock_comment = lock->comment; |
| newentry.lock_creation_date = lock->creation_date; |
| |
| SVN_ERR(svn_wc__entry_modify(adm_access, entry->name, &newentry, |
| SVN_WC__ENTRY_MODIFY_LOCK_TOKEN |
| | SVN_WC__ENTRY_MODIFY_LOCK_OWNER |
| | SVN_WC__ENTRY_MODIFY_LOCK_COMMENT |
| | SVN_WC__ENTRY_MODIFY_LOCK_CREATION_DATE, |
| TRUE, pool)); |
| |
| { /* if svn:needs-lock is present, then make the file read-write. */ |
| const svn_string_t *needs_lock; |
| |
| SVN_ERR(svn_wc_prop_get(&needs_lock, SVN_PROP_NEEDS_LOCK, |
| path, adm_access, pool)); |
| if (needs_lock) |
| SVN_ERR(svn_io_set_file_read_write(path, FALSE, pool)); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| svn_error_t *svn_wc_remove_lock(const char *path, |
| svn_wc_adm_access_t *adm_access, apr_pool_t *pool) |
| { |
| const svn_wc_entry_t *entry; |
| svn_wc_entry_t newentry; |
| |
| SVN_ERR(svn_wc_entry(&entry, path, adm_access, FALSE, pool)); |
| |
| if (! entry) |
| return svn_error_createf(SVN_ERR_UNVERSIONED_RESOURCE, NULL, |
| _("'%s' is not under version control"), path); |
| |
| newentry.lock_token = newentry.lock_owner = newentry.lock_comment = NULL; |
| newentry.lock_creation_date = 0; |
| SVN_ERR(svn_wc__entry_modify(adm_access, entry->name, &newentry, |
| SVN_WC__ENTRY_MODIFY_LOCK_TOKEN |
| | SVN_WC__ENTRY_MODIFY_LOCK_OWNER |
| | SVN_WC__ENTRY_MODIFY_LOCK_COMMENT |
| | SVN_WC__ENTRY_MODIFY_LOCK_CREATION_DATE, |
| TRUE, pool)); |
| |
| { /* if svn:needs-lock is present, then make the file read-only. */ |
| const svn_string_t *needs_lock; |
| |
| SVN_ERR(svn_wc_prop_get(&needs_lock, SVN_PROP_NEEDS_LOCK, |
| path, adm_access, pool)); |
| if (needs_lock) |
| SVN_ERR(svn_io_set_file_read_only(path, FALSE, pool)); |
| } |
| |
| return SVN_NO_ERROR; |
| } |