/* Updates (by copying and translating) the eol style in OLD_TARGET returning the filename containing the correct eol style in NEW_TARGET, if an eol style change is contained in PROP_DIFF */ static svn_error_t * maybe_update_target_eols(const char **new_target, const char *old_target, svn_wc_adm_access_t *adm_access, const apr_array_header_t *prop_diff, apr_pool_t *pool) { const svn_prop_t *prop = get_prop(prop_diff, SVN_PROP_EOL_STYLE); if (prop && prop->value) { const char *eol; const char *tmp_new; svn_subst_eol_style_from_value(NULL, &eol, prop->value->data); SVN_ERR(svn_wc_create_tmp_file2(NULL, &tmp_new, svn_wc_adm_access_path(adm_access), svn_io_file_del_none, pool)); SVN_ERR(svn_subst_copy_and_translate3(old_target, tmp_new, eol, eol ? FALSE : TRUE, NULL, FALSE, FALSE, pool)); *new_target = tmp_new; } else *new_target = old_target; return SVN_NO_ERROR; }
svn_error_t * svn_wc__check_killme(svn_wc_adm_access_t *adm_access, svn_boolean_t *exists, svn_boolean_t *kill_adm_only, apr_pool_t *pool) { const char *path; svn_error_t *err; svn_stringbuf_t *contents; path = svn_wc__adm_child(svn_wc_adm_access_path(adm_access), SVN_WC__ADM_KILLME, pool); err = svn_stringbuf_from_file2(&contents, path, pool); if (err) { if (APR_STATUS_IS_ENOENT(err->apr_err)) { /* Killme file doesn't exist. */ *exists = FALSE; svn_error_clear(err); err = SVN_NO_ERROR; } return err; } *exists = TRUE; /* If the killme file contains the string 'adm-only' then only the administrative area should be removed. */ *kill_adm_only = strcmp(contents->data, SVN_WC__KILL_ADM_ONLY) == 0; return SVN_NO_ERROR; }
svn_error_t * svn_wc__remove_adm_file(const svn_wc_adm_access_t *adm_access, const char *filename, apr_pool_t *scratch_pool) { const char *path = svn_wc__adm_child(svn_wc_adm_access_path(adm_access), filename, scratch_pool); return svn_io_remove_file(path, scratch_pool); }
svn_error_t * svn_wc__make_killme(svn_wc_adm_access_t *adm_access, svn_boolean_t adm_only, apr_pool_t *pool) { const char *path; SVN_ERR(svn_wc__adm_write_check(adm_access, pool)); path = svn_wc__adm_child(svn_wc_adm_access_path(adm_access), SVN_WC__ADM_KILLME, pool); return svn_io_file_create(path, adm_only ? SVN_WC__KILL_ADM_ONLY : "", pool); }
svn_error_t * svn_wc__adm_destroy(svn_wc_adm_access_t *adm_access, apr_pool_t *scratch_pool) { const char *path; SVN_ERR(svn_wc__adm_write_check(adm_access, scratch_pool)); /* Well, the coast is clear for blowing away the administrative directory, which also removes the lock file */ path = svn_wc__adm_child(svn_wc_adm_access_path(adm_access), NULL, scratch_pool); SVN_ERR(svn_io_remove_dir2(path, FALSE, NULL, NULL, scratch_pool)); return svn_wc_adm_close2(adm_access, scratch_pool); }
svn_boolean_t svn_wc__adm_area_exists(const svn_wc_adm_access_t *adm_access, apr_pool_t *pool) { const char *path = svn_wc__adm_child(svn_wc_adm_access_path(adm_access), NULL, pool); svn_node_kind_t kind; svn_error_t *err; err = svn_io_check_path(path, &kind, pool); if (err) { svn_error_clear(err); /* Return early, since kind is undefined in this case. */ return FALSE; } return kind != svn_node_none; }
svn_error_t * svn_wc__adm_cleanup_tmp_area(const svn_wc_adm_access_t *adm_access, apr_pool_t *scratch_pool) { const char *tmp_path; /* If the admin area doesn't even *exist*, then the temp area is definitely cleaned up. */ if (!svn_wc__adm_area_exists(adm_access, scratch_pool)) return SVN_NO_ERROR; SVN_ERR(svn_wc__adm_write_check(adm_access, scratch_pool)); /* Get the path to the tmp area, and blow it away. */ tmp_path = svn_wc__adm_child(svn_wc_adm_access_path(adm_access), SVN_WC__ADM_TMP, scratch_pool); SVN_ERR(svn_io_remove_dir2(tmp_path, TRUE, NULL, NULL, scratch_pool)); /* Now, rebuild the tmp area. */ return init_adm_tmp_area(adm_access, scratch_pool); }
static svn_error_t * init_adm_tmp_area(const svn_wc_adm_access_t *adm_access, apr_pool_t *pool) { const char *path; SVN_ERR(svn_wc__adm_write_check(adm_access, pool)); path = svn_wc_adm_access_path(adm_access); /* SVN_WC__ADM_TMP */ SVN_ERR(make_adm_subdir(path, SVN_WC__ADM_TMP, FALSE, pool)); /* SVN_WC__ADM_TMP/SVN_WC__ADM_TEXT_BASE */ SVN_ERR(make_adm_subdir(path, SVN_WC__ADM_TEXT_BASE, TRUE, pool)); /* SVN_WC__ADM_TMP/SVN_WC__ADM_PROP_BASE */ SVN_ERR(make_adm_subdir(path, SVN_WC__ADM_PROP_BASE, TRUE, pool)); /* SVN_WC__ADM_TMP/SVN_WC__ADM_PROPS */ return make_adm_subdir(path, SVN_WC__ADM_PROPS, TRUE, pool); }
svn_error_t * svn_client_status4(svn_revnum_t *result_rev, const char *path, const svn_opt_revision_t *revision, svn_wc_status_func3_t status_func, void *status_baton, svn_depth_t depth, svn_boolean_t get_all, svn_boolean_t update, svn_boolean_t no_ignore, svn_boolean_t ignore_externals, const apr_array_header_t *changelists, svn_client_ctx_t *ctx, apr_pool_t *pool) { svn_wc_adm_access_t *anchor_access, *target_access; svn_wc_traversal_info_t *traversal_info = svn_wc_init_traversal_info(pool); const char *anchor, *target; const svn_delta_editor_t *editor; void *edit_baton, *set_locks_baton; const svn_wc_entry_t *entry = NULL; struct status_baton sb; apr_array_header_t *ignores; svn_error_t *err; apr_hash_t *changelist_hash = NULL; svn_revnum_t edit_revision = SVN_INVALID_REVNUM; if (changelists && changelists->nelts) SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelists, pool)); sb.real_status_func = status_func; sb.real_status_baton = status_baton; sb.deleted_in_repos = FALSE; sb.changelist_hash = changelist_hash; /* Try to open the target directory. If the target is a file or an unversioned directory, open the parent directory instead */ err = svn_wc_adm_open3(&anchor_access, NULL, path, FALSE, SVN_DEPTH_IS_RECURSIVE(depth) ? -1 : 1, ctx->cancel_func, ctx->cancel_baton, pool); if (err && err->apr_err == SVN_ERR_WC_NOT_DIRECTORY) { svn_error_clear(err); SVN_ERR(svn_wc_adm_open_anchor(&anchor_access, &target_access, &target, path, FALSE, SVN_DEPTH_IS_RECURSIVE(depth) ? -1 : 1, ctx->cancel_func, ctx->cancel_baton, pool)); } else if (!err) { target = ""; target_access = anchor_access; } else return err; anchor = svn_wc_adm_access_path(anchor_access); /* Get the status edit, and use our wrapping status function/baton as the callback pair. */ SVN_ERR(svn_wc_get_default_ignores(&ignores, ctx->config, pool)); SVN_ERR(svn_wc_get_status_editor4(&editor, &edit_baton, &set_locks_baton, &edit_revision, anchor_access, target, depth, get_all, no_ignore, ignores, tweak_status, &sb, ctx->cancel_func, ctx->cancel_baton, traversal_info, pool)); /* If we want to know about out-of-dateness, we crawl the working copy and let the RA layer drive the editor for real. Otherwise, we just close the edit. :-) */ if (update) { svn_ra_session_t *ra_session; const char *URL; svn_node_kind_t kind; svn_boolean_t server_supports_depth; /* Get full URL from the ANCHOR. */ if (! entry) SVN_ERR(svn_wc__entry_versioned(&entry, anchor, anchor_access, FALSE, pool)); if (! entry->url) return svn_error_createf (SVN_ERR_ENTRY_MISSING_URL, NULL, _("Entry '%s' has no URL"), svn_path_local_style(anchor, pool)); URL = apr_pstrdup(pool, entry->url); /* Open a repository session to the URL. */ SVN_ERR(svn_client__open_ra_session_internal(&ra_session, URL, anchor, anchor_access, NULL, FALSE, TRUE, ctx, pool)); /* Verify that URL exists in HEAD. If it doesn't, this can save us a whole lot of hassle; if it does, the cost of this request should be minimal compared to the size of getting back the average amount of "out-of-date" information. */ SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind, pool)); if (kind == svn_node_none) { /* Our status target does not exist in HEAD of the repository. If we're just adding this thing, that's fine. But if it was previously versioned, then it must have been deleted from the repository. */ if (entry->schedule != svn_wc_schedule_add) sb.deleted_in_repos = TRUE; /* And now close the edit. */ SVN_ERR(editor->close_edit(edit_baton, pool)); } else { svn_revnum_t revnum; report_baton_t rb; if (revision->kind == svn_opt_revision_head) { /* Cause the revision number to be omitted from the request, which implies HEAD. */ revnum = SVN_INVALID_REVNUM; } else { /* Get a revision number for our status operation. */ SVN_ERR(svn_client__get_revision_number (&revnum, NULL, ra_session, revision, target, pool)); } /* Do the deed. Let the RA layer drive the status editor. */ SVN_ERR(svn_ra_do_status2(ra_session, &rb.wrapped_reporter, &rb.wrapped_report_baton, target, revnum, depth, editor, edit_baton, pool)); /* Init the report baton. */ rb.ancestor = apr_pstrdup(pool, URL); rb.set_locks_baton = set_locks_baton; rb.ctx = ctx; rb.pool = pool; SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth, SVN_RA_CAPABILITY_DEPTH, pool)); /* Drive the reporter structure, describing the revisions within PATH. When we call reporter->finish_report, EDITOR will be driven to describe differences between our working copy and HEAD. */ SVN_ERR(svn_wc_crawl_revisions4(path, target_access, &lock_fetch_reporter, &rb, FALSE, depth, TRUE, (! server_supports_depth), FALSE, NULL, NULL, NULL, pool)); } } else { SVN_ERR(editor->close_edit(edit_baton, pool)); } if (ctx->notify_func2 && update) { svn_wc_notify_t *notify = svn_wc_create_notify(path, svn_wc_notify_status_completed, pool); notify->revision = edit_revision; (ctx->notify_func2)(ctx->notify_baton2, notify, pool); } /* If the caller wants the result revision, give it to them. */ if (result_rev) *result_rev = edit_revision; /* Close the access baton here, as svn_client__do_external_status() calls back into this function and thus will be re-opening the working copy. */ SVN_ERR(svn_wc_adm_close2(anchor_access, pool)); /* If there are svn:externals set, we don't want those to show up as unversioned or unrecognized, so patch up the hash. If caller wants all the statuses, we will change unversioned status items that are interesting to an svn:externals property to svn_wc_status_unversioned, otherwise we'll just remove the status item altogether. We only descend into an external if depth is svn_depth_infinity or svn_depth_unknown. However, there are conceivable behaviors that would involve descending under other circumstances; thus, we pass depth anyway, so the code will DTRT if we change the conditional in the future. */ if (SVN_DEPTH_IS_RECURSIVE(depth) && (! ignore_externals)) SVN_ERR(svn_client__do_external_status(traversal_info, status_func, status_baton, depth, get_all, update, no_ignore, ctx, pool)); return SVN_NO_ERROR; }
svn_error_t * svn_client__switch_internal(svn_revnum_t *result_rev, const char *path, const char *switch_url, const svn_opt_revision_t *peg_revision, const svn_opt_revision_t *revision, svn_wc_adm_access_t *adm_access, svn_depth_t depth, svn_boolean_t depth_is_sticky, svn_boolean_t *timestamp_sleep, svn_boolean_t ignore_externals, svn_boolean_t allow_unver_obstructions, svn_client_ctx_t *ctx, apr_pool_t *pool) { const svn_ra_reporter3_t *reporter; void *report_baton; const svn_wc_entry_t *entry; const char *URL, *anchor, *target, *source_root, *switch_rev_url; svn_ra_session_t *ra_session; svn_revnum_t revnum; svn_error_t *err = SVN_NO_ERROR; svn_wc_adm_access_t *dir_access; const svn_boolean_t close_adm_access = ! adm_access; const char *diff3_cmd; svn_boolean_t use_commit_times; svn_boolean_t sleep_here; svn_boolean_t *use_sleep = timestamp_sleep ? timestamp_sleep : &sleep_here; const svn_delta_editor_t *switch_editor; void *switch_edit_baton; svn_wc_traversal_info_t *traversal_info = svn_wc_init_traversal_info(pool); const char *preserved_exts_str; apr_array_header_t *preserved_exts; svn_boolean_t server_supports_depth; svn_config_t *cfg = ctx->config ? apr_hash_get(ctx->config, SVN_CONFIG_CATEGORY_CONFIG, APR_HASH_KEY_STRING) : NULL; /* An unknown depth can't be sticky. */ if (depth == svn_depth_unknown) depth_is_sticky = FALSE; /* Do not support the situation of both exclude and switch a target. */ if (depth_is_sticky && depth == svn_depth_exclude) return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, _("Cannot both exclude and switch a path")); /* Get the external diff3, if any. */ svn_config_get(cfg, &diff3_cmd, SVN_CONFIG_SECTION_HELPERS, SVN_CONFIG_OPTION_DIFF3_CMD, NULL); /* See if the user wants last-commit timestamps instead of current ones. */ SVN_ERR(svn_config_get_bool(cfg, &use_commit_times, SVN_CONFIG_SECTION_MISCELLANY, SVN_CONFIG_OPTION_USE_COMMIT_TIMES, FALSE)); /* See which files the user wants to preserve the extension of when conflict files are made. */ svn_config_get(cfg, &preserved_exts_str, SVN_CONFIG_SECTION_MISCELLANY, SVN_CONFIG_OPTION_PRESERVED_CF_EXTS, ""); preserved_exts = *preserved_exts_str ? svn_cstring_split(preserved_exts_str, "\n\r\t\v ", FALSE, pool) : NULL; /* Sanity check. Without these, the switch is meaningless. */ SVN_ERR_ASSERT(path); SVN_ERR_ASSERT(switch_url && (switch_url[0] != '\0')); /* ### Need to lock the whole target tree to invalidate wcprops. Does non-recursive switch really need to invalidate the whole tree? */ if (adm_access) { svn_wc_adm_access_t *a = adm_access; const char *dir_access_path; /* This is a little hacky, but open two new read-only access baton's to get the anchor and target access batons that would be used if a locked access baton was not available. */ SVN_ERR(svn_wc_adm_open_anchor(&adm_access, &dir_access, &target, path, FALSE, -1, ctx->cancel_func, ctx->cancel_baton, pool)); anchor = svn_wc_adm_access_path(adm_access); dir_access_path = svn_wc_adm_access_path(dir_access); SVN_ERR(svn_wc_adm_close2(adm_access, pool)); SVN_ERR(svn_wc_adm_retrieve(&adm_access, a, anchor, pool)); SVN_ERR(svn_wc_adm_retrieve(&dir_access, a, dir_access_path, pool)); } else { SVN_ERR(svn_wc_adm_open_anchor(&adm_access, &dir_access, &target, path, TRUE, -1, ctx->cancel_func, ctx->cancel_baton, pool)); anchor = svn_wc_adm_access_path(adm_access); } SVN_ERR(svn_wc__entry_versioned(&entry, anchor, adm_access, FALSE, pool)); if (! entry->url) return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL, _("Directory '%s' has no URL"), svn_path_local_style(anchor, pool)); URL = apr_pstrdup(pool, entry->url); /* Open an RA session to 'source' URL */ SVN_ERR(svn_client__ra_session_from_path(&ra_session, &revnum, &switch_rev_url, switch_url, adm_access, peg_revision, revision, ctx, pool)); SVN_ERR(svn_ra_get_repos_root2(ra_session, &source_root, pool)); /* Disallow a switch operation to change the repository root of the target. */ if (! svn_path_is_ancestor(source_root, URL)) return svn_error_createf (SVN_ERR_WC_INVALID_SWITCH, NULL, _("'%s'\n" "is not the same repository as\n" "'%s'"), URL, source_root); /* We may need to crop the tree if the depth is sticky */ if (depth_is_sticky && depth < svn_depth_infinity) { const svn_wc_entry_t *target_entry; SVN_ERR(svn_wc_entry( &target_entry, svn_dirent_join(svn_wc_adm_access_path(adm_access), target, pool), adm_access, TRUE, pool)); if (target_entry && target_entry->kind == svn_node_dir) { SVN_ERR(svn_wc_crop_tree(adm_access, target, depth, ctx->notify_func2, ctx->notify_baton2, ctx->cancel_func, ctx->cancel_baton, pool)); } } SVN_ERR(svn_ra_reparent(ra_session, URL, pool)); /* Fetch the switch (update) editor. If REVISION is invalid, that's okay; the RA driver will call editor->set_target_revision() later on. */ SVN_ERR(svn_wc_get_switch_editor3(&revnum, adm_access, target, switch_rev_url, use_commit_times, depth, depth_is_sticky, allow_unver_obstructions, ctx->notify_func2, ctx->notify_baton2, ctx->cancel_func, ctx->cancel_baton, ctx->conflict_func, ctx->conflict_baton, diff3_cmd, preserved_exts, &switch_editor, &switch_edit_baton, traversal_info, pool)); /* Tell RA to do an update of URL+TARGET to REVISION; if we pass an invalid revnum, that means RA will use the latest revision. */ SVN_ERR(svn_ra_do_switch2(ra_session, &reporter, &report_baton, revnum, target, depth, switch_rev_url, switch_editor, switch_edit_baton, pool)); SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth, SVN_RA_CAPABILITY_DEPTH, pool)); /* Drive the reporter structure, describing the revisions within PATH. When we call reporter->finish_report, the update_editor will be driven by svn_repos_dir_delta2. We pass in a traversal_info for recording all externals. It shouldn't be needed for a switch if it wasn't for the relative externals of type '../path'. All of those must be resolved to the new location. */ err = svn_wc_crawl_revisions4(path, dir_access, reporter, report_baton, TRUE, depth, (! depth_is_sticky), (! server_supports_depth), use_commit_times, ctx->notify_func2, ctx->notify_baton2, traversal_info, pool); if (err) { /* Don't rely on the error handling to handle the sleep later, do it now */ svn_io_sleep_for_timestamps(path, pool); return err; } *use_sleep = TRUE; /* We handle externals after the switch is complete, so that handling external items (and any errors therefrom) doesn't delay the primary operation. */ if (SVN_DEPTH_IS_RECURSIVE(depth) && (! ignore_externals)) err = svn_client__handle_externals(adm_access, traversal_info, switch_url, path, source_root, depth, use_sleep, ctx, pool); /* Sleep to ensure timestamp integrity (we do this regardless of errors in the actual switch operation(s)). */ if (sleep_here) svn_io_sleep_for_timestamps(path, pool); /* Return errors we might have sustained. */ if (err) return err; if (close_adm_access) SVN_ERR(svn_wc_adm_close2(adm_access, pool)); /* Let everyone know we're finished here. */ if (ctx->notify_func2) { svn_wc_notify_t *notify = svn_wc_create_notify(anchor, svn_wc_notify_update_completed, pool); notify->kind = svn_node_none; notify->content_state = notify->prop_state = svn_wc_notify_state_inapplicable; notify->lock_state = svn_wc_notify_lock_state_inapplicable; notify->revision = revnum; (*ctx->notify_func2)(ctx->notify_baton2, notify, pool); } /* If the caller wants the result revision, give it to them. */ if (result_rev) *result_rev = revnum; return SVN_NO_ERROR; }
static svn_error_t * entries_dump(const char *dir_path, svn_wc_adm_access_t *related, apr_pool_t *pool) { svn_wc_adm_access_t *adm_access = NULL; apr_hash_t *entries; apr_hash_index_t *hi; svn_boolean_t locked; svn_error_t *err; svn_wc_context_t *wc_ctx = NULL; const char *dir_abspath; err = svn_wc_adm_open3(&adm_access, related, dir_path, FALSE, 0, NULL, NULL, pool); if (!err) { SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL, svn_wc__adm_get_db(adm_access), pool)); SVN_ERR(svn_dirent_get_absolute(&dir_abspath, dir_path, pool)); SVN_ERR(svn_wc_locked2(NULL, &locked, wc_ctx, dir_abspath, pool)); SVN_ERR(svn_wc_entries_read(&entries, adm_access, TRUE, pool)); } else if (err && err->apr_err == SVN_ERR_WC_LOCKED && related && ! strcmp(dir_path, svn_wc_adm_access_path(related))) { /* Common caller error: Can't open a baton when there is one. */ svn_error_clear(err); SVN_ERR(svn_wc__context_create_with_db(&wc_ctx, NULL, svn_wc__adm_get_db(related), pool)); SVN_ERR(svn_dirent_get_absolute(&dir_abspath, dir_path, pool)); SVN_ERR(svn_wc_locked2(NULL, &locked, wc_ctx, dir_abspath, pool)); SVN_ERR(svn_wc_entries_read(&entries, related, TRUE, pool)); } else { const char *lockfile_path; svn_node_kind_t kind; /* ### Should svn_wc_adm_open3 be returning UPGRADE_REQUIRED? */ if (err->apr_err != SVN_ERR_WC_NOT_DIRECTORY) return err; svn_error_clear(err); adm_access = NULL; SVN_ERR(svn_dirent_get_absolute(&dir_abspath, dir_path, pool)); SVN_ERR(svn_wc__read_entries_old(&entries, dir_abspath, pool, pool)); lockfile_path = svn_dirent_join_many(pool, dir_path, svn_wc_get_adm_dir(pool), "lock", SVN_VA_NULL); SVN_ERR(svn_io_check_path(lockfile_path, &kind, pool)); locked = (kind == svn_node_file); } for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi)) { const char *key = apr_hash_this_key(hi); const svn_wc_entry_t *entry = apr_hash_this_val(hi); SVN_ERR_ASSERT(strcmp(key, entry->name) == 0); printf("e = Entry()\n"); str_value("name", entry->name); int_value("revision", entry->revision); str_value("url", entry->url); str_value("repos", entry->repos); str_value("uuid", entry->uuid); int_value("kind", entry->kind); int_value("schedule", entry->schedule); bool_value("copied", entry->copied); bool_value("deleted", entry->deleted); bool_value("absent", entry->absent); bool_value("incomplete", entry->incomplete); str_value("copyfrom_url", entry->copyfrom_url); int_value("copyfrom_rev", entry->copyfrom_rev); str_value("conflict_old", entry->conflict_old); str_value("conflict_new", entry->conflict_new); str_value("conflict_wrk", entry->conflict_wrk); str_value("prejfile", entry->prejfile); /* skip: text_time */ /* skip: prop_time */ /* skip: checksum */ int_value("cmt_rev", entry->cmt_rev); /* skip: cmt_date */ str_value("cmt_author", entry->cmt_author); str_value("lock_token", entry->lock_token); str_value("lock_owner", entry->lock_owner); str_value("lock_comment", entry->lock_comment); /* skip: lock_creation_date */ /* skip: has_props */ /* skip: has_prop_mods */ /* skip: cachable_props */ /* skip: present_props */ str_value("changelist", entry->changelist); /* skip: working_size */ /* skip: keep_local */ int_value("depth", entry->depth); /* skip: tree_conflict_data */ bool_value("file_external", entry->file_external_path != NULL); /* skip: file_external_peg_rev */ /* skip: file_external_rev */ bool_value("locked", locked && *entry->name == '\0'); printf("entries['%s'] = e\n", (const char *)key); } if (wc_ctx) SVN_ERR(svn_wc_context_destroy(wc_ctx)); if (adm_access) SVN_ERR(svn_wc_adm_close2(adm_access, pool)); return SVN_NO_ERROR; }
/* Internal version of svn_wc_merge, also used to (loggily) merge updates from the repository. In the case of updating, the update can have sent new properties, which could affect the way the wc target is detranslated and compared with LEFT and RIGHT for merging. Property changes sent by the update are provided in PROP_DIFF. If COPYFROM_TEXT is non-null, the working text is taken from this file instead of from the actual version in the working copy (and the merge_target is allowed to not be under version control in this case). */ svn_error_t * svn_wc__merge_internal(svn_stringbuf_t **log_accum, enum svn_wc_merge_outcome_t *merge_outcome, const char *left, const char *right, const char *merge_target, const char *copyfrom_text, svn_wc_adm_access_t *adm_access, const char *left_label, const char *right_label, const char *target_label, svn_boolean_t dry_run, const char *diff3_cmd, const apr_array_header_t *merge_options, const apr_array_header_t *prop_diff, svn_wc_conflict_resolver_func_t conflict_func, void *conflict_baton, apr_pool_t *pool) { const char *tmp_target, *result_target, *working_text; const char *adm_path = svn_wc_adm_access_path(adm_access); apr_file_t *result_f; svn_boolean_t is_binary = FALSE; const svn_wc_entry_t *entry; svn_boolean_t contains_conflicts; const svn_prop_t *mimeprop; /* Sanity check: the merge target must be under revision control (unless this is an add-with-history). */ SVN_ERR(svn_wc_entry(&entry, merge_target, adm_access, FALSE, pool)); if (! entry && ! copyfrom_text) { *merge_outcome = svn_wc_merge_no_merge; return SVN_NO_ERROR; } /* Decide if the merge target is a text or binary file. */ if ((mimeprop = get_prop(prop_diff, SVN_PROP_MIME_TYPE)) && mimeprop->value) is_binary = svn_mime_type_is_binary(mimeprop->value->data); else if (! copyfrom_text) SVN_ERR(svn_wc_has_binary_prop(&is_binary, merge_target, adm_access, pool)); working_text = copyfrom_text ? copyfrom_text : merge_target; SVN_ERR(detranslate_wc_file(&tmp_target, working_text, adm_access, (! is_binary) && diff3_cmd != NULL, prop_diff, pool)); /* We cannot depend on the left file to contain the same eols as the right file. If the merge target has mods, this will mark the entire file as conflicted, so we need to compensate. */ SVN_ERR(maybe_update_target_eols(&left, left, adm_access, prop_diff, pool)); if (! is_binary) /* this is a text file */ { /* Open a second temporary file for writing; this is where diff3 will write the merged results. */ SVN_ERR(svn_wc_create_tmp_file2(&result_f, &result_target, adm_path, svn_io_file_del_none, pool)); /* Run an external merge if requested. */ if (diff3_cmd) { int exit_code; SVN_ERR(svn_io_run_diff3_2(&exit_code, ".", tmp_target, left, right, target_label, left_label, right_label, result_f, diff3_cmd, merge_options, pool)); contains_conflicts = exit_code == 1; } else { svn_diff_t *diff; const char *target_marker; const char *left_marker; const char *right_marker; svn_stream_t *ostream; svn_diff_file_options_t *options; ostream = svn_stream_from_aprfile(result_f, pool); options = svn_diff_file_options_create(pool); if (merge_options) SVN_ERR(svn_diff_file_options_parse(options, merge_options, pool)); SVN_ERR(svn_diff_file_diff3_2(&diff, left, tmp_target, right, options, pool)); /* Labels fall back to sensible defaults if not specified. */ if (target_label) target_marker = apr_psprintf(pool, "<<<<<<< %s", target_label); else target_marker = "<<<<<<< .working"; if (left_label) left_marker = apr_psprintf(pool, "||||||| %s", left_label); else left_marker = "||||||| .old"; if (right_label) right_marker = apr_psprintf(pool, ">>>>>>> %s", right_label); else right_marker = ">>>>>>> .new"; SVN_ERR(svn_diff_file_output_merge(ostream, diff, left, tmp_target, right, left_marker, target_marker, right_marker, "=======", /* seperator */ FALSE, /* display original */ FALSE, /* resolve conflicts */ pool)); SVN_ERR(svn_stream_close(ostream)); contains_conflicts = svn_diff_contains_conflicts(diff); } /* Close the output file */ SVN_ERR(svn_io_file_close(result_f, pool)); if (contains_conflicts && ! dry_run) /* got a conflict */ { const char *left_copy, *right_copy, *target_copy; const char *tmp_left, *tmp_right, *tmp_target_copy; const char *parentt, *target_base; svn_wc_adm_access_t *parent_access; svn_wc_entry_t tmp_entry; /* Give the conflict resolution callback a chance to clean up the conflict before we mark the file 'conflicted' */ if (conflict_func) { svn_wc_conflict_result_t *result = NULL; svn_wc_conflict_description_t cdesc; cdesc.path = merge_target; cdesc.node_kind = svn_node_file; cdesc.kind = svn_wc_conflict_kind_text; cdesc.is_binary = FALSE; cdesc.mime_type = (mimeprop && mimeprop->value) ? mimeprop->value->data : NULL; cdesc.access = adm_access; cdesc.action = svn_wc_conflict_action_edit; cdesc.reason = svn_wc_conflict_reason_edited; cdesc.base_file = left; cdesc.their_file = right; cdesc.my_file = tmp_target; cdesc.merged_file = result_target; cdesc.property_name = NULL; SVN_ERR(conflict_func(&result, &cdesc, conflict_baton, pool)); if (result == NULL) return svn_error_create(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, _("Conflict callback violated API:" " returned no results")); switch (result->choice) { /* If the callback wants to use one of the fulltexts to resolve the conflict, so be it.*/ case svn_wc_conflict_choose_base: { SVN_ERR(svn_wc__loggy_copy (log_accum, NULL, adm_access, svn_wc__copy_translate, left, merge_target, FALSE, pool)); *merge_outcome = svn_wc_merge_merged; contains_conflicts = FALSE; goto merge_complete; } case svn_wc_conflict_choose_theirs_full: { SVN_ERR(svn_wc__loggy_copy (log_accum, NULL, adm_access, svn_wc__copy_translate, right, merge_target, FALSE, pool)); *merge_outcome = svn_wc_merge_merged; contains_conflicts = FALSE; goto merge_complete; } case svn_wc_conflict_choose_mine_full: { /* Do nothing to merge_target, let it live untouched! */ *merge_outcome = svn_wc_merge_merged; contains_conflicts = FALSE; goto merge_complete; } /* For the case of 3-way file merging, we don't really distinguish between these return values; if the callback claims to have "generally resolved" the situation, we still interpret that as "OK, we'll assume the merged version is good to use". */ case svn_wc_conflict_choose_merged: { SVN_ERR(svn_wc__loggy_copy (log_accum, NULL, adm_access, svn_wc__copy_translate, /* Look for callback's own merged-file first: */ result->merged_file ? result->merged_file : result_target, merge_target, FALSE, pool)); *merge_outcome = svn_wc_merge_merged; contains_conflicts = FALSE; goto merge_complete; } case svn_wc_conflict_choose_postpone: default: { /* Assume conflict remains, fall through to code below. */ } } } /* Preserve the three pre-merge files, and modify the entry (mark as conflicted, track the preserved files). */ /* I miss Lisp. */ SVN_ERR(svn_io_open_unique_file2(NULL, &left_copy, merge_target, left_label, svn_io_file_del_none, pool)); /* Have I mentioned how much I miss Lisp? */ SVN_ERR(svn_io_open_unique_file2(NULL, &right_copy, merge_target, right_label, svn_io_file_del_none, pool)); /* Why, how much more pleasant to be forced to unroll my loops. If I'd been writing in Lisp, I might have mapped an inline lambda form over a list, or something equally disgusting. Thank goodness C was here to protect me! */ SVN_ERR(svn_io_open_unique_file2(NULL, &target_copy, merge_target, target_label, svn_io_file_del_none, pool)); /* We preserve all the files with keywords expanded and line endings in local (working) form. */ svn_path_split(target_copy, &parentt, &target_base, pool); SVN_ERR(svn_wc_adm_retrieve(&parent_access, adm_access, parentt, pool)); /* Log files require their paths to be in the subtree relative to the adm_access path they are executed in. Make our LEFT and RIGHT files 'local' if they aren't... */ if (! svn_path_is_child(adm_path, left, pool)) { SVN_ERR(svn_wc_create_tmp_file2 (NULL, &tmp_left, adm_path, svn_io_file_del_none, pool)); SVN_ERR(svn_io_copy_file(left, tmp_left, TRUE, pool)); } else tmp_left = left; if (! svn_path_is_child(adm_path, right, pool)) { SVN_ERR(svn_wc_create_tmp_file2 (NULL, &tmp_right, adm_path, svn_io_file_del_none, pool)); SVN_ERR(svn_io_copy_file(right, tmp_right, TRUE, pool)); } else tmp_right = right; /* NOTE: Callers must ensure that the svn:eol-style and svn:keywords property values are correct in the currently installed props. With 'svn merge', it's no big deal. But when 'svn up' calls this routine, it needs to make sure that this routine is using the newest property values that may have been received *during* the update. Since this routine will be run from within a log-command, merge_file() needs to make sure that a previous log-command to 'install latest props' has already executed first. Ben and I just checked, and that is indeed the order in which the log items are written, so everything should be fine. Really. */ /* Create LEFT and RIGHT backup files, in expanded form. We use merge_target's current properties to do the translation. */ /* Derive the basenames of the 3 backup files. */ SVN_ERR(svn_wc__loggy_translated_file(log_accum, adm_access, left_copy, tmp_left, merge_target, pool)); SVN_ERR(svn_wc__loggy_translated_file(log_accum, adm_access, right_copy, tmp_right, merge_target, pool)); /* Back up MERGE_TARGET through detranslation/retranslation: the new translation properties may not match the current ones */ SVN_ERR(svn_wc_translated_file2(&tmp_target_copy, merge_target, merge_target, adm_access, SVN_WC_TRANSLATE_TO_NF | SVN_WC_TRANSLATE_NO_OUTPUT_CLEANUP, pool)); SVN_ERR(svn_wc__loggy_translated_file (log_accum, adm_access, target_copy, tmp_target_copy, merge_target, pool)); tmp_entry.conflict_old = svn_path_is_child(adm_path, left_copy, pool); tmp_entry.conflict_new = svn_path_is_child(adm_path, right_copy, pool); tmp_entry.conflict_wrk = target_base; /* Mark merge_target's entry as "Conflicted", and start tracking the backup files in the entry as well. */ SVN_ERR(svn_wc__loggy_entry_modify (log_accum, adm_access, merge_target, &tmp_entry, SVN_WC__ENTRY_MODIFY_CONFLICT_OLD | SVN_WC__ENTRY_MODIFY_CONFLICT_NEW | SVN_WC__ENTRY_MODIFY_CONFLICT_WRK, pool)); *merge_outcome = svn_wc_merge_conflict; } else if (contains_conflicts && dry_run) { *merge_outcome = svn_wc_merge_conflict; } /* end of conflict handling */ else if (copyfrom_text) { *merge_outcome = svn_wc_merge_merged; } else { svn_boolean_t same, special; /* If 'special', then use the detranslated form of the target file. This is so we don't try to follow symlinks, but the same treatment is probably also appropriate for whatever special file types we may invent in the future. */ SVN_ERR(svn_wc__get_special(&special, merge_target, adm_access, pool)); SVN_ERR(svn_io_files_contents_same_p(&same, result_target, (special ? tmp_target : merge_target), pool)); *merge_outcome = same ? svn_wc_merge_unchanged : svn_wc_merge_merged; } if (*merge_outcome != svn_wc_merge_unchanged && ! dry_run) /* replace MERGE_TARGET with the new merged file, expanding. */ SVN_ERR(svn_wc__loggy_copy(log_accum, NULL, adm_access, svn_wc__copy_translate, result_target, merge_target, FALSE, pool)); } /* end of merging for text files */ else if (! dry_run) /* merging procedure for binary files */ { /* ### when making the binary-file backups, should we be honoring keywords and eol stuff? */ const char *left_copy, *right_copy; const char *parentt, *left_base, *right_base; svn_wc_entry_t tmp_entry; /* Give the conflict resolution callback a chance to clean up the conflict before we mark the file 'conflicted' */ if (conflict_func) { svn_wc_conflict_result_t *result = NULL; svn_wc_conflict_description_t cdesc; cdesc.path = merge_target; cdesc.node_kind = svn_node_file; cdesc.kind = svn_wc_conflict_kind_text; cdesc.is_binary = TRUE; cdesc.mime_type = (mimeprop && mimeprop->value) ? mimeprop->value->data : NULL; cdesc.access = adm_access; cdesc.action = svn_wc_conflict_action_edit; cdesc.reason = svn_wc_conflict_reason_edited; cdesc.base_file = left; cdesc.their_file = right; cdesc.my_file = tmp_target; cdesc.merged_file = NULL; /* notice there is NO merged file! */ cdesc.property_name = NULL; SVN_ERR(conflict_func(&result, &cdesc, conflict_baton, pool)); if (result == NULL) return svn_error_create(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, _("Conflict callback violated API:" " returned no results")); switch (result->choice) { /* For a binary file, there's no merged file to look at, unless the conflict-callback did the merging itself. */ case svn_wc_conflict_choose_base: { SVN_ERR(svn_wc__loggy_copy (log_accum, NULL, adm_access, svn_wc__copy_translate, left, merge_target, FALSE, pool)); *merge_outcome = svn_wc_merge_merged; contains_conflicts = FALSE; goto merge_complete; } case svn_wc_conflict_choose_theirs_full: { SVN_ERR(svn_wc__loggy_copy (log_accum, NULL, adm_access, svn_wc__copy_translate, right, merge_target, FALSE, pool)); *merge_outcome = svn_wc_merge_merged; contains_conflicts = FALSE; goto merge_complete; } /* For a binary file, if the response is to use the user's file, we do nothing. We also do nothing if the response claims to have already resolved the problem.*/ case svn_wc_conflict_choose_mine_full: { *merge_outcome = svn_wc_merge_merged; contains_conflicts = FALSE; goto merge_complete; } case svn_wc_conflict_choose_merged: { if (! result->merged_file) { /* Callback asked us to choose its own merged file, but didn't provide one! */ return svn_error_create (SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, _("Conflict callback violated API:" " returned no merged file")); } else { SVN_ERR(svn_wc__loggy_copy (log_accum, NULL, adm_access, svn_wc__copy_translate, result->merged_file, merge_target, FALSE, pool)); *merge_outcome = svn_wc_merge_merged; contains_conflicts = FALSE; goto merge_complete; } } case svn_wc_conflict_choose_postpone: default: { /* Assume conflict remains, fall through to code below. */ } } } /* reserve names for backups of left and right fulltexts */ SVN_ERR(svn_io_open_unique_file2(NULL, &left_copy, merge_target, left_label, svn_io_file_del_none, pool)); SVN_ERR(svn_io_open_unique_file2(NULL, &right_copy, merge_target, right_label, svn_io_file_del_none, pool)); /* create the backup files */ SVN_ERR(svn_io_copy_file(left, left_copy, TRUE, pool)); SVN_ERR(svn_io_copy_file(right, right_copy, TRUE, pool)); /* Was the merge target detranslated? */ if (merge_target != tmp_target) { /* Create a .mine file too */ const char *mine_copy; SVN_ERR(svn_io_open_unique_file2(NULL, &mine_copy, merge_target, target_label, svn_io_file_del_none, pool)); SVN_ERR(svn_wc__loggy_move(log_accum, NULL, adm_access, tmp_target, mine_copy, FALSE, pool)); mine_copy = svn_path_is_child(adm_path, mine_copy, pool); tmp_entry.conflict_wrk = mine_copy; } else tmp_entry.conflict_wrk = NULL; /* Derive the basenames of the backup files. */ svn_path_split(left_copy, &parentt, &left_base, pool); svn_path_split(right_copy, &parentt, &right_base, pool); /* Mark merge_target's entry as "Conflicted", and start tracking the backup files in the entry as well. */ tmp_entry.conflict_old = left_base; tmp_entry.conflict_new = right_base; SVN_ERR(svn_wc__loggy_entry_modify (log_accum, adm_access, merge_target, &tmp_entry, SVN_WC__ENTRY_MODIFY_CONFLICT_OLD | SVN_WC__ENTRY_MODIFY_CONFLICT_NEW | SVN_WC__ENTRY_MODIFY_CONFLICT_WRK, pool)); *merge_outcome = svn_wc_merge_conflict; /* a conflict happened */ } /* end of binary conflict handling */ else *merge_outcome = svn_wc_merge_conflict; /* dry_run for binary files. */ merge_complete: /* Merging is complete. Regardless of text or binariness, we might need to tweak the executable bit on the new working file. */ if (! dry_run) { SVN_ERR(svn_wc__loggy_maybe_set_executable(log_accum, adm_access, merge_target, pool)); SVN_ERR(svn_wc__loggy_maybe_set_readonly(log_accum, adm_access, merge_target, pool)); } return SVN_NO_ERROR; }
/* Detranslate a working copy file MERGE_TARGET to achieve the effect of: 1. Detranslate 2. Install new props 3. Retranslate 4. Detranslate in 1 pass to get a file which can be compared with the left and right files which were created with the 'new props' above. Property changes make this a little complex though. Changes in - svn:mime-type - svn:eol-style - svn:keywords - svn:special may change the way a file is translated. Effect for svn:mime-type: The value for svn:mime-type affects the translation wrt keywords and eol-style settings. I) both old and new mime-types are texty -> just do the translation dance (as lined out below) II) the old one is texty, the new one is binary -> detranslate with the old eol-style and keywords (the new re+detranslation is a no-op) III) the old one is binary, the new one texty -> detranslate with the new eol-style (the old detranslation is a no-op) IV) the old and new ones are binary -> don't detranslate, just make a straight copy Effect for svn:eol-style I) On add or change use the new value II) otherwise: use the old value (absent means 'no translation') Effect for svn:keywords Always use old settings (re+detranslation are no-op) Effect for svn:special Always use the old settings (same reasons as for svn:keywords) */ static svn_error_t * detranslate_wc_file(const char **detranslated_file, const char *merge_target, svn_wc_adm_access_t *adm_access, svn_boolean_t force_copy, const apr_array_header_t *prop_diff, apr_pool_t *pool) { svn_boolean_t is_binary; const svn_prop_t *prop; svn_subst_eol_style_t style; const char *eol; apr_hash_t *keywords; svn_boolean_t special; /* Decide if the merge target currently is a text or binary file. */ SVN_ERR(svn_wc_has_binary_prop(&is_binary, merge_target, adm_access, pool)); /* See if we need to do a straight copy: - old and new mime-types are binary, or - old mime-type is binary and no new mime-type specified */ if (is_binary && (((prop = get_prop(prop_diff, SVN_PROP_MIME_TYPE)) && prop->value && svn_mime_type_is_binary(prop->value->data)) || prop == NULL)) { /* this is case IV above */ keywords = NULL; special = FALSE; eol = NULL; style = svn_subst_eol_style_none; } else if ((!is_binary) && (prop = get_prop(prop_diff, SVN_PROP_MIME_TYPE)) && prop->value && svn_mime_type_is_binary(prop->value->data)) { /* Old props indicate texty, new props indicate binary: detranslate keywords and old eol-style */ SVN_ERR(svn_wc__get_keywords(&keywords, merge_target, adm_access, NULL, pool)); SVN_ERR(svn_wc__get_special(&special, merge_target, adm_access, pool)); } else { /* New props indicate texty, regardless of old props */ /* In case the file used to be special, detranslate specially */ SVN_ERR(svn_wc__get_special(&special, merge_target, adm_access, pool)); if (special) { keywords = NULL; eol = NULL; style = svn_subst_eol_style_none; } else { /* In case a new eol style was set, use that for detranslation */ if ((prop = get_prop(prop_diff, SVN_PROP_EOL_STYLE)) && prop->value) { /* Value added or changed */ svn_subst_eol_style_from_value(&style, &eol, prop->value->data); } else if (!is_binary) SVN_ERR(svn_wc__get_eol_style(&style, &eol, merge_target, adm_access, pool)); else { eol = NULL; style = svn_subst_eol_style_none; } /* In case there were keywords, detranslate with keywords (iff we were texty) */ if (!is_binary) SVN_ERR(svn_wc__get_keywords(&keywords, merge_target, adm_access, NULL, pool)); else keywords = NULL; } } /* Now, detranslate with the settings we created above */ if (force_copy || keywords || eol || special) { const char *detranslated; /* Force a copy into the temporary wc area to avoid having temporary files created below to appear in the actual wc. */ SVN_ERR(svn_wc_create_tmp_file2 (NULL, &detranslated, svn_wc_adm_access_path(adm_access), svn_io_file_del_none, pool)); SVN_ERR(svn_subst_translate_to_normal_form(merge_target, detranslated, style, eol, eol ? FALSE : TRUE, keywords, special, pool)); *detranslated_file = detranslated; } else *detranslated_file = merge_target; return SVN_NO_ERROR; }