static svn_error_t * svn_ra_local__reparent(svn_ra_session_t *session, const char *url, apr_pool_t *pool) { svn_ra_local__session_baton_t *sess = session->priv; const char *relpath = ""; /* If the new URL isn't the same as our repository root URL, then let's ensure that it's some child of it. */ if (strcmp(url, sess->repos_url) != 0) relpath = svn_path_is_child(sess->repos_url, url, pool); if (! relpath) return svn_error_createf (SVN_ERR_RA_ILLEGAL_URL, NULL, _("URL '%s' is not a child of the session's repository root " "URL '%s'"), url, sess->repos_url); /* Update our FS_PATH sess member to point to our new relative-URL-turned-absolute-filesystem-path. */ relpath = apr_pstrcat(pool, "/", svn_path_uri_decode(relpath, pool), NULL); svn_stringbuf_set(sess->fs_path, relpath); return SVN_NO_ERROR; }
/* Return the portion of URL that is relative to ANCHOR. */ static const char * subtract_anchor(const char *anchor, const char *url, apr_pool_t *pool) { if (! strcmp(url, anchor)) return ""; else return svn_path_uri_decode(svn_path_is_child(anchor, url, pool), pool); }
const char * local_style_skip_ancestor(const char *parent_path, const char *path, apr_pool_t *subpool) { const char *relpath = NULL; if (parent_path) { relpath = svn_path_is_child(parent_path, path, pool); if (!relpath) { if (strcmp(path, parent_path) == 0) relpath = "."; else relpath = path; } } return svn_path_local_style(relpath ? relpath : path, subpool); }
svn_error_t * svn_path_remove_redundancies(apr_array_header_t **pcondensed_targets, const apr_array_header_t *targets, apr_pool_t *pool) { apr_pool_t *temp_pool; apr_array_header_t *abs_targets; apr_array_header_t *rel_targets; int i; if ((targets->nelts <= 0) || (! pcondensed_targets)) { /* No targets or no place to store our work means this function really has nothing to do. */ if (pcondensed_targets) *pcondensed_targets = NULL; return SVN_NO_ERROR; } /* Initialize our temporary pool. */ temp_pool = svn_pool_create(pool); /* Create our list of absolute paths for our "keepers" */ abs_targets = apr_array_make(temp_pool, targets->nelts, sizeof(const char *)); /* Create our list of untainted paths for our "keepers" */ rel_targets = apr_array_make(pool, targets->nelts, sizeof(const char *)); /* For each target in our list we do the following: 1. Calculate its absolute path (ABS_PATH). 2. See if any of the keepers in ABS_TARGETS is a parent of, or is the same path as, ABS_PATH. If so, we ignore this target. If not, however, add this target's absolute path to ABS_TARGETS and its original path to REL_TARGETS. */ for (i = 0; i < targets->nelts; i++) { const char *rel_path = APR_ARRAY_IDX(targets, i, const char *); const char *abs_path; int j; svn_boolean_t keep_me; /* Get the absolute path for this target. */ SVN_ERR(svn_path_get_absolute(&abs_path, rel_path, temp_pool)); /* For each keeper in ABS_TARGETS, see if this target is the same as or a child of that keeper. */ keep_me = TRUE; for (j = 0; j < abs_targets->nelts; j++) { const char *keeper = APR_ARRAY_IDX(abs_targets, j, const char *); /* Quit here if we find this path already in the keepers. */ if (strcmp(keeper, abs_path) == 0) { keep_me = FALSE; break; } /* Quit here if this path is a child of one of the keepers. */ if (svn_path_is_child(keeper, abs_path, temp_pool)) { keep_me = FALSE; break; } } /* If this is a new keeper, add its absolute path to ABS_TARGETS and its original path to REL_TARGETS. */ if (keep_me) { APR_ARRAY_PUSH(abs_targets, const char *) = abs_path; APR_ARRAY_PUSH(rel_targets, const char *) = rel_path; } } /* Destroy our temporary pool. */ svn_pool_destroy(temp_pool); /* Make sure we return the list of untainted keeper paths. */ *pcondensed_targets = rel_targets; return SVN_NO_ERROR; }
/* Helper func for recursively fetching svn_dirent_t's from a remote directory and pushing them at an info-receiver callback. DEPTH is the depth starting at DIR, even though RECEIVER is never invoked on DIR: if DEPTH is svn_depth_immediates, then invoke RECEIVER on all children of DIR, but none of their children; if svn_depth_files, then invoke RECEIVER on file children of DIR but not on subdirectories; if svn_depth_infinity, recurse fully. */ static svn_error_t * push_dir_info(svn_ra_session_t *ra_session, const char *session_URL, const char *dir, svn_revnum_t rev, const char *repos_UUID, const char *repos_root, svn_info_receiver_t receiver, void *receiver_baton, svn_depth_t depth, svn_client_ctx_t *ctx, apr_hash_t *locks, apr_pool_t *pool) { apr_hash_t *tmpdirents; svn_dirent_t *the_ent; svn_info_t *info; apr_hash_index_t *hi; apr_pool_t *subpool = svn_pool_create(pool); SVN_ERR(svn_ra_get_dir2(ra_session, &tmpdirents, NULL, NULL, dir, rev, DIRENT_FIELDS, pool)); for (hi = apr_hash_first(pool, tmpdirents); hi; hi = apr_hash_next(hi)) { const char *path, *URL, *fs_path; const void *key; svn_lock_t *lock; void *val; svn_pool_clear(subpool); if (ctx->cancel_func) SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); apr_hash_this(hi, &key, NULL, &val); the_ent = val; path = svn_path_join(dir, key, subpool); URL = svn_path_url_add_component(session_URL, key, subpool); fs_path = svn_path_is_child(repos_root, URL, subpool); fs_path = apr_pstrcat(subpool, "/", fs_path, NULL); fs_path = svn_path_uri_decode(fs_path, subpool); lock = apr_hash_get(locks, fs_path, APR_HASH_KEY_STRING); SVN_ERR(build_info_from_dirent(&info, the_ent, lock, URL, rev, repos_UUID, repos_root, subpool)); if (depth >= svn_depth_immediates || (depth == svn_depth_files && the_ent->kind == svn_node_file)) { SVN_ERR(receiver(receiver_baton, path, info, subpool)); } if (depth == svn_depth_infinity && the_ent->kind == svn_node_dir) { SVN_ERR(push_dir_info(ra_session, URL, path, rev, repos_UUID, repos_root, receiver, receiver_baton, depth, ctx, locks, subpool)); } } svn_pool_destroy(subpool); 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; }
svn_error_t * svn_fs_bdb__changes_fetch(apr_hash_t **changes_p, svn_fs_t *fs, const char *key, trail_t *trail, apr_pool_t *pool) { base_fs_data_t *bfd = fs->fsap_data; DBC *cursor; DBT query, result; int db_err = 0, db_c_err = 0; svn_error_t *err = SVN_NO_ERROR; apr_hash_t *changes = apr_hash_make(pool); apr_pool_t *subpool = svn_pool_create(pool); /* Get a cursor on the first record matching KEY, and then loop over the records, adding them to the return array. */ svn_fs_base__trail_debug(trail, "changes", "cursor"); SVN_ERR(BDB_WRAP(fs, _("creating cursor for reading changes"), bfd->changes->cursor(bfd->changes, trail->db_txn, &cursor, 0))); /* Advance the cursor to the key that we're looking for. */ svn_fs_base__str_to_dbt(&query, key); svn_fs_base__result_dbt(&result); db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_SET); if (! db_err) svn_fs_base__track_dbt(&result, pool); while (! db_err) { change_t *change; skel_t *result_skel; /* Clear the per-iteration subpool. */ svn_pool_clear(subpool); /* RESULT now contains a change record associated with KEY. We need to parse that skel into an change_t structure ... */ result_skel = svn_fs_base__parse_skel(result.data, result.size, subpool); if (! result_skel) { err = svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, _("Error reading changes for key '%s'"), key); goto cleanup; } err = svn_fs_base__parse_change_skel(&change, result_skel, subpool); if (err) goto cleanup; /* ... and merge it with our return hash. */ err = fold_change(changes, change); if (err) goto cleanup; /* Now, if our change was a deletion or replacement, we have to blow away any changes thus far on paths that are (or, were) children of this path. ### i won't bother with another iteration pool here -- at most we talking about a few extra dups of paths into what is already a temporary subpool. */ if ((change->kind == svn_fs_path_change_delete) || (change->kind == svn_fs_path_change_replace)) { apr_hash_index_t *hi; for (hi = apr_hash_first(subpool, changes); hi; hi = apr_hash_next(hi)) { /* KEY is the path. */ const void *hashkey; apr_ssize_t klen; apr_hash_this(hi, &hashkey, &klen, NULL); /* If we come across our own path, ignore it. */ if (strcmp(change->path, hashkey) == 0) continue; /* If we come across a child of our path, remove it. */ if (svn_path_is_child(change->path, hashkey, subpool)) apr_hash_set(changes, hashkey, klen, NULL); } } /* Advance the cursor to the next record with this same KEY, and fetch that record. */ svn_fs_base__result_dbt(&result); db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_NEXT_DUP); if (! db_err) svn_fs_base__track_dbt(&result, pool); } /* Destroy the per-iteration subpool. */ svn_pool_destroy(subpool); /* If there are no (more) change records for this KEY, we're finished. Just return the (possibly empty) array. Any other error, however, needs to get handled appropriately. */ if (db_err && (db_err != DB_NOTFOUND)) err = BDB_WRAP(fs, _("fetching changes"), db_err); cleanup: /* Close the cursor. */ db_c_err = svn_bdb_dbc_close(cursor); /* If we had an error prior to closing the cursor, return the error. */ if (err) return err; /* If our only error thus far was when we closed the cursor, return that error. */ if (db_c_err) SVN_ERR(BDB_WRAP(fs, _("closing changes cursor"), db_c_err)); /* Finally, set our return variable and get outta here. */ *changes_p = changes; return SVN_NO_ERROR; }