Index: adm_ops.c =================================================================== --- adm_ops.c (revision 39445) +++ adm_ops.c (working copy) @@ -59,6 +59,7 @@ #include "props.h" #include "translate.h" #include "tree_conflicts.h" +#include "workqueue.h" #include "svn_private_config.h" #include "private/svn_wc_private.h" @@ -1795,6 +1796,21 @@ revert_admin_things(svn_wc_adm_access_t *adm_acces svn_boolean_t use_commit_times, apr_pool_t *pool) { +#if 1 + svn_wc__db_t *db = svn_wc__adm_get_db(adm_access); + const char *local_abspath = svn_wc__adm_access_abspath(adm_access); + + if (strcmp(name, SVN_WC_ENTRY_THIS_DIR) != 0) + local_abspath = svn_dirent_join(local_abspath, name, pool); + + SVN_ERR(svn_wc__wq_add_revert(reverted, db, local_abspath, use_commit_times, + pool)); + SVN_ERR(svn_wc__wq_run(db, local_abspath, NULL, NULL, pool)); + + return SVN_NO_ERROR; +#endif + +#if 0 const char *fullpath; svn_boolean_t reinstall_working = FALSE; /* force working file reinstall? */ svn_wc_entry_t tmp_entry; @@ -1924,6 +1940,7 @@ revert_admin_things(svn_wc_adm_access_t *adm_acces svn_dirent_local_style(text_revert, pool)); } + SVN_DBG(("revert: fullpath='%s' reinstall=%d text_revert='%s'\n", fullpath, reinstall_working, text_revert)); /* 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. @@ -1959,6 +1976,7 @@ revert_admin_things(svn_wc_adm_access_t *adm_acces SVN_ERR(svn_wc__text_modified_internal_p(&reinstall_working, db, local_abspath, FALSE, FALSE, pool)); + SVN_DBG(("revert: modified=%d\n", reinstall_working)); } if (reinstall_working) @@ -2037,6 +2055,8 @@ revert_admin_things(svn_wc_adm_access_t *adm_acces /* Clean up the copied state if this is a replacement. */ if (entry->schedule == svn_wc_schedule_replace) { + SVN_DBG(("revert: clearing copyfrom\n")); + flags |= SVN_WC__ENTRY_MODIFY_COPIED | SVN_WC__ENTRY_MODIFY_COPYFROM_URL | SVN_WC__ENTRY_MODIFY_COPYFROM_REV; @@ -2070,11 +2090,14 @@ revert_admin_things(svn_wc_adm_access_t *adm_acces *reverted = TRUE; } + SVN_DBG(("revert: schedule=%d flags=%d\n", entry->schedule, flags)); + /* Modify the entry, loggily. */ SVN_ERR(svn_wc__loggy_entry_modify(&log_accum, svn_wc__adm_access_abspath(adm_access), fullpath, &tmp_entry, flags, pool)); + SVN_DBG(("revert: log=%d\n", log_accum->len)); /* Don't run log if nothing to change. */ if (! svn_stringbuf_isempty(log_accum)) { @@ -2083,6 +2106,7 @@ revert_admin_things(svn_wc_adm_access_t *adm_acces } return SVN_NO_ERROR; +#endif } @@ -2244,7 +2268,7 @@ revert_entry(svn_depth_t *depth, case svn_node_dir: SVN_ERR(revert_admin_things(dir_access, SVN_WC_ENTRY_THIS_DIR, entry, &reverted, use_commit_times, pool)); - +#if 0 /* Also revert the entry in the parent (issue #2804). */ if (reverted && bname) { @@ -2261,6 +2285,7 @@ revert_entry(svn_depth_t *depth, entry_in_parent, &dummy_reverted, use_commit_times, pool)); } +#endif /* Force recursion on replaced directories. */ if (entry->schedule == svn_wc_schedule_replace) @@ -2504,10 +2529,12 @@ svn_wc_revert3(const char *path, if (changelists && changelists->nelts) SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelists, pool)); - return revert_internal(svn_wc__adm_get_db(parent_access), - path, parent_access, depth, use_commit_times, - changelist_hash, cancel_func, cancel_baton, - notify_func, notify_baton, pool); + return svn_error_return(revert_internal(svn_wc__adm_get_db(parent_access), + path, parent_access, depth, + use_commit_times, changelist_hash, + cancel_func, cancel_baton, + notify_func, notify_baton, + pool)); } Index: workqueue.c =================================================================== --- workqueue.c (revision 39445) +++ workqueue.c (working copy) @@ -26,10 +26,15 @@ #include "svn_types.h" #include "svn_pools.h" #include "svn_dirent_uri.h" -#include "svn_path.h" +#include "svn_subst.h" +#include "wc.h" #include "wc_db.h" #include "workqueue.h" +#include "entries.h" +#include "props.h" +#include "adm_files.h" +#include "translate.h" #include "svn_private_config.h" #include "private/svn_skel.h" @@ -39,6 +44,10 @@ return svn_error__malfunction(TRUE, __FILE__, __LINE__, "Not implemented.") +/* Workqueue operation names. */ +#define OP_REVERT "revert" + + struct work_item_dispatch { const char *name; svn_error_t *(*func)(svn_wc__db_t *db, @@ -47,18 +56,396 @@ struct work_item_dispatch { }; +/* Ripped from the old loggy cp_and_translate operation. + + LOCAL_ABSPATH specifies the destination of the copy (typically the + working file). + + SOURCE_ABSPATH specifies the source which is translated for + installation as the working file. + + VERSIONED_ABSPATH specifies the versioned file holding the properties + which specify the translation parameters. */ static svn_error_t * +copy_and_translate(svn_wc__db_t *db, + const char *source_abspath, + const char *dest_abspath, + const char *versioned_abspath, + apr_pool_t *scratch_pool) +{ + svn_subst_eol_style_t style; + const char *eol; + apr_hash_t *keywords; + svn_boolean_t special; + + SVN_ERR(svn_wc__get_eol_style(&style, &eol, db, versioned_abspath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_wc__get_keywords(&keywords, db, versioned_abspath, NULL, + scratch_pool, scratch_pool)); + + /* ### eventually, we will not be called for special files... */ + SVN_ERR(svn_wc__get_special(&special, db, versioned_abspath, + scratch_pool)); + + SVN_ERR(svn_subst_copy_and_translate3( + source_abspath, dest_abspath, + eol, TRUE, + keywords, TRUE, + special, + scratch_pool)); + + /* ### this is a problem. DEST_ABSPATH is not necessarily versioned. */ + SVN_ERR(svn_wc__maybe_set_read_only(NULL, db, dest_abspath, + scratch_pool)); + SVN_ERR(svn_wc__maybe_set_executable(NULL, db, dest_abspath, + scratch_pool)); + + return SVN_NO_ERROR; +} + + +static svn_error_t * run_revert(svn_wc__db_t *db, const svn_skel_t *work_item, apr_pool_t *scratch_pool) { - NOT_IMPLEMENTED(); + const char *local_abspath; + svn_boolean_t replaced; + svn_wc__db_kind_t kind; + svn_node_kind_t node_kind; + const char *conflict_old; + const char *conflict_new; + const char *conflict_wrk; + const char *prop_reject_file; + const char *working_props_path; + apr_uint64_t modify_flags = 0; + svn_wc_entry_t tmp_entry; + + local_abspath = apr_pstrmemdup(scratch_pool, + work_item->children->next->data, + work_item->children->next->len); + /* ### fix this code. validate. */ + replaced = work_item->children->next->next->data[0] - '0'; + /* magic_changed is extracted further below. */ + /* use_commit_times is extracted further below. */ + + /* ### see adm_ops.c::revert_admin_things() + ### + ### find base props ("base" or "revert") + ### reinstall props as base, with no "working" changes + ### alter working file based on original props? + ### put working file back, if missing + ### if revert-base exists, use it to reinstall working file + ### note: reinstall means copy-and-translate + ### conditional reinstall, IFF the text has been modified + ### if reinstalled, then update working file's timestamp. recorded TS + ### remove actual conflict files. remove record of them. + ### reset checksum from the revert base + ### remove any copyfrom info + ### return to schedule-normal + */ + + /* ### whoops. we cannot read pre-revert info. needs to be carried within + ### the work_item. */ + /* NOTE: we can read KIND here since uncommitted kind changes are not + (yet) allowed. If we read any conflict files, then we (obviously) have + not removed them from the metadata (yet). */ + SVN_ERR(svn_wc__db_read_info( + NULL, &kind, NULL, + NULL, NULL, NULL, + NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + &conflict_old, &conflict_new, &conflict_wrk, &prop_reject_file, + NULL, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + + /* We need the old school KIND for a few operations... */ + if (kind == svn_wc__db_kind_dir) + { + node_kind = svn_node_dir; + } + else + { + SVN_ERR_ASSERT(kind == svn_wc__db_kind_file + || kind == svn_wc__db_kind_symlink); + node_kind = svn_node_file; + } + + /* Move the "revert" props over/on the "base" props. */ + if (replaced) + { + const char *revert_props_path; + const char *base_props_path; + svn_error_t *err; + + SVN_ERR(svn_wc__prop_path(&revert_props_path, local_abspath, + node_kind, svn_wc__props_revert, + scratch_pool)); + SVN_ERR(svn_wc__prop_path(&base_props_path, local_abspath, + node_kind, svn_wc__props_base, + scratch_pool)); + + err = svn_io_file_rename(revert_props_path, base_props_path, + scratch_pool); + if (err) + { + if (!APR_STATUS_IS_ENOENT(err->apr_err)) + return svn_error_return(err); + + /* Not there. Maybe they've been renamed on a prior run. */ + svn_error_clear(err); + } + } + + /* The "working" props contain changes. Nuke 'em from orbit. */ + SVN_ERR(svn_wc__prop_path(&working_props_path, local_abspath, + node_kind, svn_wc__props_working, + scratch_pool)); + SVN_ERR(svn_io_remove_file2(working_props_path, TRUE, scratch_pool)); + + /* Deal with the working file, as needed. */ + if (kind == svn_wc__db_kind_file) + { + svn_boolean_t magic_changed; + svn_boolean_t reinstall_working; + const char *text_base_path; + svn_node_kind_t check_kind; + + SVN_ERR(svn_wc__text_base_path(&text_base_path, db, local_abspath, + FALSE, scratch_pool)); + + magic_changed = work_item->children->next->next->next->data[0] - '0'; + + /* If there was a magic property change, then we'll reinstall the + working-file to pick up any/all appropriate changes. If there was + a replacement, then we definitely want to reinstall the working-file + using the original base. */ + reinstall_working = magic_changed || replaced; + + if (replaced) + { + const char *revert_base_path; + svn_checksum_t *checksum; + + SVN_ERR(svn_wc__text_revert_path(&revert_base_path, db, + local_abspath, scratch_pool)); + SVN_ERR(svn_io_check_path(revert_base_path, &check_kind, + scratch_pool)); + if (check_kind == svn_node_file) + { + /* Move the revert base to the normal base. */ + SVN_ERR(svn_io_file_rename(revert_base_path, text_base_path, + scratch_pool)); + } + else if (check_kind == svn_node_none) + { + /* The revert base may have already been moved. Make sure + there is a normal text base. */ + + SVN_ERR(svn_io_check_path(text_base_path, &check_kind, + scratch_pool)); + if (check_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_dirent_local_style(local_abspath, scratch_pool)); + } + } + + /* At this point, the regular text base has been restored (just + now, or on a prior run). We need to recompute the checksum + from that. + + ### in wc-1, this recompute only happened for add-with-history. + ### need to investigate, but maybe the checksum was not touched + ### for a simple replacing add? regardless, this recompute is + ### always okay to do. */ + SVN_ERR(svn_io_file_checksum2(&checksum, text_base_path, + svn_checksum_md5, scratch_pool)); + tmp_entry.checksum = svn_checksum_to_cstring(checksum, scratch_pool); + modify_flags |= SVN_WC__ENTRY_MODIFY_CHECKSUM; + } + else if (!reinstall_working) + { + /* If the working file is missing, we need to reinstall it. */ + SVN_ERR(svn_io_check_path(local_abspath, &check_kind, scratch_pool)); + reinstall_working = (check_kind == svn_node_none); + + if (!reinstall_working) + { + /* ### can we optimize this call? we already fetched some + ### info about the node. and *definitely* never want a + ### full file-scan. */ + + /* ### for now, just always reinstall. without some extra work, + ### we could end up in a situation where the file is copied + ### from the base, but then something fails immediately + ### after that. on the second time through here, we would + ### see the file is "the same" and fail to complete those + ### follow-on actions. in some future work, examine the + ### points of failure, and possibly precompue the + ### "reinstall_working" flag, or maybe do some follow-on + ### actions unconditionally. */ +#if 1 + reinstall_working = TRUE; +#endif +#if 0 + SVN_ERR(svn_wc__text_modified_internal_p(&reinstall_working, + db, local_abspath, + FALSE, FALSE, + scratch_pool)); +#endif + } + } + + if (reinstall_working) + { + svn_boolean_t use_commit_times; + apr_finfo_t finfo; + + /* Copy from the text base to the working file. The working file + specifies the params for translation. */ + SVN_ERR(copy_and_translate(db, text_base_path, local_abspath, + local_abspath, scratch_pool)); + + use_commit_times = (work_item->children->next->next->next + ->next->data[0] - '0'); + + /* Possibly set the timestamp to last-commit-time, rather + than the 'now' time that already exists. */ + if (use_commit_times) + { + apr_time_t changed_date; + + /* Note: this function is not used for a pure addition. There + will always be a BASE node. */ + SVN_ERR(svn_wc__db_base_get_info(NULL, NULL, NULL, + NULL, NULL, NULL, + NULL, &changed_date, NULL, + NULL, NULL, NULL, + NULL, NULL, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + if (changed_date) + { + svn_boolean_t special; + + /* ### skip this test once db_kind_symlink is in use. */ + SVN_ERR(svn_wc__get_special(&special, db, local_abspath, + scratch_pool)); + if (!special) + SVN_ERR(svn_io_set_file_affected_time(changed_date, + local_abspath, + scratch_pool)); + } + } + + /* loggy_set_entry_timestamp_from_wc() */ + SVN_ERR(svn_io_file_affected_time(&tmp_entry.text_time, + local_abspath, + scratch_pool)); + modify_flags |= SVN_WC__ENTRY_MODIFY_TEXT_TIME; + + /* loggy_set_entry_working_size_from_wc() */ + SVN_ERR(svn_io_stat(&finfo, local_abspath, + APR_FINFO_MIN | APR_FINFO_LINK, + scratch_pool)); + tmp_entry.working_size = finfo.size; + modify_flags |= SVN_WC__ENTRY_MODIFY_WORKING_SIZE; + } + } + else if (kind == svn_wc__db_kind_symlink) + { + NOT_IMPLEMENTED(); + } + + /* ### in wc-ng: the following four blocks clear ACTUAL_NODE. */ + if (conflict_old != NULL) + { + SVN_ERR(svn_io_remove_file2(conflict_old, TRUE, scratch_pool)); + modify_flags |= SVN_WC__ENTRY_MODIFY_CONFLICT_OLD; + tmp_entry.conflict_old = NULL; + } + if (conflict_new != NULL) + { + SVN_ERR(svn_io_remove_file2(conflict_new, TRUE, scratch_pool)); + modify_flags |= SVN_WC__ENTRY_MODIFY_CONFLICT_NEW; + tmp_entry.conflict_new = NULL; + } + if (conflict_wrk != NULL) + { + SVN_ERR(svn_io_remove_file2(conflict_wrk, TRUE, scratch_pool)); + modify_flags |= SVN_WC__ENTRY_MODIFY_CONFLICT_WRK; + tmp_entry.conflict_wrk = NULL; + } + if (prop_reject_file != NULL) + { + SVN_ERR(svn_io_remove_file2(prop_reject_file, TRUE, scratch_pool)); + modify_flags |= SVN_WC__ENTRY_MODIFY_PREJFILE; + tmp_entry.prejfile = NULL; + } + + /* Clean up the copied state for all replacements. */ + if (replaced) + { + modify_flags |= (SVN_WC__ENTRY_MODIFY_COPIED + | SVN_WC__ENTRY_MODIFY_COPYFROM_URL + | SVN_WC__ENTRY_MODIFY_COPYFROM_REV); + tmp_entry.copied = FALSE; + tmp_entry.copyfrom_url = NULL; + tmp_entry.copyfrom_rev = SVN_INVALID_REVNUM; + } + + /* Reset schedule attribute to svn_wc_schedule_normal. It could already be + "normal", but no biggy if this is a no-op. */ + modify_flags |= SVN_WC__ENTRY_MODIFY_SCHEDULE; + tmp_entry.schedule = svn_wc_schedule_normal; + + SVN_ERR(svn_wc__entry_modify2(db, local_abspath, node_kind, FALSE, + &tmp_entry, modify_flags, + scratch_pool)); + + /* ### need to revert some bits in the parent stub. sigh. */ + if (kind == svn_wc__db_kind_dir) + { + svn_boolean_t is_wc_root; + + /* There is no parent stub if we're at the root. */ + SVN_ERR(svn_wc__check_wc_root(&is_wc_root, NULL, db, local_abspath, + scratch_pool)); + if (!is_wc_root) + { + modify_flags = (SVN_WC__ENTRY_MODIFY_COPIED + | SVN_WC__ENTRY_MODIFY_COPYFROM_URL + | SVN_WC__ENTRY_MODIFY_COPYFROM_REV + | SVN_WC__ENTRY_MODIFY_SCHEDULE); + tmp_entry.copied = FALSE; + tmp_entry.copyfrom_url = NULL; + tmp_entry.copyfrom_rev = SVN_INVALID_REVNUM; + tmp_entry.schedule = svn_wc_schedule_normal; + SVN_ERR(svn_wc__entry_modify2(db, local_abspath, svn_node_dir, TRUE, + &tmp_entry, modify_flags, + scratch_pool)); + } + } + + return SVN_NO_ERROR; } static const struct work_item_dispatch dispatch_table[] = { - {"revert", run_revert}, - {NULL, NULL} + { OP_REVERT, run_revert }, + + /* Sentinel. */ + { NULL } }; @@ -95,7 +482,7 @@ svn_wc__wq_run(svn_wc__db_t *db, /* Scan the dispatch table for a function to handle this work item. */ for (scan = &dispatch_table[0]; scan->name != NULL; ++scan) { - if (svn_skel__matches_atom(work_item, scan->name)) + if (svn_skel__matches_atom(work_item->children, scan->name)) { SVN_ERR((*scan->func)(db, work_item, iterpool)); break; @@ -127,10 +514,113 @@ svn_wc__wq_run(svn_wc__db_t *db, /* Record a work item to revert LOCAL_ABSPATH. */ svn_error_t * -svn_wc__wq_add_revert(svn_wc__db_t *db, +svn_wc__wq_add_revert(svn_boolean_t *will_revert, + svn_wc__db_t *db, const char *local_abspath, - svn_wc_schedule_t orig_schedule, + svn_boolean_t use_commit_times, apr_pool_t *scratch_pool) { - NOT_IMPLEMENTED(); + svn_boolean_t replaced; + svn_boolean_t magic_changed = FALSE; + + /* Gather a few items *before* the revert work-item has a chance to run. + During its operation, this data could/will change, which means that a + potential re-run of the work-item may gather incorrect values. */ + + SVN_ERR(svn_wc__internal_is_replaced(&replaced, db, local_abspath, + scratch_pool)); + + /* If a replacement has occurred, then a revert definitely happens. */ + *will_revert = replaced; + + if (!replaced) + { + apr_hash_t *base_props; + apr_hash_t *working_props; + apr_array_header_t *prop_diffs; + + SVN_ERR(svn_wc__load_props(&base_props, &working_props, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_prop_diffs(&prop_diffs, working_props, base_props, + scratch_pool)); + magic_changed = svn_wc__has_magic_property(prop_diffs); + + if (prop_diffs->nelts > 0) + { + /* Property changes cause a revert to occur. */ + *will_revert = TRUE; + } + else + { + svn_wc__db_status_t status; + svn_wc__db_kind_t kind; + + SVN_ERR(svn_wc__db_read_info( + &status, &kind, NULL, + NULL, NULL, NULL, + NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, + NULL, NULL, NULL, NULL, + NULL, NULL, + db, local_abspath, + scratch_pool, scratch_pool)); + SVN_DBG(("add_revert: path='%s' status=%d\n", local_abspath, status)); + /* There is nothing to do for NORMAL or ADDED nodes. Typically, + we won't even be called for added nodes (since a revert + simply removes it from version control), but it is possible + that a parent replacement was turned from a replaced copy + into a normal node, and the (broken) old ENTRY->COPIED logic + then turns the copied children into typical ADDED nodes. + Since the recursion has already started, these children are + visited (unlike most added nodes). */ + if (status != svn_wc__db_status_normal + && status != svn_wc__db_status_added) + { + *will_revert = TRUE; + } + + if (! *will_revert) + { + /* ### there may be ways to simplify this test, rather than + ### doing file comparisons and junk... */ + SVN_ERR(svn_wc__text_modified_internal_p(will_revert, + db, local_abspath, + FALSE, FALSE, + scratch_pool)); + } + } + } + + /* Don't even bother to queue a work item if there is nothing to do. */ + if (*will_revert) + { + svn_skel_t *work_item; + char bools[3] = { + replaced + '0', + magic_changed + '0', + use_commit_times + '0' + }; + + work_item = svn_skel__make_empty_list(scratch_pool); + + /* These skel atoms hold references to very transitory state, but + we only need the work_item to survive for the duration of wq_add. */ + svn_skel__prepend(svn_skel__mem_atom(&bools[2], 1, scratch_pool), + work_item); + svn_skel__prepend(svn_skel__mem_atom(&bools[1], 1, scratch_pool), + work_item); + svn_skel__prepend(svn_skel__mem_atom(&bools[0], 1, scratch_pool), + work_item); + svn_skel__prepend(svn_skel__str_atom(local_abspath, scratch_pool), + work_item); + svn_skel__prepend(svn_skel__str_atom(OP_REVERT, scratch_pool), + work_item); + + SVN_ERR(svn_wc__db_wq_add(db, local_abspath, work_item, scratch_pool)); + } + + return SVN_NO_ERROR; } Index: workqueue.h =================================================================== --- workqueue.h (revision 39445) +++ workqueue.h (working copy) @@ -48,9 +48,10 @@ svn_wc__wq_run(svn_wc__db_t *db, /* Record a work item to revert LOCAL_ABSPATH. */ svn_error_t * -svn_wc__wq_add_revert(svn_wc__db_t *db, +svn_wc__wq_add_revert(svn_boolean_t *will_revert, + svn_wc__db_t *db, const char *local_abspath, - svn_wc_schedule_t orig_schedule, + svn_boolean_t use_commit_times, apr_pool_t *scratch_pool);