# ./pullrev.sh r1564900
http://svn.apache.org/viewvc?view=revision&revision=r1564900
https://bugzilla.redhat.com/show_bug.cgi?id=1378178
--- subversion-1.7.14/subversion/tests/cmdline/diff_tests.py
+++ subversion-1.7.14/subversion/tests/cmdline/diff_tests.py
@@ -45,16 +45,39 @@
######################################################################
# Generate expected output
-def make_diff_header(path, old_tag, new_tag):
+def is_absolute_url(target):
+ return (target.startswith('file://')
+ or target.startswith('http://')
+ or target.startswith('https://')
+ or target.startswith('svn://')
+ or target.startswith('svn+ssh://'))
+
+def make_diff_header(path, old_tag, new_tag, src_label=None, dst_label=None):
"""Generate the expected diff header for file PATH, with its old and new
- versions described in parentheses by OLD_TAG and NEW_TAG. Return the header
- as an array of newline-terminated strings."""
+ versions described in parentheses by OLD_TAG and NEW_TAG. SRC_LABEL and
+ DST_LABEL are paths or urls that are added to the diff labels if we're
+ diffing against the repository or diffing two arbitrary paths.
+ Return the header as an array of newline-terminated strings."""
+ if src_label:
+ src_label = src_label.replace('\\', '/')
+ if not is_absolute_url(src_label):
+ src_label = '.../' + src_label
+ src_label = '\t(' + src_label + ')'
+ else:
+ src_label = ''
+ if dst_label:
+ dst_label = dst_label.replace('\\', '/')
+ if not is_absolute_url(dst_label):
+ dst_label = '.../' + dst_label
+ dst_label = '\t(' + dst_label + ')'
+ else:
+ dst_label = ''
path_as_shown = path.replace('\\', '/')
return [
"Index: " + path_as_shown + "\n",
"===================================================================\n",
- "--- " + path_as_shown + "\t(" + old_tag + ")\n",
- "+++ " + path_as_shown + "\t(" + new_tag + ")\n",
+ "--- " + path_as_shown + src_label + "\t(" + old_tag + ")\n",
+ "+++ " + path_as_shown + dst_label + "\t(" + new_tag + ")\n",
]
def make_no_diff_deleted_header(path, old_tag, new_tag):
@@ -3867,6 +3890,122 @@
'diff', '-c2',
sbox.repo_url + '/A/D/H')
+@Issue(4460)
+def diff_repo_wc_file_props(sbox):
+ "diff repo to wc file target with props"
+ sbox.build()
+ iota = sbox.ospath('iota')
+
+ # add a mime-type and a line to iota to test the binary check
+ sbox.simple_propset('svn:mime-type', 'text/plain', 'iota')
+ sbox.simple_append('iota','second line\n')
+
+ # test that we get the line and the property add
+ expected_output = make_diff_header(iota, 'revision 1', 'working copy') + \
+ [ '@@ -1 +1,2 @@\n',
+ " This is the file 'iota'.\n",
+ "+second line\n", ] + \
+ make_diff_prop_header(iota) + \
+ make_diff_prop_added('svn:mime-type', 'text/plain')
+ svntest.actions.run_and_verify_svn(None, expected_output, [],
+ 'diff', '-r1', iota)
+
+ # reverse the diff, should get a property delete and line delete
+ # skip actually testing the output since apparently 1.7 is busted
+ # this isn't related to issue #4460, older versions of 1.7 had the issue
+ # as well
+ #expected_output = make_diff_header(iota, 'working copy', 'revision 1') + \
+ # [ '@@ -1,2 +1 @@\n',
+ # " This is the file 'iota'.\n",
+ # "-second line\n", ] + \
+ # make_diff_prop_header(iota) + \
+ # make_diff_prop_deleted('svn:mime-type', 'text/plain')
+ expected_output = None
+ svntest.actions.run_and_verify_svn(None, expected_output, [],
+ 'diff', '--old', iota,
+ '--new', iota + '@1')
+
+ # copy iota to test with --show-copies as adds
+ sbox.simple_copy('iota', 'iota_copy')
+ iota_copy = sbox.ospath('iota_copy')
+
+ # test that we get all lines as added and the property added
+ # TODO: We only test that this test doesn't error out because of Issue #4464
+ # if and when that issue is fixed this test should check output
+ svntest.actions.run_and_verify_svn(None, None, [], 'diff',
+ '--show-copies-as-adds', '-r1', iota_copy)
+
+ # reverse the diff, should get all lines as a delete and no property
+ # TODO: We only test that this test doesn't error out because of Issue #4464
+ # if and when that issue is fixed this test should check output
+ svntest.actions.run_and_verify_svn(None, None, [], 'diff',
+ '--show-copies-as-adds',
+ '--old', iota_copy,
+ '--new', iota + '@1')
+
+ # revert and commit with the eol-style of LF and then update so
+ # that we can see a change on either windows or *nix.
+ sbox.simple_revert('iota', 'iota_copy')
+ sbox.simple_propset('svn:eol-style', 'LF', 'iota')
+ sbox.simple_commit() #r2
+ sbox.simple_update()
+
+ # now that we have a LF file on disk switch to CRLF
+ sbox.simple_propset('svn:eol-style', 'CRLF', 'iota')
+
+ # test that not only the property but also the file changes
+ # i.e. that the line endings substitution works
+ if svntest.main.is_os_windows():
+ # test suite normalizes crlf output into just lf on Windows.
+ # so we have to assume it worked because there is an add and
+ # remove line with the same content. Fortunately, it does't
+ # do this on *nix so we can be pretty sure that it works right.
+ # TODO: Provide a way to handle this better
+ crlf = '\n'
+ else:
+ crlf = '\r\n'
+ expected_output = make_diff_header(iota, 'revision 1', 'working copy') + \
+ [ '@@ -1 +1 @@\n',
+ "-This is the file 'iota'.\n",
+ "+This is the file 'iota'." + crlf ] + \
+ make_diff_prop_header(iota) + \
+ make_diff_prop_added('svn:eol-style', 'CRLF')
+
+ svntest.actions.run_and_verify_svn(None, expected_output, [],
+ 'diff', '-r1', iota)
+
+
+@Issue(4460)
+def diff_repo_repo_added_file_mime_type(sbox):
+ "diff repo to repo added file with mime-type"
+ sbox.build()
+ wc_dir = sbox.wc_dir
+ newfile = sbox.ospath('newfile')
+
+ # add a file with a mime-type
+ sbox.simple_append('newfile', "This is the file 'newfile'.\n")
+ sbox.simple_add('newfile')
+ sbox.simple_propset('svn:mime-type', 'text/plain', 'newfile')
+ sbox.simple_commit() # r2
+
+ # try to diff across the addition
+ expected_output = make_diff_header(newfile, 'revision 1', 'revision 2') + \
+ [ '@@ -0,0 +1 @@\n',
+ "+This is the file 'newfile'.\n" ] + \
+ make_diff_prop_header(newfile) + \
+ make_diff_prop_added('svn:mime-type', 'text/plain')
+
+ svntest.actions.run_and_verify_svn(None, expected_output, [], 'diff',
+ '-r1:2', newfile)
+
+ # reverse the diff to diff across a deletion
+ # Note no property delete is printed when whole file is deleted
+ expected_output = make_diff_header(newfile, 'revision 2', 'revision 1') + \
+ [ '@@ -1, +0,0 @@\n',
+ "-This is the file 'newfile'.\n" ]
+ svntest.actions.run_and_verify_svn(None, None, [], 'diff',
+ '-r2:1', newfile)
+
########################################################################
#Run the tests
@@ -3935,6 +4074,8 @@
no_spurious_conflict,
diff_deleted_url,
diff_git_format_wc_wc_dir_mv,
+ diff_repo_wc_file_props,
+ diff_repo_repo_added_file_mime_type,
]
if __name__ == '__main__':
--- subversion-1.7.14/subversion/libsvn_client/diff.c
+++ subversion-1.7.14/subversion/libsvn_client/diff.c
@@ -1892,6 +1892,7 @@
const char *file_abspath;
svn_stream_t *content;
apr_hash_t *prop_hash;
+ svn_string_t *mimetype;
SVN_ERR(svn_stream_open_unique(&content, &file_abspath, NULL,
svn_io_file_del_on_pool_cleanup,
@@ -1900,13 +1901,13 @@
&prop_hash, scratch_pool));
SVN_ERR(svn_stream_close(content));
+ mimetype = apr_hash_get(prop_hash, SVN_PROP_MIME_TYPE, APR_HASH_KEY_STRING);
+
if (show_deletion)
{
SVN_ERR(callbacks->file_deleted(NULL, NULL,
target, file_abspath, empty_file,
- apr_hash_get(prop_hash,
- SVN_PROP_MIME_TYPE,
- APR_HASH_KEY_STRING),
+ mimetype ? mimetype->data : NULL,
NULL,
make_regular_props_hash(
prop_hash, scratch_pool, scratch_pool),
@@ -1917,8 +1918,7 @@
SVN_ERR(callbacks->file_added(NULL, NULL, NULL,
target, empty_file, file_abspath,
rev1, rev2, NULL,
- apr_hash_get(prop_hash, SVN_PROP_MIME_TYPE,
- APR_HASH_KEY_STRING),
+ mimetype ? mimetype->data : NULL,
NULL, SVN_INVALID_REVNUM,
make_regular_props_array(prop_hash,
scratch_pool,
@@ -2243,6 +2243,7 @@
apr_hash_t *file1_props = NULL;
apr_hash_t *file2_props;
svn_boolean_t is_copy = FALSE;
+ svn_string_t *mimetype1, *mimetype2;
/* Get content and props of file 1 (the remote file). */
SVN_ERR(svn_stream_open_unique(&file1_content, &file1_abspath, NULL,
@@ -2292,6 +2293,7 @@
{
apr_hash_t *keywords = NULL;
svn_string_t *keywords_prop;
+ svn_string_t *eol_prop;
svn_subst_eol_style_t eol_style;
const char *eol_str;
@@ -2299,10 +2301,10 @@
scratch_pool, scratch_pool));
/* We might have to create a normalised version of the working file. */
+ eol_prop = apr_hash_get(file2_props, SVN_PROP_EOL_STYLE,
+ APR_HASH_KEY_STRING);
svn_subst_eol_style_from_value(&eol_style, &eol_str,
- apr_hash_get(file2_props,
- SVN_PROP_EOL_STYLE,
- APR_HASH_KEY_STRING));
+ eol_prop ? eol_prop->data : NULL);
keywords_prop = apr_hash_get(file2_props, SVN_PROP_KEYWORDS,
APR_HASH_KEY_STRING);
if (keywords_prop)
@@ -2309,7 +2311,7 @@
SVN_ERR(svn_subst_build_keywords2(&keywords, keywords_prop->data,
NULL, NULL, 0, NULL,
scratch_pool));
- if (svn_subst_translation_required(eol_style, SVN_SUBST_NATIVE_EOL_STR,
+ if (svn_subst_translation_required(eol_style, eol_str,
keywords, FALSE, TRUE))
{
svn_stream_t *working_content;
@@ -2323,7 +2325,7 @@
svn_io_file_del_on_pool_cleanup,
scratch_pool, scratch_pool));
normalized_content = svn_subst_stream_translated(
- file2_content, SVN_SUBST_NATIVE_EOL_STR,
+ file2_content, eol_str,
TRUE, keywords, FALSE, scratch_pool);
SVN_ERR(svn_stream_copy3(working_content, normalized_content,
ctx->cancel_func, ctx->cancel_baton,
@@ -2331,42 +2333,46 @@
}
}
+ mimetype1 = file1_props ? apr_hash_get(file1_props, SVN_PROP_MIME_TYPE,
+ APR_HASH_KEY_STRING)
+ : NULL;
+ mimetype2 = apr_hash_get(file2_props, SVN_PROP_MIME_TYPE,
+ APR_HASH_KEY_STRING);
+
if (kind1 == svn_node_file && !(show_copies_as_adds && is_copy))
{
+ apr_array_header_t *propchanges;
+
SVN_ERR(callbacks->file_opened(NULL, NULL, target,
reverse ? SVN_INVALID_REVNUM : rev,
callback_baton, scratch_pool));
if (reverse)
- SVN_ERR(callbacks->file_changed(NULL, NULL, NULL, target,
- file2_abspath, file1_abspath,
- SVN_INVALID_REVNUM, rev,
- apr_hash_get(file2_props,
- SVN_PROP_MIME_TYPE,
- APR_HASH_KEY_STRING),
- apr_hash_get(file1_props,
- SVN_PROP_MIME_TYPE,
- APR_HASH_KEY_STRING),
- make_regular_props_array(
- file1_props, scratch_pool,
- scratch_pool),
- file2_props,
- callback_baton, scratch_pool));
+ {
+ SVN_ERR(svn_prop_diffs(&propchanges, file1_props, file2_props,
+ scratch_pool));
+
+ SVN_ERR(callbacks->file_changed(NULL, NULL, NULL, target,
+ file2_abspath, file1_abspath,
+ SVN_INVALID_REVNUM, rev,
+ mimetype2 ? mimetype2->data : NULL,
+ mimetype1 ? mimetype1->data : NULL,
+ propchanges, file2_props,
+ callback_baton, scratch_pool));
+ }
else
- SVN_ERR(callbacks->file_changed(NULL, NULL, NULL, target,
- file1_abspath, file2_abspath,
- rev, SVN_INVALID_REVNUM,
- apr_hash_get(file1_props,
- SVN_PROP_MIME_TYPE,
- APR_HASH_KEY_STRING),
- apr_hash_get(file2_props,
- SVN_PROP_MIME_TYPE,
- APR_HASH_KEY_STRING),
- make_regular_props_array(
- file2_props, scratch_pool,
- scratch_pool),
- file1_props,
- callback_baton, scratch_pool));
+ {
+ SVN_ERR(svn_prop_diffs(&propchanges, file2_props, file1_props,
+ scratch_pool));
+
+ SVN_ERR(callbacks->file_changed(NULL, NULL, NULL, target,
+ file1_abspath, file2_abspath,
+ rev, SVN_INVALID_REVNUM,
+ mimetype1 ? mimetype1->data : NULL,
+ mimetype2 ? mimetype2->data : NULL,
+ propchanges, file1_props,
+ callback_baton, scratch_pool));
+ }
}
else
{
@@ -2374,9 +2380,7 @@
{
SVN_ERR(callbacks->file_deleted(NULL, NULL,
target, file2_abspath, file1_abspath,
- apr_hash_get(file2_props,
- SVN_PROP_MIME_TYPE,
- APR_HASH_KEY_STRING),
+ mimetype2 ? mimetype2->data : NULL,
NULL,
make_regular_props_hash(
file2_props, scratch_pool,
@@ -2389,9 +2393,7 @@
file1_abspath, file2_abspath,
rev, SVN_INVALID_REVNUM,
NULL,
- apr_hash_get(file2_props,
- SVN_PROP_MIME_TYPE,
- APR_HASH_KEY_STRING),
+ mimetype2 ? mimetype2->data : NULL,
NULL, SVN_INVALID_REVNUM,
make_regular_props_array(
file2_props, scratch_pool,