Index: subversion/include/svn_io.h =================================================================== --- subversion/include/svn_io.h (revision 18619) +++ subversion/include/svn_io.h (working copy) @@ -689,6 +689,17 @@ svn_error_t *svn_stream_copy(svn_stream_t *from, svn_stream_t *to, apr_pool_t *pool); +/** Set @a *same to non-zero if @a stream1 and @a stream2 have the same + * contents, else set it to zero. Use @a pool for temporary allocations. + * + * @since New in 1.4. + */ +svn_error_t * +svn_stream_contents_same(svn_boolean_t *same, + svn_stream_t *stream1, + svn_stream_t *stream2, + apr_pool_t *pool); + /** @} */ /** Sets @a *result to a string containing the contents of @a filename, a Index: subversion/include/svn_subst.h =================================================================== --- subversion/include/svn_subst.h (revision 18619) +++ subversion/include/svn_subst.h (working copy) @@ -410,6 +410,34 @@ apr_hash_t *keywords, svn_boolean_t special, apr_pool_t *pool); + +/** + * Set @a *stream_p to a stream that translates the file @a src from + * working copy form to normal form, allocated in @a pool. + * + * The values specified for @a eol_style, @a *eol_str, @a keywords and + * @a special, should be the ones used to translate the file to its + * working copy form. Usually, these are the values specified by the + * user in the files' properties. + * + * Inconsistent line endings in the file will be automatically repaired + * (made consistent) for some eol styles. For all others, an error is + * returned. By setting @a always_repair_eols to @c TRUE, eols will be + * made consistent even for those styles which don't have it by default. + * + * @since New in 1.4. + * + */ +svn_error_t * +svn_subst_detranslated_stream(svn_stream_t **stream_p, + const char *src, + svn_subst_eol_style_t eol_style, + const char *eol_str, + svn_boolean_t always_repair_eols, + apr_hash_t *keywords, + svn_boolean_t special, + apr_pool_t *pool); + /* EOL conversion and character encodings */ Index: subversion/libsvn_subr/stream.c =================================================================== --- subversion/libsvn_subr/stream.c (revision 18619) +++ subversion/libsvn_subr/stream.c (working copy) @@ -228,7 +228,36 @@ } +svn_error_t * +svn_stream_contents_same(svn_boolean_t *same, + svn_stream_t *stream1, + svn_stream_t *stream2, + apr_pool_t *pool) +{ + char *buf1 = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE); + char *buf2 = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE); + apr_size_t bytes_read1 = SVN__STREAM_CHUNK_SIZE; + apr_size_t bytes_read2 = SVN__STREAM_CHUNK_SIZE; + + *same = TRUE; /* assume TRUE, until disproved below */ + while (bytes_read1 == SVN__STREAM_CHUNK_SIZE + && bytes_read2 == SVN__STREAM_CHUNK_SIZE) + { + SVN_ERR(svn_stream_read(stream1, buf1, &bytes_read1)); + SVN_ERR(svn_stream_read(stream2, buf2, &bytes_read2)); + + if ((bytes_read1 != bytes_read2) + || (memcmp(buf1, buf2, bytes_read1))) + { + *same = FALSE; + break; + } + } + return SVN_NO_ERROR; +} + + /*** Generic readable empty stream ***/ Index: subversion/libsvn_subr/subst.c =================================================================== --- subversion/libsvn_subr/subst.c (revision 18619) +++ subversion/libsvn_subr/subst.c (working copy) @@ -1200,7 +1200,80 @@ return SVN_NO_ERROR; } +/* Given a special file at SRC, set TRANSLATED_STREAM_P to a stream + with textual representation of it. Perform all allocations in POOL. */ +static svn_error_t * +detranslated_stream_special(svn_stream_t **translated_stream_p, + const char *src, + apr_pool_t *pool) +{ + apr_finfo_t finfo; + apr_file_t *s; + svn_string_t *buf; + svn_stringbuf_t *strbuf; + + /* First determine what type of special file we are + detranslating. */ + SVN_ERR(svn_io_stat(&finfo, src, APR_FINFO_MIN | APR_FINFO_LINK, pool)); + + switch (finfo.filetype) { + case APR_REG: + /* Nothing special to do here, just create stream from the original + file's contents. */ + SVN_ERR(svn_io_file_open(&s, src, APR_READ | APR_BUFFERED, + APR_OS_DEFAULT, pool)); + *translated_stream_p = svn_stream_from_aprfile2(s, FALSE, pool); + break; + case APR_LNK: + /* Determine the destination of the link. */ + SVN_ERR(svn_io_read_link(&buf, src, pool)); + strbuf = svn_stringbuf_createf(pool, "link %s", buf->data); + *translated_stream_p = svn_stream_from_stringbuf(strbuf, pool); + + break; + default: + abort(); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_subst_detranslated_stream(svn_stream_t **stream_p, + const char *src, + svn_subst_eol_style_t eol_style, + const char *eol_str, + svn_boolean_t always_repair_eols, + apr_hash_t *keywords, + svn_boolean_t special, + apr_pool_t *pool) +{ + apr_file_t *file_h; + svn_stream_t *src_stream; + + if (special) + return detranslated_stream_special(stream_p, src, pool); + + if (eol_style == svn_subst_eol_style_native) + eol_str = SVN_SUBST__DEFAULT_EOL_STR; + else if (! (eol_style == svn_subst_eol_style_fixed + || eol_style == svn_subst_eol_style_none)) + return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL, NULL, NULL); + + SVN_ERR(svn_io_file_open(&file_h, src, APR_READ, + APR_OS_DEFAULT, pool)); + + src_stream = svn_stream_from_aprfile2(file_h, FALSE, pool); + + *stream_p = svn_subst_stream_translated( + src_stream, eol_str, + eol_style == svn_subst_eol_style_fixed || always_repair_eols, + keywords, FALSE, pool); + + return SVN_NO_ERROR; +} + svn_stream_t * svn_subst_stream_translated(svn_stream_t *stream, const char *eol_str, @@ -1397,44 +1470,23 @@ const char *dst, apr_pool_t *pool) { + svn_stream_t *translated_stream, *dst_stream; const char *dst_tmp; - svn_string_t *buf; - apr_file_t *s, *d; - svn_stream_t *src_stream, *dst_stream; - apr_finfo_t finfo; + apr_file_t *d; + + SVN_ERR(detranslated_stream_special(&translated_stream, src, pool)); - /* First determine what type of special file we are - detranslating. */ - SVN_ERR(svn_io_stat(&finfo, src, APR_FINFO_MIN | APR_FINFO_LINK, pool)); - /* Open a temporary destination that we will eventually atomically rename into place. */ SVN_ERR(svn_io_open_unique_file2(&d, &dst_tmp, dst, ".tmp", svn_io_file_del_none, pool)); dst_stream = svn_stream_from_aprfile(d, pool); - - switch (finfo.filetype) { - case APR_REG: - /* Nothing special to do here, just copy the original file's - contents. */ - SVN_ERR(svn_io_file_open(&s, src, APR_READ | APR_BUFFERED, - APR_OS_DEFAULT, pool)); - src_stream = svn_stream_from_aprfile(s, pool); - SVN_ERR(svn_stream_copy(src_stream, dst_stream, pool)); - break; - case APR_LNK: - /* Determine the destination of the link. */ - SVN_ERR(svn_io_read_link(&buf, src, pool)); + SVN_ERR(svn_stream_copy(translated_stream, dst_stream, pool)); - SVN_ERR(svn_stream_printf(dst_stream, pool, "link %s", - buf->data)); - break; - default: - abort(); - } - + SVN_ERR(svn_stream_close(dst_stream)); + SVN_ERR(svn_stream_close(translated_stream)); SVN_ERR(svn_io_file_close(d, pool)); /* Do the atomic rename from our temporary location. */ Index: subversion/libsvn_wc/questions.c =================================================================== --- subversion/libsvn_wc/questions.c (revision 18619) +++ subversion/libsvn_wc/questions.c (working copy) @@ -224,24 +224,64 @@ apr_pool_t *pool) { svn_boolean_t same; - const char *tmp_vfile; + svn_subst_eol_style_t eol_style; + const char *eol_str; + apr_hash_t *keywords; + svn_boolean_t special; - if (compare_textbases) - SVN_ERR(svn_wc_translated_file2 - (&tmp_vfile, versioned_file, - versioned_file, adm_access, - SVN_WC_TRANSLATE_TO_NF, - pool)); - else - SVN_ERR(svn_wc_translated_file2 - (&tmp_vfile, base_file, - versioned_file, adm_access, - SVN_WC_TRANSLATE_FROM_NF, - pool)); + SVN_ERR(svn_wc__get_eol_style(&eol_style, &eol_str, versioned_file, + adm_access, pool)); + SVN_ERR(svn_wc__get_keywords(&keywords, versioned_file, + adm_access, NULL, pool)); + SVN_ERR(svn_wc__get_special(&special, versioned_file, adm_access, pool)); - SVN_ERR(svn_io_files_contents_same_p(&same, tmp_vfile, - compare_textbases - ? base_file : versioned_file, pool)); + + if (! svn_subst_translation_required(eol_style, eol_str, keywords, + special, TRUE)) + { + /* Translation would be a no-op, so compare the original file. */ + SVN_ERR(svn_io_files_contents_same_p(&same, base_file, versioned_file, + pool)); + + } + else /* some translation is necessary */ + { + /* "v_" means versioned_file, "b_" means base_file. */ + apr_file_t *v_file_h, *b_file_h; + svn_stream_t *v_stream, *b_stream; + + SVN_ERR(svn_io_file_open(&b_file_h, base_file, APR_READ, + APR_OS_DEFAULT, pool)); + + b_stream = svn_stream_from_aprfile2(b_file_h, FALSE, pool); + + if (compare_textbases) + { + /* Create stream for detranslate versioned file to normal form. */ + SVN_ERR(svn_subst_detranslated_stream(&v_stream, + versioned_file, + eol_style, + eol_str, FALSE, + keywords, special, + pool)); + } + else + { + /* Translate text-base to working copy form. */ + SVN_ERR(svn_io_file_open(&v_file_h, versioned_file, APR_READ, + APR_OS_DEFAULT, pool)); + v_stream = svn_stream_from_aprfile2(v_file_h, FALSE, pool); + + b_stream = svn_subst_stream_translated(b_stream, eol_str, FALSE, + keywords, TRUE, pool); + } + + SVN_ERR(svn_stream_contents_same(&same, b_stream, v_stream, pool)); + + SVN_ERR(svn_stream_close(v_stream)); + SVN_ERR(svn_stream_close(b_stream)); + } + *modified_p = (! same); return SVN_NO_ERROR;