You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by rh...@apache.org on 2015/10/02 18:58:19 UTC
svn commit: r1706446 - in /subversion/trunk/subversion:
libsvn_client/patch.c tests/cmdline/patch_tests.py
Author: rhuijben
Date: Fri Oct 2 16:58:19 2015
New Revision: 1706446
URL: http://svn.apache.org/viewvc?rev=1706446&view=rev
Log:
Make 'svn patch' capable of handling git-like symlink changes.
* subversion/libsvn_client/patch.c
(patch_target_t): Add boolean.
(readline_symlink_git): New function.
(write_symlink): Remove function. Assume intermediate file is always text.
(contradictory_executability): Remove function. Fold in single caller.
(init_patch_target): Detect when to use git like symlinks. Create
intermediate always as normal file to simplify processing. Detect when to
set svn:special. Simplify svn:executable handling.
(apply_one_patch): Create svn:special hunk if necessary. Always close patch
file.
* subversion/tests/cmdline/patch_tests.py
(patch_git_symlink): Update expected results. Assume links are handled.
(patch_like_git_symlink): New test. Based on patch_git_symlink but with mode
specifying a regular file.
Modified:
subversion/trunk/subversion/libsvn_client/patch.c
subversion/trunk/subversion/tests/cmdline/patch_tests.py
Modified: subversion/trunk/subversion/libsvn_client/patch.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_client/patch.c?rev=1706446&r1=1706445&r2=1706446&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_client/patch.c (original)
+++ subversion/trunk/subversion/libsvn_client/patch.c Fri Oct 2 16:58:19 2015
@@ -264,6 +264,10 @@ typedef struct patch_target_t {
/* A hash table of prop_patch_target_t objects keyed by property names. */
apr_hash_t *prop_targets;
+ /* When TRUE, this patch uses the raw git symlink format instead of the
+ Subversion internal style format where links start with 'link '. */
+ svn_boolean_t git_symlink_format;
+
} patch_target_t;
@@ -912,6 +916,23 @@ readline_symlink(void *baton, svn_string
return SVN_NO_ERROR;
}
+/* Identical to readline_symlink(), but returns symlink in raw format to
+ * allow patching links in git-style.
+ */
+static svn_error_t *
+readline_symlink_git(void *baton, svn_stringbuf_t **line, const char **eol_str,
+ svn_boolean_t *eof, apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(readline_symlink(baton, line, eol_str, eof,
+ result_pool, scratch_pool));
+
+ if (*line && (*line)->len > 5 && !strncmp((*line)->data, "link ", 5))
+ svn_stringbuf_remove(*line, 0, 5); /* Skip "link " */
+
+ return SVN_NO_ERROR;
+}
+
/* Set *OFFSET to 1 or 0 depending on whether the "normal form" of
* the symlink has already been read. */
static svn_error_t *
@@ -934,30 +955,6 @@ seek_symlink(void *baton, apr_off_t offs
return SVN_NO_ERROR;
}
-
-/* Set the target of the symlink accessed via BATON.
- * The contents of BUF must be a valid "normal form" of a symlink. */
-static svn_error_t *
-write_symlink(void *baton, const char *buf, apr_size_t len,
- apr_pool_t *scratch_pool)
-{
- const char *target_abspath = baton;
-
- /* We assume the entire symlink is written at once, as the patch
- format is line based */
-
- {
- svn_stream_t *stream;
- SVN_ERR(svn_subst_create_specialfile(&stream, target_abspath,
- scratch_pool, scratch_pool));
- SVN_ERR(svn_stream_write(stream, buf, &len));
- SVN_ERR(svn_stream_close(stream));
- }
-
- return SVN_NO_ERROR;
-}
-
-
/* Return a suitable filename for the target of PATCH.
* Examine the ``old'' and ``new'' file names, and choose the file name
* with the fewest path components, the shortest basename, and the shortest
@@ -1001,41 +998,6 @@ choose_target_filename(const svn_patch_t
return (old < new) ? patch->old_filename : patch->new_filename;
}
-/* Return whether the svn:executable proppatch and the out-of-band
- * executability metadata contradict each other, assuming both are present.
- */
-static svn_boolean_t
-contradictory_executability(const svn_patch_t *patch,
- const prop_patch_target_t *target)
-{
- switch (target->operation)
- {
- case svn_diff_op_added:
- return patch->new_executable_bit == svn_tristate_false;
-
- case svn_diff_op_deleted:
- return patch->new_executable_bit == svn_tristate_true;
-
- case svn_diff_op_unchanged:
- /* ### Can this happen? */
- return (patch->old_executable_bit != svn_tristate_unknown
- && patch->new_executable_bit != svn_tristate_unknown
- && patch->old_executable_bit != patch->new_executable_bit);
-
- case svn_diff_op_modified:
- /* Can't happen: the property should only ever be added or deleted,
- * but never modified from one valid value to another. */
- return (patch->old_executable_bit != svn_tristate_unknown
- && patch->new_executable_bit != svn_tristate_unknown
- && patch->old_executable_bit == patch->new_executable_bit);
-
- default:
- /* Can't happen: the proppatch parser never generates other values. */
- SVN_ERR_MALFUNCTION_NO_RETURN();
- }
-}
-
-
/* Attempt to initialize a *PATCH_TARGET structure for a target file
* described by PATCH. Use working copy context WC_CTX.
* STRIP_COUNT specifies the number of leading path components
@@ -1090,6 +1052,12 @@ init_patch_target(patch_target_t **patch
const char *diff_header;
apr_size_t len;
+ if (patch->old_symlink_bit == svn_tristate_true
+ || patch->new_symlink_bit == svn_tristate_true)
+ {
+ target->git_symlink_format = TRUE;
+ }
+
/* Create a temporary file to write the patched result to.
* Also grab various bits of information about the file. */
if (target->is_symlink)
@@ -1102,7 +1070,8 @@ init_patch_target(patch_target_t **patch
/* Wire up the read callbacks. */
content->read_baton = sb;
- content->readline = readline_symlink;
+ content->readline = target->git_symlink_format ? readline_symlink_git
+ : readline_symlink;
content->seek = seek_symlink;
content->tell = tell_symlink;
}
@@ -1199,34 +1168,17 @@ init_patch_target(patch_target_t **patch
}
}
- if (! target->is_symlink)
- {
- /* Open a temporary file to write the patched result to. */
- SVN_ERR(svn_io_open_unique_file3(&target->patched_file,
- &target->patched_path, NULL,
- remove_tempfiles ?
- svn_io_file_del_on_pool_cleanup :
- svn_io_file_del_none,
- result_pool, scratch_pool));
-
- /* Put the write callback in place. */
- content->write = write_file;
- content->write_baton = target->patched_file;
- }
- else
- {
- /* Put the write callback in place. */
- SVN_ERR(svn_io_open_unique_file3(NULL,
- &target->patched_path, NULL,
- remove_tempfiles ?
- svn_io_file_del_on_pool_cleanup :
- svn_io_file_del_none,
- result_pool, scratch_pool));
-
- content->write_baton = (void*)target->patched_path;
-
- content->write = write_symlink;
- }
+ /* Open a temporary file to write the patched result to. */
+ SVN_ERR(svn_io_open_unique_file3(&target->patched_file,
+ &target->patched_path, NULL,
+ remove_tempfiles ?
+ svn_io_file_del_on_pool_cleanup :
+ svn_io_file_del_none,
+ result_pool, scratch_pool));
+
+ /* Put the write callback in place. */
+ content->write = write_file;
+ content->write_baton = target->patched_file;
/* Open a temporary file to write rejected hunks to. */
SVN_ERR(svn_io_open_unique_file3(&target->reject_file,
@@ -1250,7 +1202,6 @@ init_patch_target(patch_target_t **patch
if (! target->skipped)
{
apr_hash_index_t *hi;
- prop_patch_target_t *prop_executable_target;
for (hi = apr_hash_first(result_pool, patch->prop_patches);
hi;
@@ -1268,82 +1219,98 @@ init_patch_target(patch_target_t **patch
svn_hash_sets(target->prop_targets, prop_name, prop_target);
}
- /* Now, check for an out-of-band mode change and convert it to
- * an svn:executable property patch. */
- prop_executable_target = svn_hash_gets(target->prop_targets,
- SVN_PROP_EXECUTABLE);
+ /* Now, check for out-of-band mode changes and convert these in
+ their Subversion equivalent properties. */
if (patch->new_executable_bit != svn_tristate_unknown
- && patch->new_executable_bit != patch->old_executable_bit
- && prop_executable_target)
+ && patch->new_executable_bit != patch->old_executable_bit)
{
- if (contradictory_executability(patch, prop_executable_target))
- /* Invalid input: specifies both git-like "new mode" lines and
- * svn-like addition/removal of svn:executable.
- *
- * If this were merely a hunk that didn't apply, we'd reject it
- * and move on. However, this is a self-contradictory hunk;
- * it has no unambiguous interpretation. Therefore: */
- return svn_error_createf(SVN_ERR_INVALID_INPUT, NULL,
- _("Invalid patch: specifies "
- "contradicting mode changes and "
- "%s changes (for '%s')"),
- SVN_PROP_EXECUTABLE,
- target->local_abspath);
+ svn_diff_operation_kind_t operation;
+
+ if (patch->new_executable_bit == svn_tristate_true)
+ operation = svn_diff_op_added;
+ else if (patch->new_executable_bit == svn_tristate_false)
+ {
+ /* Made non-executable. */
+ if (patch->old_executable_bit == svn_tristate_true)
+ operation = svn_diff_op_deleted;
+ else
+ operation = svn_diff_op_unchanged;
+ }
else
+ operation = svn_diff_op_unchanged;
+
+ if (operation != svn_diff_op_unchanged)
{
- /* We have two representations of the same change. This will
- * be detected where the svn:executable hunk would otherwise
- * be created.
- */
+ prop_patch_target_t *prop_target;
+
+ prop_target = svn_hash_gets(target->prop_targets,
+ SVN_PROP_EXECUTABLE);
+
+ if (prop_target && operation != prop_target->operation)
+ {
+ return svn_error_createf(SVN_ERR_INVALID_INPUT, NULL,
+ _("Invalid patch: specifies "
+ "contradicting mode changes and "
+ "%s changes (for '%s')"),
+ SVN_PROP_EXECUTABLE,
+ target->local_abspath);
+ }
+ else if (!prop_target)
+ {
+ SVN_ERR(init_prop_target(&prop_target,
+ SVN_PROP_EXECUTABLE,
+ operation,
+ wc_ctx, target->local_abspath,
+ result_pool, scratch_pool));
+ svn_hash_sets(target->prop_targets, SVN_PROP_EXECUTABLE,
+ prop_target);
+ }
}
}
- else if (patch->new_executable_bit != svn_tristate_unknown
- && !prop_executable_target)
+
+ if (patch->new_symlink_bit != svn_tristate_unknown
+ && patch->new_symlink_bit != patch->old_symlink_bit)
{
svn_diff_operation_kind_t operation;
- svn_boolean_t nothing_to_do = FALSE;
- prop_patch_target_t *prop_target;
- if (patch->old_executable_bit == patch->new_executable_bit)
+ if (patch->new_symlink_bit == svn_tristate_true)
+ operation = svn_diff_op_added;
+ else if (patch->new_symlink_bit == svn_tristate_false)
{
- /* Noop change. */
- operation = svn_diff_op_unchanged;
- }
- else switch (patch->old_executable_bit)
- {
- case svn_tristate_false:
- /* Made executable. */
- operation = svn_diff_op_added;
- break;
-
- case svn_tristate_true:
- /* Made non-executable. */
+ /* Made non-symlink. */
+ if (patch->old_symlink_bit == svn_tristate_true)
operation = svn_diff_op_deleted;
- break;
-
- case svn_tristate_unknown:
- if (patch->new_executable_bit == svn_tristate_true)
- /* New, executable file. */
- operation = svn_diff_op_added;
- else
- /* New, non-executable file. That's not a change. */
- nothing_to_do = TRUE;
- break;
-
- default:
- /* NOTREACHED */
- SVN_ERR_MALFUNCTION();
+ else
+ operation = svn_diff_op_unchanged;
}
+ else
+ operation = svn_diff_op_unchanged;
- if (! nothing_to_do)
+ if (operation != svn_diff_op_unchanged)
{
- SVN_ERR(init_prop_target(&prop_target,
- SVN_PROP_EXECUTABLE,
- operation,
- wc_ctx, target->local_abspath,
- result_pool, scratch_pool));
- svn_hash_sets(target->prop_targets, SVN_PROP_EXECUTABLE,
- prop_target);
+ prop_patch_target_t *prop_target;
+ prop_target = svn_hash_gets(target->prop_targets,
+ SVN_PROP_SPECIAL);
+
+ if (prop_target && operation != prop_target->operation)
+ {
+ return svn_error_createf(SVN_ERR_INVALID_INPUT, NULL,
+ _("Invalid patch: specifies "
+ "contradicting mode changes and "
+ "%s changes (for '%s')"),
+ SVN_PROP_SPECIAL,
+ target->local_abspath);
+ }
+ else if (!prop_target)
+ {
+ SVN_ERR(init_prop_target(&prop_target,
+ SVN_PROP_SPECIAL,
+ operation,
+ wc_ctx, target->local_abspath,
+ result_pool, scratch_pool));
+ svn_hash_sets(target->prop_targets, SVN_PROP_SPECIAL,
+ prop_target);
+ }
}
}
}
@@ -2715,6 +2682,45 @@ apply_one_patch(patch_target_t **patch_t
APR_ARRAY_PUSH(prop_target->content->hunks, hunk_info_t *) = hi;
}
+ if (patch->new_symlink_bit != svn_tristate_unknown
+ && patch->new_symlink_bit != patch->old_symlink_bit
+ && svn_hash_gets(target->prop_targets, SVN_PROP_SPECIAL)
+ && !svn_hash_gets(patch->prop_patches, SVN_PROP_SPECIAL))
+ {
+ hunk_info_t *hi;
+ svn_diff_hunk_t *hunk;
+
+ prop_patch_target_t *prop_target = svn_hash_gets(target->prop_targets,
+ SVN_PROP_SPECIAL);
+
+ if (patch->new_symlink_bit == svn_tristate_true)
+ {
+ SVN_ERR(svn_diff_hunk__create_adds_single_line(
+ &hunk,
+ SVN_PROP_SPECIAL_VALUE,
+ patch,
+ result_pool,
+ iterpool));
+ target->is_special = TRUE;
+ }
+ else
+ SVN_ERR(svn_diff_hunk__create_deletes_single_line(
+ &hunk,
+ SVN_PROP_SPECIAL_VALUE,
+ patch,
+ result_pool,
+ iterpool));
+
+ /* Derive a hunk_info from hunk. */
+ SVN_ERR(get_hunk_info(&hi, target, prop_target->content,
+ hunk, 0 /* fuzz */, 0 /* previous_offset */,
+ ignore_whitespace,
+ TRUE /* is_prop_hunk */,
+ cancel_func, cancel_baton,
+ result_pool, iterpool));
+ APR_ARRAY_PUSH(prop_target->content->hunks, hunk_info_t *) = hi;
+ }
+
/* Apply or reject property hunks. */
for (hash_index = apr_hash_first(scratch_pool, target->prop_targets);
hash_index;
@@ -2779,10 +2785,10 @@ apply_one_patch(patch_target_t **patch_t
* will be closed later in write_out_rejected_hunks(). */
if (target->kind_on_disk == svn_node_file)
SVN_ERR(svn_io_file_close(target->file, scratch_pool));
-
- SVN_ERR(svn_io_file_close(target->patched_file, scratch_pool));
}
+ SVN_ERR(svn_io_file_close(target->patched_file, scratch_pool));
+
if (! target->skipped)
{
apr_finfo_t working_file;
@@ -3127,7 +3133,8 @@ install_patched_target(patch_target_t *t
if (! dry_run && ! target->skipped)
{
- if (target->is_special)
+ /* ### Are these properly reset? */
+ if (target->is_special || target->is_symlink)
{
svn_stream_t *stream;
svn_stream_t *patched_stream;
@@ -3138,6 +3145,8 @@ install_patched_target(patch_target_t *t
SVN_ERR(svn_subst_create_specialfile(&stream,
target->local_abspath,
pool, pool));
+ if (target->git_symlink_format)
+ SVN_ERR(svn_stream_puts(stream, "link "));
SVN_ERR(svn_stream_copy3(patched_stream, stream,
ctx->cancel_func, ctx->cancel_baton,
pool));
Modified: subversion/trunk/subversion/tests/cmdline/patch_tests.py
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/tests/cmdline/patch_tests.py?rev=1706446&r1=1706445&r2=1706446&view=diff
==============================================================================
--- subversion/trunk/subversion/tests/cmdline/patch_tests.py (original)
+++ subversion/trunk/subversion/tests/cmdline/patch_tests.py Fri Oct 2 16:58:19 2015
@@ -6759,10 +6759,6 @@ def patch_add_remove_executable(sbox):
def patch_git_symlink(sbox):
"patch a git symlink"
- # ### Currently we completely ignore the symlink behavior via mode in
- # ### Subversion but writing this test already found a few bugs in the
- # ### patch code.
-
sbox.build(read_only = True)
wc_dir = sbox.wc_dir
@@ -6826,6 +6822,132 @@ def patch_git_symlink(sbox):
})
expected_disk = svntest.main.greek_state.copy()
expected_disk.add({
+ 'link-to-iota' : Item(contents="This is the file 'iota'.\n",
+ props={'svn:special': '*'}),
+ })
+ if not svntest.main.is_posix_os():
+ expected_disk.tweak('link-to-iota', contents='link iota')
+ expected_skip = svntest.wc.State(wc_dir, {})
+
+ svntest.actions.run_and_verify_patch(wc_dir, add_patch,
+ expected_output, expected_disk,
+ expected_status, expected_skip,
+ [], True, True)
+
+ # And again
+ expected_output.tweak('link-to-iota', status='GG')
+ svntest.actions.run_and_verify_patch(wc_dir, add_patch,
+ expected_output, expected_disk,
+ expected_status, expected_skip,
+ [], True, True)
+
+ # Now tweak the link
+ expected_output.tweak('link-to-iota', status='G ')
+ if svntest.main.is_posix_os():
+ expected_disk.tweak('link-to-iota', contents="This is the file 'mu'.\n")
+ else:
+ expected_disk.tweak('link-to-iota', contents='link A/mu')
+ svntest.actions.run_and_verify_patch(wc_dir, edit_patch,
+ expected_output, expected_disk,
+ expected_status, expected_skip,
+ [], True, True)
+
+ # And again
+ svntest.actions.run_and_verify_patch(wc_dir, edit_patch,
+ expected_output, expected_disk,
+ expected_status, expected_skip,
+ [], True, True)
+
+ # And replace the link with a file
+ expected_output.tweak('link-to-iota', status='A ', prev_status='D ')
+ expected_disk.tweak('link-to-iota', contents="This is a real file\n",
+ props={})
+ svntest.actions.run_and_verify_patch(wc_dir, to_file_patch,
+ expected_output, expected_disk,
+ expected_status, expected_skip,
+ [], True, True)
+
+ # And again - Delete can't be applied
+ expected_output.tweak('link-to-iota', status='G ', prev_status='C ')
+ expected_disk.add({
+ 'link-to-iota.svnpatch.rej': Item(
+ contents='--- link-to-iota\n'
+ '+++ link-to-iota\n'
+ '@@ -1,1 +0,0 @@\n'
+ '-A/mu\n'
+ '\\ No newline at end of file\n'),
+ })
+ svntest.actions.run_and_verify_patch(wc_dir, to_file_patch,
+ expected_output, expected_disk,
+ expected_status, expected_skip,
+ [], True, True)
+
+def patch_like_git_symlink(sbox):
+ "patch like a git symlink"
+
+ sbox.build(read_only = True)
+ wc_dir = sbox.wc_dir
+
+ patch_add = [
+ 'diff --git a/link-to-iota b/link-to-iota\n',
+ 'new file mode 100000\n',
+ 'index 0000000..3ef26e4\n',
+ '--- /dev/null\n',
+ '+++ b/link-to-iota\n',
+ '@@ -0,0 +1 @@\n',
+ '+iota\n',
+ '\ No newline at end of file\n',
+ ]
+
+ patch_edit = [
+ 'diff --git a/link-to-iota b/link-to-iota\n',
+ 'index 3ef26e4..33e5b38 100000\n',
+ '--- a/link-to-iota\n',
+ '+++ b/link-to-iota\n',
+ '@@ -1 +1 @@\n',
+ '-iota\n',
+ '\ No newline at end of file\n',
+ '+A/mu\n',
+ '\ No newline at end of file\n',
+ ]
+
+ patch_to_file = [
+ 'diff --git a/link-to-iota b/link-to-iota\n',
+ 'deleted file mode 100000\n',
+ 'index 33e5b38..0000000\n',
+ '--- a/link-to-iota\n',
+ '+++ /dev/null\n',
+ '@@ -1 +0,0 @@\n',
+ '-A/mu\n',
+ '\ No newline at end of file\n',
+ 'diff --git a/link-to-iota b/link-to-iota\n',
+ 'new file mode 100644\n',
+ 'index 0000000..1b130bf\n',
+ '--- /dev/null\n',
+ '+++ b/link-to-iota\n',
+ '@@ -0,0 +1 @@\n',
+ '+This is a real file\n',
+ ]
+
+ add_patch = sbox.get_tempname('add.patch')
+ svntest.main.file_write(add_patch, ''.join(patch_add), mode='wb')
+
+ edit_patch = sbox.get_tempname('edit.patch')
+ svntest.main.file_write(edit_patch, ''.join(patch_edit), mode='wb')
+
+ to_file_patch = sbox.get_tempname('to_file.patch')
+ svntest.main.file_write(to_file_patch, ''.join(patch_to_file), mode='wb')
+
+
+ expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
+ expected_status.add({
+ 'link-to-iota' : Item(status='A ', wc_rev='-'),
+ })
+ expected_output = svntest.wc.State(wc_dir, {
+ 'link-to-iota' : Item(status='A '),
+ })
+ expected_disk = svntest.main.greek_state.copy()
+ expected_disk.add({
'link-to-iota' : Item(contents="iota"),
})
expected_skip = svntest.wc.State(wc_dir, {})
@@ -6953,6 +7075,7 @@ test_list = [ None,
patch_empty_vs_delete,
patch_add_remove_executable,
patch_git_symlink,
+ patch_like_git_symlink,
]
if __name__ == '__main__':