/* 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; }
/* Helper function that gets the eol style and optionally overrides the EOL marker for files marked as native with the EOL marker matching the string specified in requested_value which is of the same format as the svn:eol-style property values. */ static svn_error_t * get_eol_style(svn_subst_eol_style_t *style, const char **eol, const char *value, const char *requested_value) { svn_subst_eol_style_from_value(style, eol, value); if (requested_value && *style == svn_subst_eol_style_native) { svn_subst_eol_style_t requested_style; const char *requested_eol; svn_subst_eol_style_from_value(&requested_style, &requested_eol, requested_value); if (requested_style == svn_subst_eol_style_fixed) *eol = requested_eol; else return svn_error_createf(SVN_ERR_IO_UNKNOWN_EOL, NULL, _("'%s' is not a valid EOL value"), requested_value); } return SVN_NO_ERROR; }
svn_error_t * svn_wc__get_translate_info(svn_subst_eol_style_t *style, const char **eol, apr_hash_t **keywords, svn_boolean_t *special, svn_wc__db_t *db, const char *local_abspath, apr_hash_t *props, svn_boolean_t for_normalization, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { const char *propval; SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); if (props == NULL) SVN_ERR(svn_wc__get_actual_props(&props, db, local_abspath, scratch_pool, scratch_pool)); if (eol) { propval = svn_prop_get_value(props, SVN_PROP_EOL_STYLE); svn_subst_eol_style_from_value(style, eol, propval); } if (keywords) { propval = svn_prop_get_value(props, SVN_PROP_KEYWORDS); if (!propval || *propval == '\0') *keywords = NULL; else SVN_ERR(svn_wc__expand_keywords(keywords, db, local_abspath, NULL, propval, for_normalization, result_pool, scratch_pool)); } if (special) { propval = svn_prop_get_value(props, SVN_PROP_SPECIAL); *special = (propval != NULL); } return SVN_NO_ERROR; }
svn_error_t * svn_client__get_normalized_stream(svn_stream_t **normal_stream, svn_wc_context_t *wc_ctx, const char *local_abspath, const svn_opt_revision_t *revision, svn_boolean_t expand_keywords, svn_boolean_t normalize_eols, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { apr_hash_t *kw = NULL; svn_subst_eol_style_t style; apr_hash_t *props; svn_string_t *eol_style, *keywords, *special; const char *eol = NULL; svn_boolean_t local_mod = FALSE; apr_time_t tm; svn_stream_t *input; svn_node_kind_t kind; SVN_ERR_ASSERT(SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(revision->kind)); SVN_ERR(svn_wc_read_kind(&kind, wc_ctx, local_abspath, FALSE, scratch_pool)); if (kind == svn_node_unknown || kind == svn_node_none) return svn_error_createf(SVN_ERR_UNVERSIONED_RESOURCE, NULL, _("'%s' is not under version control"), svn_dirent_local_style(local_abspath, scratch_pool)); if (kind != svn_node_file) return svn_error_createf(SVN_ERR_CLIENT_IS_DIRECTORY, NULL, _("'%s' refers to a directory"), svn_dirent_local_style(local_abspath, scratch_pool)); if (revision->kind != svn_opt_revision_working) { SVN_ERR(svn_wc_get_pristine_contents2(&input, wc_ctx, local_abspath, result_pool, scratch_pool)); if (input == NULL) return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, _("'%s' has no base revision until it is committed"), svn_dirent_local_style(local_abspath, scratch_pool)); SVN_ERR(svn_wc_get_pristine_props(&props, wc_ctx, local_abspath, scratch_pool, scratch_pool)); } else { svn_wc_status3_t *status; SVN_ERR(svn_stream_open_readonly(&input, local_abspath, scratch_pool, result_pool)); SVN_ERR(svn_wc_prop_list2(&props, wc_ctx, local_abspath, scratch_pool, scratch_pool)); SVN_ERR(svn_wc_status3(&status, wc_ctx, local_abspath, scratch_pool, scratch_pool)); if (status->text_status != svn_wc_status_normal) local_mod = TRUE; } eol_style = apr_hash_get(props, SVN_PROP_EOL_STYLE, APR_HASH_KEY_STRING); keywords = apr_hash_get(props, SVN_PROP_KEYWORDS, APR_HASH_KEY_STRING); special = apr_hash_get(props, SVN_PROP_SPECIAL, APR_HASH_KEY_STRING); if (eol_style) svn_subst_eol_style_from_value(&style, &eol, eol_style->data); if (local_mod && (! special)) { /* Use the modified time from the working copy if the file */ SVN_ERR(svn_io_file_affected_time(&tm, local_abspath, scratch_pool)); } else { SVN_ERR(svn_wc__node_get_changed_info(NULL, &tm, NULL, wc_ctx, local_abspath, scratch_pool, scratch_pool)); } if (keywords) { svn_revnum_t changed_rev; const char *rev_str; const char *author; const char *url; SVN_ERR(svn_wc__node_get_changed_info(&changed_rev, NULL, &author, wc_ctx, local_abspath, scratch_pool, scratch_pool)); SVN_ERR(svn_wc__node_get_url(&url, wc_ctx, local_abspath, scratch_pool, scratch_pool)); if (local_mod) { /* For locally modified files, we'll append an 'M' to the revision number, and set the author to "(local)" since we can't always determine the current user's username */ rev_str = apr_psprintf(scratch_pool, "%ldM", changed_rev); author = _("(local)"); } else { rev_str = apr_psprintf(scratch_pool, "%ld", changed_rev); } SVN_ERR(svn_subst_build_keywords2(&kw, keywords->data, rev_str, url, tm, author, scratch_pool)); } /* Wrap the output stream if translation is needed. */ if (eol != NULL || kw != NULL) input = svn_subst_stream_translated( input, (eol_style && normalize_eols) ? SVN_SUBST_NATIVE_EOL_STR : eol, FALSE, kw, expand_keywords, result_pool); *normal_stream = input; return SVN_NO_ERROR; }
svn_error_t * svn_client_cat2(svn_stream_t *out, const char *path_or_url, const svn_opt_revision_t *peg_revision, const svn_opt_revision_t *revision, svn_client_ctx_t *ctx, apr_pool_t *pool) { svn_ra_session_t *ra_session; svn_revnum_t rev; svn_string_t *eol_style; svn_string_t *keywords; apr_hash_t *props; const char *url; svn_stream_t *output = out; svn_error_t *err; /* ### Inconsistent default revision logic in this command. */ if (peg_revision->kind == svn_opt_revision_unspecified) { peg_revision = svn_cl__rev_default_to_head_or_working(peg_revision, path_or_url); revision = svn_cl__rev_default_to_head_or_base(revision, path_or_url); } else { peg_revision = svn_cl__rev_default_to_head_or_working(peg_revision, path_or_url); revision = svn_cl__rev_default_to_peg(revision, peg_revision); } if (! svn_path_is_url(path_or_url) && SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(peg_revision->kind) && SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(revision->kind)) { const char *local_abspath; svn_stream_t *normal_stream; SVN_ERR(svn_dirent_get_absolute(&local_abspath, path_or_url, pool)); SVN_ERR(svn_client__get_normalized_stream(&normal_stream, ctx->wc_ctx, local_abspath, revision, TRUE, FALSE, ctx->cancel_func, ctx->cancel_baton, pool, pool)); /* We don't promise to close output, so disown it to ensure we don't. */ output = svn_stream_disown(output, pool); return svn_error_trace(svn_stream_copy3(normal_stream, output, ctx->cancel_func, ctx->cancel_baton, pool)); } /* Get an RA plugin for this filesystem object. */ SVN_ERR(svn_client__ra_session_from_path(&ra_session, &rev, &url, path_or_url, NULL, peg_revision, revision, ctx, pool)); /* Grab some properties we need to know in order to figure out if anything special needs to be done with this file. */ err = svn_ra_get_file(ra_session, "", rev, NULL, NULL, &props, pool); if (err) { if (err->apr_err == SVN_ERR_FS_NOT_FILE) { return svn_error_createf(SVN_ERR_CLIENT_IS_DIRECTORY, err, _("URL '%s' refers to a directory"), url); } else { return svn_error_trace(err); } } eol_style = apr_hash_get(props, SVN_PROP_EOL_STYLE, APR_HASH_KEY_STRING); keywords = apr_hash_get(props, SVN_PROP_KEYWORDS, APR_HASH_KEY_STRING); if (eol_style || keywords) { /* It's a file with no special eol style or keywords. */ svn_subst_eol_style_t eol; const char *eol_str; apr_hash_t *kw; if (eol_style) svn_subst_eol_style_from_value(&eol, &eol_str, eol_style->data); else { eol = svn_subst_eol_style_none; eol_str = NULL; } if (keywords) { svn_string_t *cmt_rev, *cmt_date, *cmt_author; apr_time_t when = 0; cmt_rev = apr_hash_get(props, SVN_PROP_ENTRY_COMMITTED_REV, APR_HASH_KEY_STRING); cmt_date = apr_hash_get(props, SVN_PROP_ENTRY_COMMITTED_DATE, APR_HASH_KEY_STRING); cmt_author = apr_hash_get(props, SVN_PROP_ENTRY_LAST_AUTHOR, APR_HASH_KEY_STRING); if (cmt_date) SVN_ERR(svn_time_from_cstring(&when, cmt_date->data, pool)); SVN_ERR(svn_subst_build_keywords2 (&kw, keywords->data, cmt_rev->data, url, when, cmt_author ? cmt_author->data : NULL, pool)); } else kw = NULL; /* Interject a translating stream */ output = svn_subst_stream_translated(svn_stream_disown(out, pool), eol_str, FALSE, kw, TRUE, pool); } SVN_ERR(svn_ra_get_file(ra_session, "", rev, output, NULL, NULL, pool)); if (out != output) /* Close the interjected stream */ SVN_ERR(svn_stream_close(output)); return SVN_NO_ERROR; }
/* Produce a diff between two arbitrary files at LOCAL_ABSPATH1 and * LOCAL_ABSPATH2, using the diff callbacks from CALLBACKS. * Use PATH as the name passed to diff callbacks. * FILE1_IS_EMPTY and FILE2_IS_EMPTY are used as hints which diff callback * function to use to compare the files (added/deleted/changed). * * If ORIGINAL_PROPS_OVERRIDE is not NULL, use it as original properties * instead of reading properties from LOCAL_ABSPATH1. This is required when * a file replaces a directory, where LOCAL_ABSPATH1 is an empty file that * file content must be diffed against, but properties to diff against come * from the replaced directory. */ static svn_error_t * do_arbitrary_files_diff(const char *local_abspath1, const char *local_abspath2, const char *path, svn_boolean_t file1_is_empty, svn_boolean_t file2_is_empty, apr_hash_t *original_props_override, const svn_wc_diff_callbacks4_t *callbacks, void *diff_baton, svn_client_ctx_t *ctx, apr_pool_t *scratch_pool) { apr_hash_t *original_props; apr_hash_t *modified_props; apr_array_header_t *prop_changes; svn_string_t *original_mime_type = NULL; svn_string_t *modified_mime_type = NULL; if (ctx->cancel_func) SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); /* Try to get properties from either file. It's OK if the files do not * have properties, or if they are unversioned. */ if (original_props_override) original_props = original_props_override; else SVN_ERR(get_props(&original_props, local_abspath1, ctx->wc_ctx, scratch_pool, scratch_pool)); SVN_ERR(get_props(&modified_props, local_abspath2, ctx->wc_ctx, scratch_pool, scratch_pool)); SVN_ERR(svn_prop_diffs(&prop_changes, modified_props, original_props, scratch_pool)); /* Try to determine the mime-type of each file. */ original_mime_type = svn_hash_gets(original_props, SVN_PROP_MIME_TYPE); if (!file1_is_empty && !original_mime_type) { const char *mime_type; SVN_ERR(svn_io_detect_mimetype2(&mime_type, local_abspath1, ctx->mimetypes_map, scratch_pool)); if (mime_type) original_mime_type = svn_string_create(mime_type, scratch_pool); } modified_mime_type = svn_hash_gets(modified_props, SVN_PROP_MIME_TYPE); if (!file2_is_empty && !modified_mime_type) { const char *mime_type; SVN_ERR(svn_io_detect_mimetype2(&mime_type, local_abspath1, ctx->mimetypes_map, scratch_pool)); if (mime_type) modified_mime_type = svn_string_create(mime_type, scratch_pool); } /* Produce the diff. */ if (file1_is_empty && !file2_is_empty) SVN_ERR(callbacks->file_added(NULL, NULL, NULL, path, local_abspath1, local_abspath2, /* ### TODO get real revision info * for versioned files? */ SVN_INVALID_REVNUM, SVN_INVALID_REVNUM, original_mime_type ? original_mime_type->data : NULL, modified_mime_type ? modified_mime_type->data : NULL, /* ### TODO get copyfrom? */ NULL, SVN_INVALID_REVNUM, prop_changes, original_props, diff_baton, scratch_pool)); else if (!file1_is_empty && file2_is_empty) SVN_ERR(callbacks->file_deleted(NULL, NULL, path, local_abspath1, local_abspath2, original_mime_type ? original_mime_type->data : NULL, modified_mime_type ? modified_mime_type->data : NULL, original_props, diff_baton, scratch_pool)); else { svn_stream_t *file1; svn_stream_t *file2; svn_boolean_t same; svn_string_t *val; /* We have two files, which may or may not be the same. ### Our caller assumes that we should ignore symlinks here and handle them as normal paths. Perhaps that should change? */ SVN_ERR(svn_stream_open_readonly(&file1, local_abspath1, scratch_pool, scratch_pool)); SVN_ERR(svn_stream_open_readonly(&file2, local_abspath2, scratch_pool, scratch_pool)); /* Wrap with normalization, etc. if necessary */ if (original_props) { val = svn_hash_gets(original_props, SVN_PROP_EOL_STYLE); if (val) { svn_subst_eol_style_t style; const char *eol; svn_subst_eol_style_from_value(&style, &eol, val->data); /* ### Ignoring keywords */ if (eol) file1 = svn_subst_stream_translated(file1, eol, TRUE, NULL, FALSE, scratch_pool); } } if (modified_props) { val = svn_hash_gets(modified_props, SVN_PROP_EOL_STYLE); if (val) { svn_subst_eol_style_t style; const char *eol; svn_subst_eol_style_from_value(&style, &eol, val->data); /* ### Ignoring keywords */ if (eol) file2 = svn_subst_stream_translated(file2, eol, TRUE, NULL, FALSE, scratch_pool); } } SVN_ERR(svn_stream_contents_same2(&same, file1, file2, scratch_pool)); if (! same || prop_changes->nelts > 0) { /* ### We should probably pass the normalized data we created using the subst streams as that is what diff users expect */ SVN_ERR(callbacks->file_changed(NULL, NULL, NULL, path, same ? NULL : local_abspath1, same ? NULL : local_abspath2, /* ### TODO get real revision info * for versioned files? */ SVN_INVALID_REVNUM /* rev1 */, SVN_INVALID_REVNUM /* rev2 */, original_mime_type ? original_mime_type->data : NULL, modified_mime_type ? modified_mime_type->data : NULL, prop_changes, original_props, diff_baton, scratch_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; }