/* Remove every no-op property change from CHANGES: that is, remove every entry in which the target value is the same as the value of the corresponding property in PRISTINE_PROPS. Issue #3657 'dav update report handler in skelta mode can cause spurious conflicts'. When communicating with the repository via ra_serf and ra_neon, the change_dir_prop and change_file_prop svn_delta_editor_t callbacks are called (obviously) when a directory or file property has changed between the start and end of the edit. Less obvious however, is that these callbacks may be made describing *all* of the properties on FILE_BATON->PATH when using the DAV providers, not just the change(s). (Specifically ra_neon does this for diff/merge and ra_serf does it for diff/merge/update/switch). This means that the change_[file|dir]_prop svn_delta_editor_t callbacks may be made where there are no property changes (i.e. a noop change of NAME from VALUE to VALUE). Normally this is harmless, but during a merge it can result in spurious conflicts if the WC's pristine property NAME has a value other than VALUE. In an ideal world the mod_dav_svn update report handler, when in 'skelta' mode and describing changes to a path on which a property has changed, wouldn't ask the client to later fetch all properties and figure out what has changed itself. The server already knows which properties have changed! Regardless, such a change is not yet implemented, and even when it is, the client should DTRT with regard to older servers which behave this way. Hence this little hack: We populate FILE_BATON->PROPCHANGES only with *actual* property changes. See http://subversion.tigris.org/issues/show_bug.cgi?id=3657#desc9 and http://svn.haxx.se/dev/archive-2010-08/0351.shtml for more details. */ static void remove_non_prop_changes(apr_hash_t *pristine_props, apr_array_header_t *changes) { int i; for (i = 0; i < changes->nelts; i++) { svn_prop_t *change = &APR_ARRAY_IDX(changes, i, svn_prop_t); if (change->value) { const svn_string_t *old_val = apr_hash_get(pristine_props, change->name, APR_HASH_KEY_STRING); if (old_val && svn_string_compare(old_val, change->value)) { int j; /* Remove the matching change by shifting the rest */ for (j = i; j < changes->nelts - 1; j++) { APR_ARRAY_IDX(changes, j, svn_prop_t) = APR_ARRAY_IDX(changes, j+1, svn_prop_t); } changes->nelts--; } } } }
/* retrieve ssl server CA failure overrides (if any) from servers config */ static svn_error_t * ssl_server_trust_file_first_credentials(void **credentials, void **iter_baton, void *provider_baton, apr_hash_t *parameters, const char *realmstring, apr_pool_t *pool) { apr_uint32_t *failures = svn_hash_gets(parameters, SVN_AUTH_PARAM_SSL_SERVER_FAILURES); const svn_auth_ssl_server_cert_info_t *cert_info = svn_hash_gets(parameters, SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO); apr_hash_t *creds_hash = NULL; const char *config_dir; svn_error_t *error = SVN_NO_ERROR; *credentials = NULL; *iter_baton = NULL; /* Check if this is a permanently accepted certificate */ config_dir = svn_hash_gets(parameters, SVN_AUTH_PARAM_CONFIG_DIR); error = svn_config_read_auth_data(&creds_hash, SVN_AUTH_CRED_SSL_SERVER_TRUST, realmstring, config_dir, pool); svn_error_clear(error); if (! error && creds_hash) { svn_string_t *trusted_cert, *this_cert, *failstr; apr_uint32_t last_failures = 0; trusted_cert = svn_hash_gets(creds_hash, SVN_CONFIG_AUTHN_ASCII_CERT_KEY); this_cert = svn_string_create(cert_info->ascii_cert, pool); failstr = svn_hash_gets(creds_hash, SVN_CONFIG_AUTHN_FAILURES_KEY); if (failstr) SVN_ERR(svn_cstring_atoui(&last_failures, failstr->data)); /* If the cert is trusted and there are no new failures, we * accept it by clearing all failures. */ if (trusted_cert && svn_string_compare(this_cert, trusted_cert) && (*failures & ~last_failures) == 0) { *failures = 0; } } /* If all failures are cleared now, we return the creds */ if (! *failures) { svn_auth_cred_ssl_server_trust_t *creds = apr_pcalloc(pool, sizeof(*creds)); creds->may_save = FALSE; /* No need to save it again... */ *credentials = creds; } return SVN_NO_ERROR; }
static svn_error_t * check_and_set_revprop(svn_revnum_t *set_rev, svn_ra_session_t *ra_session, const char *propname, const svn_string_t *original_propval, const svn_string_t *propval, apr_pool_t *pool) { if (original_propval) { /* Ensure old value hasn't changed behind our back. */ svn_string_t *current; SVN_ERR(svn_ra_rev_prop(ra_session, *set_rev, propname, ¤t, pool)); if (original_propval->data && (! current)) { return svn_error_createf( SVN_ERR_RA_OUT_OF_DATE, NULL, _("revprop '%s' in r%ld is unexpectedly absent " "in repository (maybe someone else deleted it?)"), propname, *set_rev); } else if (original_propval->data && (! svn_string_compare(original_propval, current))) { return svn_error_createf( SVN_ERR_RA_OUT_OF_DATE, NULL, _("revprop '%s' in r%ld has unexpected value " "in repository (maybe someone else changed it?)"), propname, *set_rev); } else if ((! original_propval->data) && current) { return svn_error_createf( SVN_ERR_RA_OUT_OF_DATE, NULL, _("revprop '%s' in r%ld is unexpectedly present " "in repository (maybe someone else set it?)"), propname, *set_rev); } } SVN_ERR(svn_ra_change_rev_prop2(ra_session, *set_rev, propname, NULL, propval, pool)); return SVN_NO_ERROR; }
svn_error_t *svn_fs_bdb__set_node_origin(svn_fs_t *fs, const char *node_id, const svn_fs_id_t *origin_id, trail_t *trail, apr_pool_t *pool) { base_fs_data_t *bfd = fs->fsap_data; DBT key, value; int db_err; /* Create a key from our NODE_ID. */ svn_fs_base__str_to_dbt(&key, node_id); /* Check to see if we already have a mapping for NODE_ID. If so, and the value is the same one we were about to write. That's cool -- just do nothing. If, however, the value is *different*, that's a red flag! */ svn_fs_base__trail_debug(trail, "node-origins", "get"); db_err = bfd->node_origins->get(bfd->node_origins, trail->db_txn, &key, svn_fs_base__result_dbt(&value), 0); svn_fs_base__track_dbt(&value, pool); if (db_err != DB_NOTFOUND) { const svn_string_t *origin_id_str = svn_fs_base__id_unparse(origin_id, pool); const svn_string_t *old_origin_id_str = svn_string_ncreate(value.data, value.size, pool); if (! svn_string_compare(origin_id_str, old_origin_id_str)) return svn_error_createf (SVN_ERR_FS_CORRUPT, NULL, _("Node origin for '%s' exists in filesystem '%s' with a different " "value (%s) than what we were about to store (%s)"), node_id, fs->path, old_origin_id_str->data, origin_id_str->data); else return SVN_NO_ERROR; } /* Create a value from our ORIGIN_ID, and add this record to the table. */ svn_fs_base__id_to_dbt(&value, origin_id, pool); svn_fs_base__trail_debug(trail, "node-origins", "put"); SVN_ERR(BDB_WRAP(fs, _("storing node-origins record"), bfd->node_origins->put(bfd->node_origins, trail->db_txn, &key, &value, 0))); return SVN_NO_ERROR; }
svn_boolean_t svn_fs__prop_lists_equal(apr_hash_t *a, apr_hash_t *b, apr_pool_t *pool) { apr_hash_index_t *hi; /* Quick checks and special cases. */ if (a == b) return TRUE; if (a == NULL) return apr_hash_count(b) == 0; if (b == NULL) return apr_hash_count(a) == 0; if (apr_hash_count(a) != apr_hash_count(b)) return FALSE; /* Compare prop by prop. */ for (hi = apr_hash_first(pool, a); hi; hi = apr_hash_next(hi)) { const char *key; apr_ssize_t klen; svn_string_t *val_a, *val_b; apr_hash_this(hi, (const void **)&key, &klen, (void **)&val_a); val_b = apr_hash_get(b, key, klen); if (!val_b || !svn_string_compare(val_a, val_b)) return FALSE; } /* No difference found. */ return TRUE; }
/* Implements svn_hash_write2 and svn_hash_write_incremental. */ static svn_error_t * hash_write(apr_hash_t *hash, apr_hash_t *oldhash, svn_stream_t *stream, const char *terminator, apr_pool_t *pool) { apr_pool_t *subpool; apr_size_t len; apr_array_header_t *list; int i; subpool = svn_pool_create(pool); list = svn_sort__hash(hash, svn_sort_compare_items_lexically, pool); for (i = 0; i < list->nelts; i++) { svn_sort__item_t *item = &APR_ARRAY_IDX(list, i, svn_sort__item_t); svn_string_t *valstr = item->value; svn_pool_clear(subpool); /* Don't output entries equal to the ones in oldhash, if present. */ if (oldhash) { svn_string_t *oldstr = apr_hash_get(oldhash, item->key, item->klen); if (oldstr && svn_string_compare(valstr, oldstr)) continue; } /* Write it out. */ SVN_ERR(svn_stream_printf(stream, subpool, "K %" APR_SSIZE_T_FMT "\n%s\n" "V %" APR_SIZE_T_FMT "\n", item->klen, (const char *) item->key, valstr->len)); len = valstr->len; SVN_ERR(svn_stream_write(stream, valstr->data, &len)); SVN_ERR(svn_stream_printf(stream, subpool, "\n")); } if (oldhash) { /* Output a deletion entry for each property in oldhash but not hash. */ list = svn_sort__hash(oldhash, svn_sort_compare_items_lexically, pool); for (i = 0; i < list->nelts; i++) { svn_sort__item_t *item = &APR_ARRAY_IDX(list, i, svn_sort__item_t); svn_pool_clear(subpool); /* If it's not present in the new hash, write out a D entry. */ if (! apr_hash_get(hash, item->key, item->klen)) SVN_ERR(svn_stream_printf(stream, subpool, "D %" APR_SSIZE_T_FMT "\n%s\n", item->klen, (const char *) item->key)); } } if (terminator) SVN_ERR(svn_stream_printf(stream, subpool, "%s\n", terminator)); svn_pool_destroy(subpool); return SVN_NO_ERROR; }
svn_error_t * svn_prop_diffs(apr_array_header_t **propdiffs, const apr_hash_t *target_props, const apr_hash_t *source_props, apr_pool_t *pool) { apr_hash_index_t *hi; apr_array_header_t *ary = apr_array_make(pool, 1, sizeof(svn_prop_t)); /* Note: we will be storing the pointers to the keys (from the hashes) into the propdiffs array. It is acceptable for us to reference the same memory as the base/target_props hash. */ /* Loop over SOURCE_PROPS and examine each key. This will allow us to detect any `deletion' events or `set-modification' events. */ for (hi = apr_hash_first(pool, (apr_hash_t *)source_props); hi; hi = apr_hash_next(hi)) { const void *key; apr_ssize_t klen; void *val; const svn_string_t *propval1, *propval2; /* Get next property */ apr_hash_this(hi, &key, &klen, &val); propval1 = val; /* Does property name exist in TARGET_PROPS? */ propval2 = apr_hash_get((apr_hash_t *)target_props, key, klen); if (propval2 == NULL) { /* Add a delete event to the array */ svn_prop_t *p = apr_array_push(ary); p->name = key; p->value = NULL; } else if (! svn_string_compare(propval1, propval2)) { /* Add a set (modification) event to the array */ svn_prop_t *p = apr_array_push(ary); p->name = key; p->value = svn_string_dup(propval2, pool); } } /* Loop over TARGET_PROPS and examine each key. This allows us to detect `set-creation' events */ for (hi = apr_hash_first(pool, (apr_hash_t *)target_props); hi; hi = apr_hash_next(hi)) { const void *key; apr_ssize_t klen; void *val; const svn_string_t *propval; /* Get next property */ apr_hash_this(hi, &key, &klen, &val); propval = val; /* Does property name exist in SOURCE_PROPS? */ if (NULL == apr_hash_get((apr_hash_t *)source_props, key, klen)) { /* Add a set (creation) event to the array */ svn_prop_t *p = apr_array_push(ary); p->name = key; p->value = svn_string_dup(propval, pool); } } /* Done building our array of user events. */ *propdiffs = ary; return SVN_NO_ERROR; }
/* This implements the `svn_opt_subcommand_t' interface. */ svn_error_t * svn_cl__propedit(apr_getopt_t *os, void *baton, apr_pool_t *pool) { svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; const char *pname, *pname_utf8; apr_array_header_t *args, *targets; int i; /* Validate the input and get the property's name (and a UTF-8 version of that name). */ SVN_ERR(svn_opt_parse_num_args(&args, os, 1, pool)); pname = APR_ARRAY_IDX(args, 0, const char *); SVN_ERR(svn_utf_cstring_to_utf8(&pname_utf8, pname, pool)); if (! svn_prop_name_is_valid(pname_utf8)) return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL, _("'%s' is not a valid Subversion property name"), pname_utf8); if (opt_state->encoding && !svn_prop_needs_translation(pname_utf8)) return svn_error_create (SVN_ERR_UNSUPPORTED_FEATURE, NULL, _("--encoding option applies only to textual" " Subversion-controlled properties")); /* Suck up all the remaining arguments into a targets array */ SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, opt_state->targets, ctx, pool)); if (opt_state->revprop) /* operate on a revprop */ { svn_revnum_t rev; const char *URL; svn_string_t *propval; svn_string_t original_propval; const char *temp_dir; /* Implicit "." is okay for revision properties; it just helps us find the right repository. */ svn_opt_push_implicit_dot_target(targets, pool); SVN_ERR(svn_cl__revprop_prepare(&opt_state->start_revision, targets, &URL, pool)); /* Fetch the current property. */ SVN_ERR(svn_client_revprop_get(pname_utf8, &propval, URL, &(opt_state->start_revision), &rev, ctx, pool)); if (! propval) { propval = svn_string_create("", pool); /* This is how we signify to svn_client_revprop_set2() that we want it to check that the original value hasn't changed, but that that original value was non-existent: */ original_propval.data = NULL; /* and .len is ignored */ } else { original_propval = *propval; } /* Run the editor on a temporary file which contains the original property value... */ SVN_ERR(svn_io_temp_dir(&temp_dir, pool)); SVN_ERR(svn_cl__edit_string_externally (&propval, NULL, opt_state->editor_cmd, temp_dir, propval, "svn-prop", ctx->config, svn_prop_needs_translation(pname_utf8), opt_state->encoding, pool)); /* ...and re-set the property's value accordingly. */ if (propval) { SVN_ERR(svn_client_revprop_set2(pname_utf8, propval, &original_propval, URL, &(opt_state->start_revision), &rev, opt_state->force, ctx, pool)); SVN_ERR (svn_cmdline_printf (pool, _("Set new value for property '%s' on revision %ld\n"), pname_utf8, rev)); } else { SVN_ERR(svn_cmdline_printf (pool, _("No changes to property '%s' on revision %ld\n"), pname_utf8, rev)); } } else if (opt_state->start_revision.kind != svn_opt_revision_unspecified) { return svn_error_createf (SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("Cannot specify revision for editing versioned property '%s'"), pname_utf8); } else /* operate on a normal, versioned property (not a revprop) */ { apr_pool_t *subpool = svn_pool_create(pool); /* The customary implicit dot rule has been prone to user error * here. For example, Jon Trowbridge <*****@*****.**> did * * $ svn propedit HACKING * * and then when he closed his editor, he was surprised to see * * Set new value for property 'HACKING' on '' * * ...meaning that the property named 'HACKING' had been set on * the current working directory, with the value taken from the * editor. So we don't do the implicit dot thing anymore; an * explicit target is always required when editing a versioned * property. */ if (targets->nelts == 0) { return svn_error_create (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, _("Explicit target argument required")); } SVN_ERR(svn_opt__eat_peg_revisions(&targets, targets, pool)); /* For each target, edit the property PNAME. */ for (i = 0; i < targets->nelts; i++) { apr_hash_t *props; const char *target = APR_ARRAY_IDX(targets, i, const char *); svn_string_t *propval, *edited_propval; const char *base_dir = target; const char *target_local; svn_wc_adm_access_t *adm_access; const svn_wc_entry_t *entry; svn_opt_revision_t peg_revision; svn_revnum_t base_rev = SVN_INVALID_REVNUM; svn_pool_clear(subpool); SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton)); /* Propedits can only happen on HEAD or the working copy, so the peg revision can be as unspecified. */ peg_revision.kind = svn_opt_revision_unspecified; /* Fetch the current property. */ SVN_ERR(svn_client_propget3(&props, pname_utf8, target, &peg_revision, &(opt_state->start_revision), &base_rev, svn_depth_empty, NULL, ctx, subpool)); /* Get the property value. */ propval = apr_hash_get(props, target, APR_HASH_KEY_STRING); if (! propval) propval = svn_string_create("", subpool); if (svn_path_is_url(target)) { /* For URLs, put the temporary file in the current directory. */ base_dir = "."; } else { if (opt_state->message || opt_state->filedata || opt_state->revprop_table) { return svn_error_create (SVN_ERR_CL_UNNECESSARY_LOG_MESSAGE, NULL, _("Local, non-commit operations do not take a log message " "or revision properties")); } /* Split the path if it is a file path. */ SVN_ERR(svn_wc_adm_probe_open3(&adm_access, NULL, target, FALSE, 0, ctx->cancel_func, ctx->cancel_baton, subpool)); SVN_ERR(svn_wc_entry(&entry, target, adm_access, FALSE, subpool)); if (! entry) return svn_error_createf (SVN_ERR_ENTRY_NOT_FOUND, NULL, _("'%s' does not appear to be a working copy path"), target); if (entry->kind == svn_node_file) svn_path_split(target, &base_dir, NULL, subpool); } /* Run the editor on a temporary file which contains the original property value... */ SVN_ERR(svn_cl__edit_string_externally(&edited_propval, NULL, opt_state->editor_cmd, base_dir, propval, "svn-prop", ctx->config, svn_prop_needs_translation (pname_utf8), opt_state->encoding, subpool)); target_local = svn_path_is_url(target) ? target : svn_path_local_style(target, subpool); /* ...and re-set the property's value accordingly. */ if (edited_propval && !svn_string_compare(propval, edited_propval)) { svn_commit_info_t *commit_info = NULL; svn_error_t *err = SVN_NO_ERROR; svn_cl__check_boolean_prop_val(pname_utf8, edited_propval->data, subpool); if (ctx->log_msg_func3) SVN_ERR(svn_cl__make_log_msg_baton(&(ctx->log_msg_baton3), opt_state, NULL, ctx->config, subpool)); err = svn_client_propset3(&commit_info, pname_utf8, edited_propval, target, svn_depth_empty, opt_state->force, base_rev, NULL, opt_state->revprop_table, ctx, subpool); if (ctx->log_msg_func3) SVN_ERR(svn_cl__cleanup_log_msg(ctx->log_msg_baton3, err, pool)); else if (err) return err; /* Print a message if we successfully committed or if it was just a wc propset (but not if the user aborted an URL propedit). */ if (commit_info || ! svn_path_is_url(target)) SVN_ERR (svn_cmdline_printf (subpool, _("Set new value for property '%s' on '%s'\n"), pname_utf8, target_local)); if (commit_info && ! opt_state->quiet) SVN_ERR(svn_cl__print_commit_info(commit_info, subpool)); } else { SVN_ERR (svn_cmdline_printf (subpool, _("No changes to property '%s' on '%s'\n"), pname_utf8, target_local)); } } svn_pool_destroy(subpool); } return SVN_NO_ERROR; }