# ./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,