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));