Hi! This is not a finished patch.
I've added mark and seek handlers to svn_subst_stream_translated(). Those two handlers are just wrappers that calls the underlying streams handlers. Now that works in my test but what happens if we set a mark in front of what we've read so far from the file? We might end up with an off-by-one caused by one dropped '\r' causing the hunk to be applied at the wrong line. I guess we could forbid a mark from beeing set in front of the last character read so far but that would be a restraint that would look strange on the outside. What to do? If we plan to add keyword expansion in the future, things would get even more complex. Right now I open a translated stream for all cases when there's a svn:eol-style property. In reality it's only needed when the eol of the file differs from the property. I will fix that if there's a solution to 'the seekable translated stream' problem. [[[ Fix #3356 - svn patch ignores svn:eol-style. When the eol in the file to be patched differed from the svn:eol-style prop the code now translates to the prop eol. To do that we need to use svn_subst_stream_translated on a stream that needs to be seekable. * subversion/libsvn_subr/subst.c (translated_stream_mark): New. (translated_stream_seek): New. (svn_subst_straem_translated): Set seek and mark functions. * subversion/libsvn_client/patch.c (init_patch_target): Check if there is a svn:eol-style prop set on the file to be patched. If it is we translate the eols of both the file and the patch to that value. * subversion/tests/cmdline/patch_tests.py (patch_handle_eol): New. (test_list): Add the new test. Patch by: Daniel Näslund <daniel{_AT_}longitudo.com> ]]]
Index: subversion/libsvn_subr/subst.c =================================================================== --- subversion/libsvn_subr/subst.c (revision 900853) +++ subversion/libsvn_subr/subst.c (arbetskopia) @@ -1136,7 +1136,25 @@ return svn_error_return(err); } +static svn_error_t * +translated_stream_mark(void *baton, svn_stream_mark_t **mark, + apr_pool_t *pool) +{ + struct translated_stream_baton *b = baton; + SVN_ERR(svn_stream_mark(b->stream, mark, pool)); + return SVN_NO_ERROR; +} +static svn_error_t * +translated_stream_seek(void *baton, svn_stream_mark_t *mark) +{ + struct translated_stream_baton *b = baton; + SVN_ERR(svn_stream_seek(b->stream, mark)); + return SVN_NO_ERROR; +} + + + svn_error_t * svn_subst_read_specialfile(svn_stream_t **stream, const char *path, @@ -1235,6 +1253,8 @@ svn_stream_set_write(s, translated_stream_write); svn_stream_set_close(s, translated_stream_close); svn_stream_set_reset(s, translated_stream_reset); + svn_stream_set_mark(s, translated_stream_mark); + svn_stream_set_seek(s, translated_stream_seek); return s; } Index: subversion/tests/cmdline/patch_tests.py =================================================================== --- subversion/tests/cmdline/patch_tests.py (revision 900853) +++ subversion/tests/cmdline/patch_tests.py (arbetskopia) @@ -833,7 +833,73 @@ 1, # dry-run '-p1') +def patch_handle_eol(sbox): + "patching should not change eol of target" + sbox.build() + wc_dir = sbox.wc_dir + + patch_file_path = tempfile.mkstemp(dir=os.path.abspath(svntest.main.temp_dir))[1] + + if os.name == 'nt': + crlf = '\n' + else: + crlf = '\r\n' + + mu_path = os.path.join(wc_dir, 'A', 'mu') + + orig = "This is the file 'mu'.\n" + + rev = 0 + for eol, eolchar in zip(['CRLF', 'CR', 'native', 'LF'], + [crlf, '\015', '\n', '\012']): + + expected = ''.join(["Inserting one line at the top.", eolchar, + "This is the file 'mu'.", eolchar, + "Inserting one line at the bottom.", eolchar]) + + svntest.main.file_write(mu_path, orig, 'wb') + svntest.main.run_svn(None, 'commit', '-m', "''", wc_dir) + svntest.main.run_svn(None, 'update', wc_dir) + svntest.main.run_svn(None, 'propset', 'svn:eol-style', eol, mu_path) + + # Create a patch + patch = [ + "Index: A/mu\n", + "===================================================================\n", + "--- A/mu\t(revision 1)\n", + "+++ A/mu\t(working copy)\n", + "@@ -1 +1,3 @@\n", + "+Inserting one line at the top.\n", + " This is the file 'mu'.\n", + "+Inserting one line at the bottom.\n", + ] + + svntest.main.file_write(patch_file_path, ''.join(patch)) + + expected_output = [ + 'U %s\n' % os.path.join(wc_dir, 'A', 'mu'), + ] + expected_disk = svntest.main.greek_state.copy() + expected_disk.tweak('A/mu', props={u'svn:eol-style' : unicode(eol)}) + expected_disk.tweak('A/mu', contents=expected) + rev += 1 + expected_status = svntest.actions.get_virginal_state(wc_dir, rev) + #expected_status.tweak('A/mu', status='MM', wc_rev=rev) + expected_status.tweak('A/mu', status='MM', wc_rev=rev) + expected_skip = wc.State('', { }) + + # apply the patch + svntest.actions.run_and_verify_patch(wc_dir, + os.path.abspath(patch_file_path), + expected_output, + expected_disk, + expected_status, + expected_skip, + None, # expected err + 1, # check-props + 1) # dry-run + ######################################################################## #Run the tests @@ -844,6 +910,7 @@ patch_unidiff_offset, patch_chopped_leading_spaces, patch_unidiff_strip1, + patch_handle_eol, ] if __name__ == '__main__': Index: subversion/libsvn_client/patch.c =================================================================== --- subversion/libsvn_client/patch.c (revision 900853) +++ subversion/libsvn_client/patch.c (arbetskopia) @@ -34,6 +34,7 @@ #include "svn_path.h" #include "svn_pools.h" #include "svn_subst.h" +#include "svn_props.h" #include "svn_wc.h" #include "svn_private_config.h" @@ -368,14 +369,45 @@ if (new_target->kind == svn_node_file && ! new_target->skipped) { + apr_hash_t *props; + svn_string_t *eol_style; + /* Try to open the target file */ SVN_ERR(svn_io_file_open(&new_target->file, new_target->abs_path, APR_READ | APR_BINARY | APR_BUFFERED, APR_OS_DEFAULT, result_pool)); - SVN_ERR(svn_eol__detect_file_eol(&new_target->eol_str, new_target->file, - scratch_pool)); - new_target->stream = svn_stream_from_aprfile2(new_target->file, FALSE, - result_pool); + + /* Try to get the eol-style property. If that fails use the eol of the + * file. */ + SVN_ERR(svn_wc_prop_list2(&props, ctx->wc_ctx, new_target->abs_path, + result_pool, scratch_pool)); + eol_style = apr_hash_get(props, SVN_PROP_EOL_STYLE, + APR_HASH_KEY_STRING); + if (eol_style) + svn_subst_eol_style_from_value(NULL, &new_target->eol_str, + eol_style->data); + if (new_target->eol_str == NULL) + { + SVN_ERR(svn_eol__detect_file_eol(&new_target->eol_str, + new_target->file, scratch_pool)); + + new_target->stream = svn_stream_from_aprfile2(new_target->file, + FALSE, + result_pool); + } + else + { + svn_stream_t *stream; + stream = svn_stream_from_aprfile2(new_target->file, + FALSE, + result_pool); + + new_target->stream = svn_subst_stream_translated(stream, + new_target->eol_str, + TRUE, + NULL, FALSE, + result_pool); + } } if (new_target->eol_str == NULL) @@ -403,10 +435,12 @@ &new_target->patched_path, NULL, svn_io_file_del_on_pool_cleanup, result_pool, scratch_pool)); - new_target->patched = svn_subst_stream_translated(new_target->patched_raw, - "\n", TRUE, - keywords, FALSE, - result_pool); + new_target->patched = + svn_subst_stream_translated(new_target->patched_raw, + new_target->eol_str, + TRUE, + keywords, FALSE, + result_pool); SVN_ERR(check_local_mods(&new_target->local_mods, ctx->wc_ctx, new_target->abs_path, scratch_pool));