| /* |
| * 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-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 <string.h> |
| |
| #include <apr_pools.h> |
| #include <apr_tables.h> |
| #include <apr_hash.h> |
| #include <apr_file_io.h> |
| #include <apr_time.h> |
| #include <apr_errno.h> |
| |
| #include "svn_types.h" |
| #include "svn_pools.h" |
| #include "svn_string.h" |
| #include "svn_error.h" |
| #include "svn_path.h" |
| #include "svn_hash.h" |
| #include "svn_wc.h" |
| #include "svn_io.h" |
| #include "svn_xml.h" |
| #include "svn_time.h" |
| #include "svn_diff.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" |
| #include "private/svn_wc_private.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_depth_t depth, |
| apr_hash_t *exclude_paths, |
| 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" */ |
| if (! apr_hash_get(exclude_paths, svn_wc_adm_access_path(dirpath), |
| APR_HASH_KEY_STRING)) |
| 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))); |
| |
| if (depth == svn_depth_unknown) |
| depth = svn_depth_infinity; |
| |
| if (depth > svn_depth_empty) |
| { |
| 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_path; |
| const char *child_url = NULL; |
| svn_boolean_t excluded; |
| |
| 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); |
| |
| child_path = svn_path_join(svn_wc_adm_access_path(dirpath), name, |
| subpool); |
| excluded = (apr_hash_get(exclude_paths, child_path, |
| APR_HASH_KEY_STRING) != NULL); |
| |
| /* If a file, or deleted or absent dir, then tweak the entry |
| but don't recurse. */ |
| if ((current_entry->kind == svn_node_file) |
| || (current_entry->deleted || current_entry->absent)) |
| { |
| if (! excluded) |
| 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 ((depth == svn_depth_infinity |
| || depth == svn_depth_immediates) |
| && (current_entry->kind == svn_node_dir)) |
| { |
| svn_depth_t depth_below_here = depth; |
| |
| if (depth == svn_depth_immediates) |
| depth_below_here = svn_depth_empty; |
| |
| /* 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 |
| && !excluded) |
| { |
| 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, |
| depth_below_here, exclude_paths, 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; |
| } |
| |
| |
| static svn_error_t * |
| remove_revert_files(svn_stringbuf_t **logtags, |
| svn_wc_adm_access_t *adm_access, |
| const char *path, |
| apr_pool_t * pool) |
| { |
| const char *revert_file; |
| svn_node_kind_t kind; |
| |
| revert_file = svn_wc__text_revert_path(path, FALSE, pool); |
| SVN_ERR(svn_io_check_path(revert_file, &kind, pool)); |
| if (kind == svn_node_file) |
| SVN_ERR(svn_wc__loggy_remove(logtags, adm_access, revert_file, pool)); |
| |
| return svn_wc__loggy_props_delete(logtags, path, svn_wc__props_revert, |
| adm_access, pool); |
| } |
| |
| svn_error_t * |
| svn_wc__do_update_cleanup(const char *path, |
| svn_wc_adm_access_t *adm_access, |
| svn_depth_t depth, |
| 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_hash_t *exclude_paths, |
| 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; |
| if (apr_hash_get(exclude_paths, path, APR_HASH_KEY_STRING)) |
| return SVN_NO_ERROR; |
| 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, |
| depth, exclude_paths, 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; |
| } |
| |
| |
| static svn_error_t * |
| process_committed_leaf(int log_number, |
| const char *path, |
| svn_wc_adm_access_t *adm_access, |
| const svn_wc_entry_t *entry, |
| svn_revnum_t new_revnum, |
| const char *rev_date, |
| const char *rev_author, |
| apr_array_header_t *wcprop_changes, |
| svn_boolean_t remove_lock, |
| svn_boolean_t remove_changelist, |
| svn_checksum_t *checksum, |
| apr_pool_t *pool) |
| { |
| svn_wc_entry_t tmp_entry; |
| apr_uint64_t modify_flags = 0; |
| svn_stringbuf_t *logtags = svn_stringbuf_create("", pool); |
| |
| SVN_ERR(svn_wc__adm_write_check(adm_access, pool)); |
| |
| /* 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. */ |
| |
| if (entry->kind == svn_node_file) |
| { |
| /* If the props or text revert file exists it needs to be deleted when |
| * the file is committed. */ |
| /* ### don't directories have revert props? */ |
| SVN_ERR(remove_revert_files(&logtags, adm_access, path, pool)); |
| |
| if (checksum == NULL) |
| { |
| /* 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. |
| */ |
| const char *latest_base; |
| svn_error_t *err; |
| svn_checksum_t *local_checksum; |
| |
| latest_base = svn_wc__text_base_path(path, TRUE, pool); |
| err = svn_io_file_checksum2(&local_checksum, latest_base, |
| svn_checksum_md5, pool); |
| |
| if (err && APR_STATUS_IS_ENOENT(err->apr_err)) |
| { |
| svn_error_clear(err); |
| latest_base = svn_wc__text_base_path(path, FALSE, pool); |
| err = svn_io_file_checksum2(&local_checksum, latest_base, |
| svn_checksum_md5, pool); |
| } |
| |
| if (! err) |
| checksum = local_checksum; |
| else if (APR_STATUS_IS_ENOENT(err->apr_err)) |
| svn_error_clear(err); |
| else |
| return err; |
| } |
| } |
| |
| |
| /* 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 (checksum) |
| { |
| tmp_entry.checksum = svn_checksum_to_cstring(checksum, pool); |
| modify_flags |= SVN_WC__ENTRY_MODIFY_CHECKSUM; |
| } |
| |
| if (modify_flags) |
| SVN_ERR(svn_wc__loggy_entry_modify(&logtags, adm_access, |
| path, &tmp_entry, modify_flags, pool)); |
| |
| if (remove_lock) |
| SVN_ERR(svn_wc__loggy_delete_lock(&logtags, adm_access, path, pool)); |
| |
| if (remove_changelist) |
| SVN_ERR(svn_wc__loggy_delete_changelist(&logtags, adm_access, path, 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, |
| path, 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, |
| path, prop->name, |
| prop->value ? prop->value->data : NULL, |
| pool)); |
| } |
| } |
| |
| /* Write our accumulation of log entries into a log file */ |
| return svn_wc__write_log(adm_access, log_number, logtags, pool); |
| } |
| |
| |
| static svn_error_t * |
| process_committed_internal(int *log_number, |
| 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, |
| svn_boolean_t remove_changelist, |
| svn_checksum_t *checksum, |
| apr_pool_t *pool) |
| { |
| const svn_wc_entry_t *entry; |
| |
| SVN_ERR(svn_wc_entry(&entry, path, adm_access, FALSE, pool)); |
| if (entry == NULL) |
| return SVN_NO_ERROR; /* deleted/absent. (?) ... nothing to do. */ |
| |
| SVN_ERR(process_committed_leaf((*log_number)++, path, adm_access, entry, |
| new_revnum, rev_date, rev_author, |
| wcprop_changes, |
| remove_lock, remove_changelist, |
| checksum, pool)); |
| |
| if (recurse && entry->kind == svn_node_dir) |
| { |
| 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_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); |
| |
| /* 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. */ |
| if (current_entry->kind == svn_node_dir) |
| { |
| svn_wc_adm_access_t *child_access; |
| int inner_log = 0; |
| |
| SVN_ERR(svn_wc_adm_retrieve(&child_access, adm_access, |
| this_path, subpool)); |
| |
| SVN_ERR(process_committed_internal(&inner_log, |
| this_path, child_access, |
| TRUE /* recurse */, |
| new_revnum, rev_date, |
| rev_author, |
| NULL, FALSE /* remove_lock */, |
| remove_changelist, NULL, |
| subpool)); |
| SVN_ERR(svn_wc__run_log(child_access, NULL, pool)); |
| } |
| else |
| { |
| /* Suppress log creation for deleted entries in a replaced |
| directory. By the time any log we create here is run, |
| those entries will already have been removed (as a result |
| of running the log for the replaced directory that was |
| created at the start of this function). */ |
| if (current_entry->schedule == svn_wc_schedule_delete) |
| { |
| svn_wc_entry_t *parent_entry; |
| |
| parent_entry = apr_hash_get(entries, SVN_WC_ENTRY_THIS_DIR, |
| APR_HASH_KEY_STRING); |
| if (parent_entry->schedule == svn_wc_schedule_replace) |
| continue; |
| } |
| SVN_ERR(process_committed_leaf |
| ((*log_number)++, this_path, adm_access, current_entry, |
| new_revnum, rev_date, rev_author, NULL, FALSE, |
| remove_changelist, NULL, subpool)); |
| } |
| } |
| |
| svn_pool_destroy(subpool); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| struct svn_wc_committed_queue_t |
| { |
| apr_pool_t *pool; |
| apr_array_header_t *queue; |
| svn_boolean_t have_recursive; |
| }; |
| |
| typedef struct |
| { |
| const char *path; |
| svn_wc_adm_access_t *adm_access; |
| svn_boolean_t recurse; |
| svn_boolean_t remove_lock; |
| svn_boolean_t remove_changelist; |
| apr_array_header_t *wcprop_changes; |
| svn_checksum_t *checksum; |
| } committed_queue_item_t; |
| |
| |
| svn_wc_committed_queue_t * |
| svn_wc_committed_queue_create(apr_pool_t *pool) |
| { |
| svn_wc_committed_queue_t *q; |
| |
| q = apr_palloc(pool, sizeof(*q)); |
| q->pool = pool; |
| q->queue = apr_array_make(pool, 1, sizeof(committed_queue_item_t *)); |
| q->have_recursive = FALSE; |
| |
| return q; |
| } |
| |
| svn_error_t * |
| svn_wc_queue_committed2(svn_wc_committed_queue_t *queue, |
| const char *path, |
| svn_wc_adm_access_t *adm_access, |
| svn_boolean_t recurse, |
| apr_array_header_t *wcprop_changes, |
| svn_boolean_t remove_lock, |
| svn_boolean_t remove_changelist, |
| svn_checksum_t *checksum, |
| apr_pool_t *pool) |
| { |
| /* ### fill this in... */ |
| SVN_ERR_MALFUNCTION(); |
| } |
| |
| svn_error_t * |
| svn_wc_queue_committed(svn_wc_committed_queue_t **queue, |
| const char *path, |
| svn_wc_adm_access_t *adm_access, |
| svn_boolean_t recurse, |
| apr_array_header_t *wcprop_changes, |
| svn_boolean_t remove_lock, |
| svn_boolean_t remove_changelist, |
| const unsigned char *digest, |
| apr_pool_t *pool) |
| { |
| committed_queue_item_t *cqi; |
| |
| (*queue)->have_recursive |= recurse; |
| |
| /* Use the same pool as the one *QUEUE was allocated in, |
| to prevent lifetime issues. Intermediate operations |
| should use POOL. */ |
| |
| /* Add to the array with paths and options */ |
| cqi = apr_palloc((*queue)->pool, sizeof(*cqi)); |
| cqi->path = path; |
| cqi->adm_access = adm_access; |
| cqi->recurse = recurse; |
| cqi->remove_lock = remove_lock; |
| cqi->remove_changelist = remove_changelist; |
| cqi->wcprop_changes = wcprop_changes; |
| |
| if (digest) |
| { |
| cqi->checksum = svn_checksum_create(svn_checksum_md5, (*queue)->pool); |
| cqi->checksum->digest = digest; |
| } |
| else |
| cqi->checksum = NULL; |
| |
| APR_ARRAY_PUSH((*queue)->queue, committed_queue_item_t *) = cqi; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| typedef struct affected_adm_t |
| { |
| int next_log; |
| svn_wc_adm_access_t *adm_access; |
| } affected_adm_t; |
| |
| |
| /* Return TRUE if any item of QUEUE is a parent of ITEM and will be |
| processed recursively, return FALSE otherwise. |
| */ |
| static svn_boolean_t |
| have_recursive_parent(apr_array_header_t *queue, |
| int item, |
| apr_pool_t *pool) |
| { |
| int i; |
| const char *path |
| = APR_ARRAY_IDX(queue, item, committed_queue_item_t *)->path; |
| |
| for (i = 0; i < queue->nelts; i++) |
| { |
| committed_queue_item_t *qi; |
| |
| if (i == item) |
| continue; |
| |
| qi = APR_ARRAY_IDX(queue, i, committed_queue_item_t *); |
| if (qi->recurse && svn_path_is_child(qi->path, path, pool)) |
| return TRUE; |
| } |
| |
| return FALSE; |
| } |
| |
| svn_error_t * |
| svn_wc_process_committed_queue(svn_wc_committed_queue_t *queue, |
| svn_wc_adm_access_t *adm_access, |
| svn_revnum_t new_revnum, |
| const char *rev_date, |
| const char *rev_author, |
| apr_pool_t *pool) |
| { |
| int i; |
| apr_hash_index_t *hi; |
| apr_hash_t *updated_adms = apr_hash_make(pool); |
| apr_pool_t *iterpool = svn_pool_create(pool); |
| |
| /* Now, we write all log files, collecting the affected adms in |
| the process ... */ |
| for (i = 0; i < queue->queue->nelts; i++) |
| { |
| affected_adm_t *affected_adm; |
| const char *adm_path; |
| committed_queue_item_t *cqi = APR_ARRAY_IDX(queue->queue, |
| i, committed_queue_item_t *); |
| |
| svn_pool_clear(iterpool); |
| |
| /* If there are some recursive items, then see if this item is a |
| child of one, and will (implicitly) be accounted for. */ |
| if (queue->have_recursive |
| && have_recursive_parent(queue->queue, i, iterpool)) |
| continue; |
| |
| adm_path = svn_wc_adm_access_path(cqi->adm_access); |
| affected_adm = apr_hash_get(updated_adms, |
| adm_path, APR_HASH_KEY_STRING); |
| if (! affected_adm) |
| { |
| /* allocate in pool instead of iterpool: |
| we don't want this cleared at the next iteration */ |
| affected_adm = apr_palloc(pool, sizeof(*affected_adm)); |
| affected_adm->next_log = 0; |
| affected_adm->adm_access = cqi->adm_access; |
| apr_hash_set(updated_adms, adm_path, APR_HASH_KEY_STRING, |
| affected_adm); |
| } |
| |
| SVN_ERR(process_committed_internal(&affected_adm->next_log, cqi->path, |
| cqi->adm_access, cqi->recurse, |
| new_revnum, rev_date, rev_author, |
| cqi->wcprop_changes, |
| cqi->remove_lock, |
| cqi->remove_changelist, |
| cqi->checksum, iterpool)); |
| } |
| |
| /* ... and then we run them; all at once. |
| |
| This prevents writing the entries file |
| more than once per adm area */ |
| for (hi = apr_hash_first(pool, updated_adms); hi; hi = apr_hash_next(hi)) |
| { |
| void *val; |
| affected_adm_t *this_adm; |
| |
| svn_pool_clear(iterpool); |
| |
| apr_hash_this(hi, NULL, NULL, &val); |
| this_adm = val; |
| |
| SVN_ERR(svn_wc__run_log(this_adm->adm_access, NULL, iterpool)); |
| } |
| |
| queue->queue->nelts = 0; |
| |
| svn_pool_destroy(iterpool); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| svn_error_t * |
| svn_wc_process_committed4(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, |
| svn_boolean_t remove_changelist, |
| const unsigned char *digest, |
| apr_pool_t *pool) |
| { |
| svn_checksum_t *checksum; |
| int log_number = 0; |
| |
| if (digest) |
| { |
| checksum = svn_checksum_create(svn_checksum_md5, pool); |
| checksum->digest = digest; |
| } |
| else |
| checksum = NULL; |
| |
| SVN_ERR(process_committed_internal(&log_number, |
| path, adm_access, recurse, |
| new_revnum, rev_date, rev_author, |
| wcprop_changes, remove_lock, |
| remove_changelist, checksum, pool)); |
| |
| /* Run the log file(s) we just created. */ |
| return svn_wc__run_log(adm_access, NULL, 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, COPIED and/or KEEP_LOCAL |
| flag, depending on the state of MODIFY_FLAGS (which may contain only a |
| subset of the possible modification flags, namely, those indicating a change |
| to one of the three flags mentioned above). */ |
| static svn_error_t * |
| mark_tree(svn_wc_adm_access_t *adm_access, |
| apr_uint64_t modify_flags, |
| svn_wc_schedule_t schedule, |
| svn_boolean_t copied, |
| svn_boolean_t keep_local, |
| 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; |
| svn_wc_entry_t tmp_entry; |
| apr_uint64_t this_dir_flags; |
| |
| /* 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; |
| |
| /* 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, keep_local, |
| cancel_func, cancel_baton, |
| notify_func, notify_baton, |
| subpool)); |
| } |
| |
| tmp_entry.schedule = schedule; |
| tmp_entry.copied = copied; |
| SVN_ERR(svn_wc__entry_modify |
| (adm_access, base_name, &tmp_entry, |
| modify_flags & (SVN_WC__ENTRY_MODIFY_SCHEDULE |
| | SVN_WC__ENTRY_MODIFY_COPIED), |
| TRUE, subpool)); |
| |
| if (copied) |
| /* Remove now obsolete wcprops */ |
| SVN_ERR(svn_wc__props_delete(fullpath, svn_wc__props_wcprop, |
| adm_access, 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); |
| this_dir_flags = 0; |
| |
| /* 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)) |
| { |
| if (modify_flags & SVN_WC__ENTRY_MODIFY_SCHEDULE) |
| { |
| tmp_entry.schedule = schedule; |
| this_dir_flags |= SVN_WC__ENTRY_MODIFY_SCHEDULE; |
| } |
| |
| if (modify_flags & SVN_WC__ENTRY_MODIFY_COPIED) |
| { |
| tmp_entry.copied = copied; |
| this_dir_flags |= SVN_WC__ENTRY_MODIFY_COPIED; |
| } |
| } |
| |
| /* Set keep_local on the "this dir", if requested. */ |
| if (modify_flags & SVN_WC__ENTRY_MODIFY_KEEP_LOCAL) |
| { |
| tmp_entry.keep_local = keep_local; |
| this_dir_flags |= SVN_WC__ENTRY_MODIFY_KEEP_LOCAL; |
| } |
| |
| /* Modify this_dir entry if requested. */ |
| if (this_dir_flags) |
| SVN_ERR(svn_wc__entry_modify(adm_access, NULL, &tmp_entry, this_dir_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_error_t *err; |
| |
| /* Optimize the common case: try to delete the file */ |
| err = svn_io_remove_file(path, pool); |
| if (err) |
| { |
| /* Then maybe it was a directory? */ |
| svn_error_clear(err); |
| |
| err = svn_io_remove_dir2(path, FALSE, cancel_func, cancel_baton, pool); |
| |
| if (err) |
| { |
| /* We're unlikely to end up here. But we need this fallback |
| to make sure we report the right error *and* try the |
| correct deletion at least once. */ |
| svn_node_kind_t kind; |
| |
| svn_error_clear(err); |
| SVN_ERR(svn_io_check_path(path, &kind, pool)); |
| if (kind == svn_node_file) |
| SVN_ERR(svn_io_remove_file(path, pool)); |
| else if (kind == svn_node_dir) |
| SVN_ERR(svn_io_remove_dir2(path, FALSE, |
| cancel_func, cancel_baton, pool)); |
| else if (kind == svn_node_none) |
| return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL, |
| _("'%s' does not exist"), |
| svn_path_local_style(path, pool)); |
| else |
| return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, |
| _("Unsupported node kind for path '%s'"), |
| svn_path_local_style(path, pool)); |
| |
| } |
| } |
| |
| 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) |
| { |
| const svn_wc_entry_t *entry; |
| |
| if (cancel_func) |
| SVN_ERR(cancel_func(cancel_baton)); |
| |
| if (kind == svn_node_file) |
| SVN_ERR(remove_file_if_present(path, pool)); |
| |
| else if (kind == svn_node_dir) |
| /* This must be a directory or absent */ |
| { |
| apr_hash_t *ver, *unver; |
| apr_hash_index_t *hi; |
| svn_wc_adm_access_t *dir_access; |
| svn_error_t *err; |
| |
| /* ### 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 */ |
| err = svn_wc_adm_retrieve(&dir_access, adm_access, path, pool); |
| |
| /* If there's no on-disk item, be sure to exit early and |
| not to return an error */ |
| if (err) |
| { |
| svn_node_kind_t wc_kind; |
| svn_error_t *err2 = svn_io_check_path(path, &wc_kind, pool); |
| |
| if (err2) |
| { |
| svn_error_clear(err); |
| return err2; |
| } |
| |
| if (wc_kind != svn_node_none) |
| return err; |
| |
| svn_error_clear(err); |
| return SVN_NO_ERROR; |
| } |
| 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 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, adm_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)); |
| } |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_error_t * |
| svn_wc_delete3(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, |
| svn_boolean_t keep_local, |
| 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); |
| |
| /* A file external should not be deleted since the file external is |
| implemented as a switched file and it would delete the file the |
| file external is switched to, which is not the behavior the user |
| would probably want. */ |
| if (entry->file_external_path) |
| return svn_error_createf(SVN_ERR_WC_CANNOT_DELETE_FILE_EXTERNAL, NULL, |
| _("Cannot remove the file external at '%s'; " |
| "please propedit or propdel the svn:externals " |
| "description that created it"), |
| svn_path_local_style(path, 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__ENTRY_MODIFY_KEEP_LOCAL, |
| svn_wc_schedule_delete, FALSE, keep_local, |
| 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, |
| path, &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(path, FALSE, pool); |
| const char *text_revert = |
| svn_wc__text_revert_path(path, 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, adm_access, |
| text_revert, text_base, |
| pool)); |
| |
| SVN_ERR(svn_wc__loggy_revert_props_restore(&log_accum, |
| path, adm_access, pool)); |
| } |
| if (was_schedule == svn_wc_schedule_add) |
| SVN_ERR(svn_wc__loggy_props_delete(&log_accum, path, |
| svn_wc__props_base, |
| adm_access, 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 (!keep_local) |
| { |
| 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_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_versioned(&ent, path, adm_access, FALSE, pool)); |
| |
| if (url) |
| *url = apr_pstrdup(pool, ent->url); |
| |
| if (rev) |
| *rev = ent->revision; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_error_t * |
| svn_wc_add3(const char *path, |
| svn_wc_adm_access_t *parent_access, |
| svn_depth_t depth, |
| 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_uint64_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_revert3() 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 and the property |
| flags so they are not set to their respective old values. */ |
| if (is_replace) |
| { |
| tmp_entry.checksum = NULL; |
| modify_flags |= SVN_WC__ENTRY_MODIFY_CHECKSUM; |
| |
| tmp_entry.has_props = FALSE; |
| tmp_entry.has_prop_mods = FALSE; |
| modify_flags |= SVN_WC__ENTRY_MODIFY_HAS_PROPS; |
| modify_flags |= SVN_WC__ENTRY_MODIFY_HAS_PROP_MODS; |
| } |
| |
| 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)) |
| SVN_ERR(svn_wc__props_delete(path, svn_wc__props_working, |
| adm_access, pool)); |
| |
| if (is_replace) |
| { |
| /* We don't want the old base text (if any) and base props to be |
| mistakenly used as the bases for the new, replacement object. |
| So, move them out of the way. */ |
| |
| /* ### TODO: In an ideal world, this whole function would be loggy. |
| * ### But the directory recursion code below is already tangled |
| * ### enough, and re-doing the code above would require setting |
| * ### up more of tmp_entry. It's more than a SMOP. For now, |
| * ### I'm leaving it be, though we set up the revert base(s) |
| * ### loggily because that's Just How It's Done. |
| */ |
| svn_stringbuf_t *log_accum = svn_stringbuf_create("", pool); |
| |
| if (orig_entry->kind == svn_node_file) |
| { |
| const char *textb = svn_wc__text_base_path(path, FALSE, pool); |
| const char *rtextb = svn_wc__text_revert_path(path, FALSE, pool); |
| SVN_ERR(svn_wc__loggy_move(&log_accum, adm_access, |
| textb, rtextb, pool)); |
| } |
| SVN_ERR(svn_wc__loggy_revert_props_create(&log_accum, path, |
| adm_access, TRUE, pool)); |
| SVN_ERR(svn_wc__write_log(adm_access, 0, log_accum, pool)); |
| SVN_ERR(svn_wc__run_log(adm_access, NULL, 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_adm3(path, p_entry->uuid, new_url, |
| p_entry->repos, 0, depth, 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_adm3(path, parent_entry->uuid, copyfrom_url, |
| parent_entry->repos, copyfrom_rev, |
| depth, 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_adm3 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, |
| depth, new_url, |
| parent_entry->repos, |
| SVN_INVALID_REVNUM, NULL, |
| NULL, FALSE, apr_hash_make(pool), |
| 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, FALSE, |
| cancel_func, |
| cancel_baton, |
| NULL, NULL, /* N/A cuz we aren't deleting */ |
| pool)); |
| |
| /* Clean out the now-obsolete wcprops. */ |
| SVN_ERR(svn_wc__props_delete(path, svn_wc__props_wcprop, |
| adm_access, 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; |
| } |
| |
| |
| /* 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. |
| |
| Set *REVERTED to TRUE if something (text or props or both) is |
| reverted, FALSE otherwise. |
| |
| If something is reverted and USE_COMMIT_TIMES is true, then update |
| the entry's timestamp to the last-committed-time; otherwise don't |
| do that. |
| |
| 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; |
| svn_boolean_t reinstall_working = FALSE; /* force working file reinstall? */ |
| svn_wc_entry_t tmp_entry; |
| apr_uint64_t flags = 0; |
| svn_stringbuf_t *log_accum = svn_stringbuf_create("", pool); |
| apr_hash_t *baseprops = NULL; |
| svn_boolean_t revert_base = FALSE; |
| |
| /* By default, assume no action; we'll see what happens later. */ |
| *reverted = FALSE; |
| |
| /* 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) |
| { |
| /* Refer to the original base, before replacement. */ |
| revert_base = TRUE; |
| |
| /* Use the revertpath as the new propsbase if it exists. */ |
| |
| baseprops = apr_hash_make(pool); |
| SVN_ERR(svn_wc__load_props(NULL, NULL, &baseprops, |
| adm_access, fullpath, pool)); |
| |
| /* Ensure the revert propfile gets removed. */ |
| SVN_ERR(svn_wc__loggy_props_delete(&log_accum, |
| fullpath, svn_wc__props_revert, |
| adm_access, 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, fullpath, |
| baseprops, baseprops, revert_base, pool)); |
| *reverted = TRUE; |
| } |
| |
| /* Deal with the contents. */ |
| |
| if (entry->kind == svn_node_file) |
| { |
| svn_node_kind_t kind; |
| |
| const char *regular_base_path |
| = svn_wc__text_base_path(fullpath, FALSE, pool); |
| |
| /* This becomes NULL if there is no revert-base. */ |
| const char *revert_base_path |
| = svn_wc__text_revert_path(fullpath, FALSE, pool); |
| |
| if (! reinstall_working) |
| { |
| /* If the working file is missing, we need to reinstall it. */ |
| SVN_ERR(svn_io_check_path(fullpath, &kind, pool)); |
| if (kind == svn_node_none) |
| reinstall_working = TRUE; |
| } |
| |
| /* Whether or not the working file was missing, if there is a |
| revert text-base, we'll need to put it back to the regular |
| text-base and then reinstall the working file. (Strictly |
| speaking, the working file could be unmodified w.r.t. the |
| revert-base, but discovering that would be costly and chances |
| are it's not the case anyway. So we just assume. */ |
| SVN_ERR(svn_io_check_path(revert_base_path, &kind, pool)); |
| if (kind == svn_node_file) |
| { |
| reinstall_working = TRUE; |
| } |
| else if (kind == svn_node_none) |
| { |
| SVN_ERR(svn_io_check_path(regular_base_path, &kind, pool)); |
| if (kind != svn_node_file) |
| { |
| /* A real file must have either a regular or a revert |
| text-base. If it has neither, we could be looking at |
| the situation described in issue #2101, in which |
| case all we can do is deliver the expected error. */ |
| return svn_error_createf(APR_ENOENT, NULL, |
| _("Error restoring text for '%s'"), |
| svn_path_local_style(fullpath, pool)); |
| } |
| else |
| { |
| revert_base_path = NULL; |
| } |
| } |
| else |
| { |
| return svn_error_createf |
| (SVN_ERR_NODE_UNKNOWN_KIND, NULL, |
| _("unexpected kind for revert-base '%s'"), |
| svn_path_local_style(revert_base_path, pool)); |
| } |
| |
| /* You'd think we could just write out one log command to move |
| the revert base (if any) to the regular base, then another to |
| copy-and-translate the regular base to the working file. |
| |
| Unfortunately, svn_wc__loggy_copy() doesn't actually write |
| out a copy instruction if the src file for the copy isn't |
| present *at the time the log is being composed*. See that |
| function's documentation for details. |
| |
| So instead, we write out a log command to copy-and-translate |
| the revert text-base to the working file, then another log |
| command to move the revert text-base to the regular |
| text-base. */ |
| |
| if (revert_base_path) |
| { |
| SVN_ERR(svn_wc__loggy_copy(&log_accum, adm_access, |
| revert_base_path, fullpath, |
| pool)); |
| SVN_ERR(svn_wc__loggy_move(&log_accum, adm_access, |
| revert_base_path, regular_base_path, |
| pool)); |
| *reverted = TRUE; |
| } |
| else |
| { |
| /* No revert-base -- so don't assume reinstall_working either. */ |
| |
| if (! reinstall_working) |
| { |
| SVN_ERR(svn_wc__text_modified_internal_p |
| (&reinstall_working, fullpath, FALSE, |
| adm_access, FALSE, pool)); |
| } |
| |
| if (reinstall_working) |
| { |
| SVN_ERR(svn_wc__loggy_copy(&log_accum, adm_access, |
| regular_base_path, fullpath, |
| pool)); |
| *reverted = TRUE; |
| } |
| } |
| |
| /* If we reinstalled the working file, then maybe update the |
| text timestamp in the entries file. */ |
| if (reinstall_working) |
| { |
| /* 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, fullpath, |
| svn_time_to_cstring(entry->cmt_date, pool), |
| pool)); |
| |
| SVN_ERR(svn_wc__loggy_set_entry_timestamp_from_wc |
| (&log_accum, adm_access, |
| fullpath, SVN_WC__ENTRY_ATTR_TEXT_TIME, pool)); |
| SVN_ERR(svn_wc__loggy_set_entry_working_size_from_wc |
| (&log_accum, adm_access, fullpath, pool)); |
| } |
| } |
| |
| /* 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, |
| svn_path_join(svn_wc_adm_access_path(adm_access), |
| entry->conflict_old, pool), 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, |
| svn_path_join(svn_wc_adm_access_path(adm_access), |
| entry->conflict_new, pool), 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, |
| svn_path_join(svn_wc_adm_access_path(adm_access), |
| entry->conflict_wrk, pool), pool)); |
| } |
| |
| /* Remove the property conflict 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, |
| svn_path_join(svn_wc_adm_access_path(adm_access), |
| entry->prejfile, pool), pool)); |
| } |
| |
| /* Clean up the copied state if this is a replacement. */ |
| if (entry->schedule == svn_wc_schedule_replace) |
| { |
| flags |= SVN_WC__ENTRY_MODIFY_COPIED | |
| SVN_WC__ENTRY_MODIFY_COPYFROM_URL | |
| SVN_WC__ENTRY_MODIFY_COPYFROM_REV; |
| tmp_entry.copied = FALSE; |
| |
| /* Reset the checksum if this is a replace-with-history. */ |
| if (entry->kind == svn_node_file && entry->copyfrom_url) |
| { |
| const char *base_path; |
| svn_checksum_t *checksum; |
| |
| base_path = svn_wc__text_revert_path(fullpath, FALSE, pool); |
| SVN_ERR(svn_io_file_checksum2(&checksum, base_path, |
| svn_checksum_md5, pool)); |
| tmp_entry.checksum = svn_checksum_to_cstring(checksum, pool); |
| flags |= SVN_WC__ENTRY_MODIFY_CHECKSUM; |
| } |
| |
| /* Set this to the empty string, because NULL values will disappear |
| in the XML log file. */ |
| tmp_entry.copyfrom_url = ""; |
| tmp_entry.copyfrom_rev = SVN_INVALID_REVNUM; |
| } |
| |
| /* 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; |
| } |
| |
| /* If the entry is for this_dir, delete tree conflict data. */ |
| if ((strcmp(name, SVN_WC_ENTRY_THIS_DIR) == 0) |
| && entry->tree_conflict_data) |
| { |
| flags |= SVN_WC__ENTRY_MODIFY_TREE_CONFLICT_DATA; |
| tmp_entry.tree_conflict_data = NULL; |
| *reverted = TRUE; |
| |
| } |
| |
| /* Modify the entry, loggily. */ |
| SVN_ERR(svn_wc__loggy_entry_modify(&log_accum, adm_access, fullpath, |
| &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; |
| } |
| |
| |
| /* Revert PATH of on-disk KIND. ENTRY is the working copy entry for |
| PATH. *DEPTH is the depth of the reversion crawl the caller is |
| using; this function may choose to override that value as needed. |
| |
| See svn_wc_revert3() for the interpretations of PARENT_ACCESS, |
| USE_COMMIT_TIMES, CANCEL_FUNC, CANCEL_BATON, NOTIFY_FUNC, and |
| NOTIFY_BATON. |
| |
| Use POOL for allocations. |
| */ |
| static svn_error_t * |
| revert_entry(svn_depth_t *depth, |
| const char *path, |
| svn_node_kind_t kind, |
| const svn_wc_entry_t *entry, |
| svn_wc_adm_access_t *parent_access, |
| 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) |
| { |
| const char *bname; |
| svn_boolean_t is_wc_root = FALSE; |
| svn_wc_adm_access_t *dir_access; |
| |
| /* Initialize this even though revert_admin_things() is guaranteed |
| to set it, because we don't know that revert_admin_things() will |
| be called. */ |
| svn_boolean_t reverted = FALSE; |
| |
| /* Fetch the access baton for this path. */ |
| SVN_ERR(svn_wc_adm_probe_retrieve(&dir_access, parent_access, 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(&is_wc_root, path, dir_access, pool)); |
| bname = is_wc_root ? NULL : svn_path_basename(path, 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 |
| || svn_wc__adm_missing(parent_access, path)) |
| { |
| /* Schedule add but missing, just remove the entry |
| or it's missing an adm area in which case |
| svn_wc_adm_probe_retrieve() returned the parent's |
| adm_access, for which we definitely can't use the 'else' |
| code path (as it will remove the parent from version |
| control... (See issue 2425) */ |
| svn_wc__entry_remove(entries, basey); |
| SVN_ERR(svn_wc__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. */ |
| *depth = svn_depth_empty; |
| 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; /* ### FIXME: Why the heap alloc? */ |
| 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). */ |
| switch (entry->kind) |
| { |
| case svn_node_file: |
| SVN_ERR(revert_admin_things(parent_access, bname, entry, |
| &reverted, use_commit_times, pool)); |
| break; |
| |
| case svn_node_dir: |
| SVN_ERR(revert_admin_things(dir_access, SVN_WC_ENTRY_THIS_DIR, entry, |
| &reverted, use_commit_times, pool)); |
| |
| /* Also revert the entry in the parent (issue #2804). */ |
| if (reverted && bname) |
| { |
| svn_boolean_t dummy_reverted; |
| svn_wc_entry_t *entry_in_parent; |
| apr_hash_t *entries; |
| |
| SVN_ERR(svn_wc_entries_read(&entries, parent_access, TRUE, |
| pool)); |
| entry_in_parent = apr_hash_get(entries, bname, |
| APR_HASH_KEY_STRING); |
| SVN_ERR(revert_admin_things(parent_access, bname, |
| entry_in_parent, &dummy_reverted, |
| use_commit_times, pool)); |
| } |
| |
| /* Force recursion on replaced directories. */ |
| if (entry->schedule == svn_wc_schedule_replace) |
| *depth = svn_depth_infinity; |
| break; |
| |
| default: |
| /* No op? */ |
| break; |
| } |
| } |
| |
| /* 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); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* This is just the guts of svn_wc_revert3() save that it accepts a |
| hash CHANGELIST_HASH whose keys are changelist names instead of an |
| array of said names. See svn_wc_revert3() for additional |
| documentation. */ |
| static svn_error_t * |
| revert_internal(const char *path, |
| svn_wc_adm_access_t *parent_access, |
| svn_depth_t depth, |
| svn_boolean_t use_commit_times, |
| apr_hash_t *changelist_hash, |
| 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 svn_wc_entry_t *entry; |
| 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)); |
| |
| /* Fetch the access baton for this path. */ |
| SVN_ERR(svn_wc_adm_probe_retrieve(&dir_access, parent_access, path, pool)); |
| |
| /* Safeguard 1: is this a versioned resource? */ |
| SVN_ERR_W(svn_wc__entry_versioned(&entry, path, dir_access, FALSE, pool), |
| _("Cannot revert")); |
| |
| /* 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) |
| { |
| svn_wc_notify_t *notify = |
| svn_wc_create_notify(path, svn_wc_notify_failed_revert, pool); |
| notify_func(notify_baton, notify, pool); |
| } |
| return SVN_NO_ERROR; |
| } |
| } |
| |
| /* Safeguard 2: can we handle this entry's recorded 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 currently 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)); |
| |
| /* If the entry passes changelist filtering, revert it! */ |
| if (SVN_WC__CL_MATCH(changelist_hash, entry)) |
| { |
| /* Actually revert this entry. If this is a working copy root, |
| we provide a base_name from the parent path. */ |
| SVN_ERR(revert_entry(&depth, path, kind, entry, |
| parent_access, use_commit_times, cancel_func, |
| cancel_baton, notify_func, notify_baton, pool)); |
| } |
| |
| /* Finally, recurse if requested. */ |
| if (entry->kind == svn_node_dir && depth > svn_depth_empty) |
| { |
| 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; |
| void *val; |
| const char *full_entry_path; |
| svn_depth_t depth_under_here = depth; |
| svn_wc_entry_t *child_entry; |
| |
| if (depth == svn_depth_files || depth == svn_depth_immediates) |
| depth_under_here = svn_depth_empty; |
| |
| svn_pool_clear(subpool); |
| |
| /* Get the next entry */ |
| apr_hash_this(hi, &key, NULL, &val); |
| keystring = key; |
| child_entry = val; |
| |
| /* Skip "this dir" */ |
| if (! strcmp(keystring, SVN_WC_ENTRY_THIS_DIR)) |
| continue; |
| |
| /* Skip subdirectories if we're called with depth-files. */ |
| if ((depth == svn_depth_files) |
| && (child_entry->kind != svn_node_file)) |
| continue; |
| |
| /* Add the entry name to FULL_ENTRY_PATH. */ |
| full_entry_path = svn_path_join(path, keystring, subpool); |
| |
| /* Revert the entry. */ |
| SVN_ERR(revert_internal(full_entry_path, dir_access, |
| depth_under_here, use_commit_times, |
| changelist_hash, cancel_func, cancel_baton, |
| notify_func, notify_baton, subpool)); |
| } |
| |
| svn_pool_destroy(subpool); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| svn_error_t * |
| svn_wc_revert3(const char *path, |
| svn_wc_adm_access_t *parent_access, |
| svn_depth_t depth, |
| svn_boolean_t use_commit_times, |
| const apr_array_header_t *changelists, |
| svn_cancel_func_t cancel_func, |
| void *cancel_baton, |
| svn_wc_notify_func2_t notify_func, |
| void *notify_baton, |
| apr_pool_t *pool) |
| { |
| apr_hash_t *changelist_hash = NULL; |
| if (changelists && changelists->nelts) |
| SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelists, pool)); |
| return revert_internal(path, parent_access, depth, use_commit_times, |
| changelist_hash, cancel_func, cancel_baton, |
| notify_func, notify_baton, 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_get_pristine_contents(svn_stream_t **contents, |
| const char *path, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| const char *text_base = svn_wc__text_base_path(path, FALSE, scratch_pool); |
| |
| if (text_base == NULL) |
| { |
| *contents = NULL; |
| return SVN_NO_ERROR; |
| } |
| |
| return svn_stream_open_readonly(contents, text_base, result_pool, |
| scratch_pool); |
| } |
| |
| |
| 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_node_kind_t kind; |
| svn_boolean_t wc_special, local_special; |
| svn_boolean_t text_modified_p; |
| full_path = svn_path_join(full_path, name, pool); |
| |
| /* Only check if the file was modified when it wasn't overwritten with a |
| special file */ |
| SVN_ERR(svn_wc__get_special(&wc_special, full_path, adm_access, pool)); |
| SVN_ERR(svn_io_check_special_path(full_path, &kind, &local_special, |
| pool)); |
| if (wc_special || ! local_special) |
| { |
| /* 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__props_delete(full_path, svn_wc__props_wcprop, |
| adm_access, pool)); |
| /* Remove prop/NAME, prop-base/NAME.svn-base. */ |
| SVN_ERR(svn_wc__props_delete(full_path, svn_wc__props_working, |
| adm_access, pool)); |
| SVN_ERR(svn_wc__props_delete(full_path, svn_wc__props_base, |
| adm_access, 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 */ |
| SVN_ERR(remove_file_if_present(svn_wc__text_base_path(full_path, FALSE, |
| pool), pool)); |
| |
| /* If we were asked to destroy the working file, do so unless |
| it has local mods. */ |
| if (destroy_wf) |
| { |
| /* Don't kill local mods. */ |
| if (text_modified_p || (! wc_special && local_special)) |
| 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__props_delete(full_path, svn_wc__props_wcprop, |
| adm_access, 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) |
| { |
| apr_hash_t *parent_entries; |
| 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(&parent_entries, parent_access, TRUE, |
| pool)); |
| svn_wc__entry_remove(parent_entries, base_name); |
| SVN_ERR(svn_wc__entries_write(parent_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_error_t *err = svn_io_remove_file(full_path, pool); |
| |
| *was_present = ! err || ! APR_STATUS_IS_ENOENT(err->apr_err); |
| if (*was_present) |
| return err; |
| |
| svn_error_clear(err); |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* 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, RESOLVE_PROPS and |
| RESOLVE_TREE are TRUE if text, property and tree conflicts respectively |
| are to be resolved. |
| |
| See svn_wc_resolved_conflict3() for how CONFLICT_CHOICE behaves. |
| */ |
| 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_boolean_t resolve_tree, |
| svn_wc_conflict_choice_t conflict_choice, |
| svn_wc_notify_func2_t notify_func, |
| void *notify_baton, |
| apr_pool_t *pool) |
| { |
| svn_boolean_t was_present, need_feedback = FALSE; |
| apr_uint64_t modify_flags = 0; |
| svn_wc_entry_t *entry = svn_wc_entry_dup(orig_entry, pool); |
| |
| if (resolve_text) |
| { |
| const char *auto_resolve_src; |
| |
| /* Handle automatic conflict resolution before the temporary files are |
| * deleted, if necessary. */ |
| switch (conflict_choice) |
| { |
| case svn_wc_conflict_choose_base: |
| auto_resolve_src = entry->conflict_old; |
| break; |
| case svn_wc_conflict_choose_mine_full: |
| auto_resolve_src = entry->conflict_wrk; |
| break; |
| case svn_wc_conflict_choose_theirs_full: |
| auto_resolve_src = entry->conflict_new; |
| break; |
| case svn_wc_conflict_choose_merged: |
| auto_resolve_src = NULL; |
| break; |
| case svn_wc_conflict_choose_theirs_conflict: |
| case svn_wc_conflict_choose_mine_conflict: |
| { |
| if (entry->conflict_old && entry->conflict_wrk && |
| entry->conflict_new) |
| { |
| apr_file_t *tmp_f; |
| svn_stream_t *tmp_stream; |
| svn_diff_t *diff; |
| svn_diff_conflict_display_style_t style = |
| conflict_choice == svn_wc_conflict_choose_theirs_conflict |
| ? svn_diff_conflict_display_latest |
| : svn_diff_conflict_display_modified; |
| |
| SVN_ERR(svn_wc_create_tmp_file2(&tmp_f, |
| &auto_resolve_src, |
| svn_wc_adm_access_path(conflict_dir), |
| svn_io_file_del_none, |
| pool)); |
| tmp_stream = svn_stream_from_aprfile2(tmp_f, FALSE, pool); |
| SVN_ERR(svn_diff_file_diff3_2(&diff, |
| entry->conflict_old, |
| entry->conflict_wrk, |
| entry->conflict_new, |
| svn_diff_file_options_create(pool), |
| pool)); |
| SVN_ERR(svn_diff_file_output_merge2(tmp_stream, diff, |
| entry->conflict_old, |
| entry->conflict_wrk, |
| entry->conflict_new, |
| /* markers ignored */ |
| NULL, NULL, NULL, NULL, |
| style, |
| pool)); |
| SVN_ERR(svn_stream_close(tmp_stream)); |
| } |
| else |
| auto_resolve_src = NULL; |
| break; |
| } |
| default: |
| return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, |
| _("Invalid 'conflict_result' argument")); |
| } |
| |
| if (auto_resolve_src) |
| SVN_ERR(svn_io_copy_file( |
| svn_path_join(svn_wc_adm_access_path(conflict_dir), auto_resolve_src, |
| pool), |
| path, TRUE, 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 (resolve_tree && (entry->kind == svn_node_dir) |
| && entry->tree_conflict_data) |
| { |
| modify_flags |= SVN_WC__ENTRY_MODIFY_TREE_CONFLICT_DATA; |
| entry->tree_conflict_data = NULL; |
| need_feedback = TRUE; |
| } |
| |
| 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, tree_conflict; |
| SVN_ERR(svn_wc_conflicted_p2(&text_conflict, &prop_conflict, |
| &tree_conflict, path, conflict_dir, |
| pool)); |
| if ((! (resolve_text && text_conflict)) |
| && (! (resolve_props && prop_conflict)) |
| && (! (resolve_tree && tree_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; |
| /* TRUE if tree conflicts are to be resolved. */ |
| svn_boolean_t resolve_tree; |
| /* The type of automatic conflict resolution to perform */ |
| svn_wc_conflict_choice_t conflict_choice; |
| /* 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->resolve_tree, |
| baton->conflict_choice, baton->notify_func, |
| baton->notify_baton, pool); |
| } |
| |
| static const svn_wc_entry_callbacks2_t |
| resolve_walk_callbacks = |
| { |
| resolve_found_entry_callback, |
| svn_wc__walker_default_error_handler |
| }; |
| |
| |
| /* The public function */ |
| svn_error_t * |
| svn_wc_resolved_conflict4(const char *path, |
| svn_wc_adm_access_t *adm_access, |
| svn_boolean_t resolve_text, |
| svn_boolean_t resolve_props, |
| svn_boolean_t resolve_tree, |
| svn_depth_t depth, |
| svn_wc_conflict_choice_t conflict_choice, |
| 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->resolve_tree = resolve_tree; |
| baton->adm_access = adm_access; |
| baton->notify_func = notify_func; |
| baton->notify_baton = notify_baton; |
| baton->conflict_choice = conflict_choice; |
| |
| return svn_wc_walk_entries3(path, adm_access, |
| &resolve_walk_callbacks, baton, depth, |
| FALSE, cancel_func, cancel_baton, pool); |
| } |
| |
| 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_versioned(&entry, path, adm_access, FALSE, pool)); |
| |
| |
| 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_versioned(&entry, path, adm_access, FALSE, pool)); |
| |
| 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; |
| } |
| |
| |
| svn_error_t * |
| svn_wc_set_changelist(const char *path, |
| const char *changelist, |
| 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) |
| { |
| const svn_wc_entry_t *entry; |
| svn_wc_entry_t newentry; |
| svn_wc_notify_t *notify; |
| |
| 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); |
| |
| /* We can't do changelists on directories. */ |
| if (entry->kind == svn_node_dir) |
| return svn_error_createf(SVN_ERR_CLIENT_IS_DIRECTORY, NULL, |
| _("'%s' is a directory, and thus cannot" |
| " be a member of a changelist"), path); |
| |
| /* If the path has no changelist and we're removing changelist, skip it. */ |
| if (! (changelist || entry->changelist)) |
| return SVN_NO_ERROR; |
| |
| /* If the path is already assigned to the changelist we're |
| trying to assign, skip it. */ |
| if (entry->changelist |
| && changelist |
| && strcmp(entry->changelist, changelist) == 0) |
| return SVN_NO_ERROR; |
| |
| /* If the path is already a member of a changelist, warn the |
| user about this, but still allow the reassignment to happen. */ |
| if (entry->changelist && changelist && notify_func) |
| { |
| svn_error_t *reassign_err = |
| svn_error_createf(SVN_ERR_WC_CHANGELIST_MOVE, NULL, |
| _("Removing '%s' from changelist '%s'."), |
| path, entry->changelist); |
| notify = svn_wc_create_notify(path, svn_wc_notify_changelist_moved, |
| pool); |
| notify->err = reassign_err; |
| notify_func(notify_baton, notify, pool); |
| svn_error_clear(notify->err); |
| } |
| |
| /* Tweak the entry. */ |
| newentry.changelist = changelist; |
| SVN_ERR(svn_wc__entry_modify(adm_access, entry->name, &newentry, |
| SVN_WC__ENTRY_MODIFY_CHANGELIST, TRUE, pool)); |
| |
| /* And tell someone what we've done. */ |
| if (notify_func) |
| { |
| notify = svn_wc_create_notify(path, |
| changelist |
| ? svn_wc_notify_changelist_set |
| : svn_wc_notify_changelist_clear, |
| pool); |
| notify->changelist_name = changelist; |
| notify_func(notify_baton, notify, pool); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| svn_error_t * |
| svn_wc__set_file_external_location(svn_wc_adm_access_t *adm_access, |
| const char *name, |
| const char *url, |
| const svn_opt_revision_t *peg_rev, |
| const svn_opt_revision_t *rev, |
| const char *repos_root_url, |
| apr_pool_t *pool) |
| { |
| apr_hash_t *entries; |
| svn_wc_entry_t entry = { 0 }; |
| |
| SVN_ERR(svn_wc_entries_read(&entries, adm_access, FALSE, pool)); |
| |
| if (url) |
| { |
| /* A repository root relative path is stored in the entry. */ |
| SVN_ERR_ASSERT(peg_rev); |
| SVN_ERR_ASSERT(rev); |
| entry.file_external_path = url + strlen(repos_root_url); |
| entry.file_external_peg_rev = *peg_rev; |
| entry.file_external_rev = *rev; |
| } |
| else |
| { |
| entry.file_external_path = NULL; |
| entry.file_external_peg_rev.kind = svn_opt_revision_unspecified; |
| entry.file_external_rev.kind = svn_opt_revision_unspecified; |
| } |
| |
| SVN_ERR(svn_wc__entry_modify(adm_access, name, &entry, |
| SVN_WC__ENTRY_MODIFY_FILE_EXTERNAL, TRUE, |
| pool)); |
| |
| return SVN_NO_ERROR; |
| } |