diff options
Diffstat (limited to 'subversion/libsvn_client/patch.c')
-rw-r--r-- | subversion/libsvn_client/patch.c | 1000 |
1 files changed, 549 insertions, 451 deletions
diff --git a/subversion/libsvn_client/patch.c b/subversion/libsvn_client/patch.c index 24a4c5f..b7fbf06 100644 --- a/subversion/libsvn_client/patch.c +++ b/subversion/libsvn_client/patch.c @@ -47,6 +47,7 @@ #include "private/svn_wc_private.h" #include "private/svn_dep_compat.h" #include "private/svn_string_private.h" +#include "private/svn_subr_private.h" typedef struct hunk_info_t { /* The hunk. */ @@ -174,6 +175,9 @@ typedef struct patch_target_t { * CONTENT->existed). */ apr_file_t *file; + /* The target file is a symlink */ + svn_boolean_t is_symlink; + /* The patched file. * This is equivalent to the target, except that in appropriate * places it contains the modified text as it appears in the patch file. @@ -309,8 +313,7 @@ obtain_eol_and_keywords_for_file(apr_hash_t **keywords, SVN_ERR(svn_wc_prop_list2(&props, wc_ctx, local_abspath, scratch_pool, scratch_pool)); - keywords_val = apr_hash_get(props, SVN_PROP_KEYWORDS, - APR_HASH_KEY_STRING); + keywords_val = svn_hash_gets(props, SVN_PROP_KEYWORDS); if (keywords_val) { svn_revnum_t changed_rev; @@ -318,6 +321,7 @@ obtain_eol_and_keywords_for_file(apr_hash_t **keywords, const char *rev_str; const char *author; const char *url; + const char *root_url; SVN_ERR(svn_wc__node_get_changed_info(&changed_rev, &changed_date, @@ -329,14 +333,16 @@ obtain_eol_and_keywords_for_file(apr_hash_t **keywords, SVN_ERR(svn_wc__node_get_url(&url, wc_ctx, local_abspath, scratch_pool, scratch_pool)); - SVN_ERR(svn_subst_build_keywords2(keywords, + SVN_ERR(svn_wc__node_get_repos_info(NULL, NULL, &root_url, NULL, + wc_ctx, local_abspath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_subst_build_keywords3(keywords, keywords_val->data, - rev_str, url, changed_date, + rev_str, url, root_url, changed_date, author, result_pool)); } - eol_style_val = apr_hash_get(props, SVN_PROP_EOL_STYLE, - APR_HASH_KEY_STRING); + eol_style_val = svn_hash_gets(props, SVN_PROP_EOL_STYLE); if (eol_style_val) { svn_subst_eol_style_from_value(eol_style, @@ -365,7 +371,7 @@ obtain_eol_and_keywords_for_file(apr_hash_t **keywords, static svn_error_t * resolve_target_path(patch_target_t *target, const char *path_from_patchfile, - const char *local_abspath, + const char *wcroot_abspath, int strip_count, svn_boolean_t prop_changes_only, svn_wc_context_t *wc_ctx, @@ -398,7 +404,8 @@ resolve_target_path(patch_target_t *target, if (svn_dirent_is_absolute(stripped_path)) { - target->local_relpath = svn_dirent_is_child(local_abspath, stripped_path, + target->local_relpath = svn_dirent_is_child(wcroot_abspath, + stripped_path, result_pool); if (! target->local_relpath) @@ -419,7 +426,7 @@ resolve_target_path(patch_target_t *target, /* Make sure the path is secure to use. We want the target to be inside * of the working copy and not be fooled by symlinks it might contain. */ SVN_ERR(svn_dirent_is_under_root(&under_root, - &target->local_abspath, local_abspath, + &target->local_abspath, wcroot_abspath, target->local_relpath, result_pool)); if (! under_root) @@ -435,54 +442,66 @@ resolve_target_path(patch_target_t *target, result_pool, scratch_pool); if (err) { - if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) - svn_error_clear(err); - else + if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) return svn_error_trace(err); + + svn_error_clear(err); + + target->locally_deleted = TRUE; + target->db_kind = svn_node_none; + status = NULL; } else if (status->node_status == svn_wc_status_ignored || status->node_status == svn_wc_status_unversioned || status->node_status == svn_wc_status_missing || - status->node_status == svn_wc_status_obstructed) + status->node_status == svn_wc_status_obstructed || + status->conflicted) { target->skipped = TRUE; return SVN_NO_ERROR; } - - SVN_ERR(svn_io_check_path(target->local_abspath, - &target->kind_on_disk, scratch_pool)); - err = svn_wc__node_is_status_deleted(&target->locally_deleted, - wc_ctx, target->local_abspath, - scratch_pool); - if (err) + else if (status->node_status == svn_wc_status_deleted) { - if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) - { - svn_error_clear(err); - target->locally_deleted = FALSE; - } - else - return svn_error_trace(err); + target->locally_deleted = TRUE; } - SVN_ERR(svn_wc_read_kind(&target->db_kind, wc_ctx, target->local_abspath, - FALSE, scratch_pool)); - /* If the target is a versioned directory present on disk, - * and there are only property changes in the patch, we accept - * a directory target. Else, we skip directories. */ - if (target->db_kind == svn_node_dir && ! prop_changes_only) - { - /* ### We cannot yet replace a locally deleted dir with a file, - * ### but some day we might want to allow it. */ - target->skipped = TRUE; - return SVN_NO_ERROR; - } + if (status && (status->kind != svn_node_unknown)) + target->db_kind = status->kind; + else + target->db_kind = svn_node_none; + + SVN_ERR(svn_io_check_special_path(target->local_abspath, + &target->kind_on_disk, &target->is_symlink, + scratch_pool)); - /* ### Shouldn't libsvn_wc flag an obstruction in this case? */ - if (target->locally_deleted && target->kind_on_disk != svn_node_none) + if (target->locally_deleted) { - target->skipped = TRUE; - return SVN_NO_ERROR; + const char *moved_to_abspath; + + SVN_ERR(svn_wc__node_was_moved_away(&moved_to_abspath, NULL, + wc_ctx, target->local_abspath, + result_pool, scratch_pool)); + if (moved_to_abspath) + { + target->local_abspath = moved_to_abspath; + target->local_relpath = svn_dirent_skip_ancestor(wcroot_abspath, + moved_to_abspath); + SVN_ERR_ASSERT(target->local_relpath && + target->local_relpath[0] != '\0'); + + /* As far as we are concerned this target is not locally deleted. */ + target->locally_deleted = FALSE; + + SVN_ERR(svn_io_check_special_path(target->local_abspath, + &target->kind_on_disk, + &target->is_symlink, + scratch_pool)); + } + else if (target->kind_on_disk != svn_node_none) + { + target->skipped = TRUE; + return SVN_NO_ERROR; + } } return SVN_NO_ERROR; @@ -500,6 +519,8 @@ typedef struct prop_read_baton_t { * the property value runs out in which case *EOF is set to TRUE. * The line-terminator is not stored in *STRINGBUF. * + * If the line is empty or could not be read, *line is set to NULL. + * * The line-terminator is detected automatically and stored in *EOL * if EOL is not NULL. If the end of the property value is reached * and does not end with a newline character, and EOL is not NULL, @@ -513,17 +534,15 @@ readline_prop(void *baton, svn_stringbuf_t **line, const char **eol_str, apr_pool_t *scratch_pool) { prop_read_baton_t *b = (prop_read_baton_t *)baton; - svn_stringbuf_t *str; + svn_stringbuf_t *str = NULL; const char *c; svn_boolean_t found_eof; - str = svn_stringbuf_create_ensure(80, result_pool); - - if (b->offset >= b->value->len) + if ((apr_uint64_t)b->offset >= (apr_uint64_t)b->value->len) { *eol_str = NULL; *eof = TRUE; - *line = str; + *line = NULL; return SVN_NO_ERROR; } @@ -555,7 +574,11 @@ readline_prop(void *baton, svn_stringbuf_t **line, const char **eol_str, } } else - svn_stringbuf_appendbyte(str, *c); + { + if (str == NULL) + str = svn_stringbuf_create_ensure(80, result_pool); + svn_stringbuf_appendbyte(str, *c); + } if (*eol_str) break; @@ -629,7 +652,7 @@ init_prop_target(prop_patch_target_t **prop_target, content->hunks = apr_array_make(result_pool, 0, sizeof(hunk_info_t *)); content->keywords = apr_hash_make(result_pool); - new_prop_target = apr_palloc(result_pool, sizeof(*new_prop_target)); + new_prop_target = apr_pcalloc(result_pool, sizeof(*new_prop_target)); new_prop_target->name = apr_pstrdup(result_pool, prop_name); new_prop_target->operation = operation; new_prop_target->content = content; @@ -648,11 +671,11 @@ init_prop_target(prop_patch_target_t **prop_target, } content->existed = (value != NULL); new_prop_target->value = value; - new_prop_target->patched_value = svn_stringbuf_create("", result_pool); + new_prop_target->patched_value = svn_stringbuf_create_empty(result_pool); /* Wire up the read and write callbacks. */ - prop_read_baton = apr_palloc(result_pool, sizeof(*prop_read_baton)); + prop_read_baton = apr_pcalloc(result_pool, sizeof(*prop_read_baton)); prop_read_baton->value = value; prop_read_baton->offset = 0; content->readline = readline_prop; @@ -673,6 +696,8 @@ init_prop_target(prop_patch_target_t **prop_target, * or if EOF is reached in which case *EOF is set to TRUE. * The line-terminator is not stored in *STRINGBUF. * + * If the line is empty or could not be read, *line is set to NULL. + * * The line-terminator is detected automatically and stored in *EOL * if EOL is not NULL. If EOF is reached and FILE does not end * with a newline character, and EOL is not NULL, *EOL is set to NULL. @@ -685,13 +710,11 @@ readline_file(void *baton, svn_stringbuf_t **line, const char **eol_str, apr_pool_t *scratch_pool) { apr_file_t *file = (apr_file_t *)baton; - svn_stringbuf_t *str; + svn_stringbuf_t *str = NULL; apr_size_t numbytes; char c; svn_boolean_t found_eof; - str = svn_stringbuf_create_ensure(80, result_pool); - /* Read bytes into STR up to and including, but not storing, * the next EOL sequence. */ *eol_str = NULL; @@ -738,7 +761,11 @@ readline_file(void *baton, svn_stringbuf_t **line, const char **eol_str, } } else - svn_stringbuf_appendbyte(str, c); + { + if (str == NULL) + str = svn_stringbuf_create_ensure(80, result_pool); + svn_stringbuf_appendbyte(str, c); + } if (*eol_str) break; @@ -784,6 +811,118 @@ write_file(void *baton, const char *buf, apr_size_t len, return SVN_NO_ERROR; } +/* Handling symbolic links: + * + * In Subversion, symlinks can be represented on disk in two distinct ways. + * On systems which support symlinks, a symlink is created on disk. + * On systems which do not support symlink, a file is created on disk + * which contains the "normal form" of the symlink, which looks like: + * link TARGET + * where TARGET is the file the symlink points to. + * + * When reading symlinks (i.e. the link itself, not the file the symlink + * is pointing to) through the svn_subst_create_specialfile() function + * into a buffer, the buffer always contains the "normal form" of the symlink. + * Due to this representation symlinks always contain a single line of text. + * + * The functions below are needed to deal with the case where a patch + * wants to change the TARGET that a symlink points to. + */ + +/* Baton for the (readline|tell|seek|write)_symlink functions. */ +struct symlink_baton_t +{ + /* The path to the symlink on disk (not the path to the target of the link) */ + const char *local_abspath; + + /* Indicates whether the "normal form" of the symlink has been read. */ + svn_boolean_t at_eof; +}; + +/* Allocate *STRINGBUF in RESULT_POOL, and store into it the "normal form" + * of the symlink accessed via BATON. + * + * Otherwise behaves like readline_file(), which see. + */ +static svn_error_t * +readline_symlink(void *baton, svn_stringbuf_t **line, const char **eol_str, + svn_boolean_t *eof, apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + struct symlink_baton_t *sb = baton; + + if (eof) + *eof = TRUE; + if (eol_str) + *eol_str = NULL; + + if (sb->at_eof) + { + *line = NULL; + } + else + { + svn_string_t *dest; + + SVN_ERR(svn_io_read_link(&dest, sb->local_abspath, scratch_pool)); + *line = svn_stringbuf_createf(result_pool, "link %s", dest->data); + sb->at_eof = TRUE; + } + + 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 * +tell_symlink(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool) +{ + struct symlink_baton_t *sb = baton; + + *offset = sb->at_eof ? 1 : 0; + return SVN_NO_ERROR; +} + +/* If offset is non-zero, mark the symlink as having been read in its + * "normal form". Else, mark the symlink as not having been read yet. */ +static svn_error_t * +seek_symlink(void *baton, apr_off_t offset, apr_pool_t *scratch_pool) +{ + struct symlink_baton_t *sb = baton; + + sb->at_eof = (offset != 0); + 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; + const char *new_name; + const char *link = apr_pstrndup(scratch_pool, buf, len); + + if (strncmp(link, "link ", 5) != 0) + return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, + _("Invalid link representation")); + + link += 5; /* Skip "link " */ + + /* We assume the entire symlink is written at once, as the patch + format is line based */ + + SVN_ERR(svn_io_create_unique_link(&new_name, target_abspath, link, + ".tmp", scratch_pool)); + + SVN_ERR(svn_io_file_rename(new_name, target_abspath, scratch_pool)); + + 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 @@ -835,7 +974,7 @@ choose_target_filename(const svn_patch_t *patch) static svn_error_t * init_patch_target(patch_target_t **patch_target, const svn_patch_t *patch, - const char *base_dir, + const char *wcroot_abspath, svn_wc_context_t *wc_ctx, int strip_count, svn_boolean_t remove_tempfiles, apr_pool_t *result_pool, apr_pool_t *scratch_pool) @@ -880,7 +1019,7 @@ init_patch_target(patch_target_t **patch_target, target->prop_targets = apr_hash_make(result_pool); SVN_ERR(resolve_target_path(target, choose_target_filename(patch), - base_dir, strip_count, prop_changes_only, + wcroot_abspath, strip_count, prop_changes_only, wc_ctx, result_pool, scratch_pool)); if (! target->skipped) { @@ -889,10 +1028,24 @@ init_patch_target(patch_target_t **patch_target, /* Create a temporary file to write the patched result to. * Also grab various bits of information about the file. */ - if (target->kind_on_disk == svn_node_file) + if (target->is_symlink) + { + struct symlink_baton_t *sb = apr_pcalloc(result_pool, sizeof(*sb)); + content->existed = TRUE; + + sb->local_abspath = target->local_abspath; + + /* Wire up the read callbacks. */ + content->read_baton = sb; + + content->readline = readline_symlink; + content->seek = seek_symlink; + content->tell = tell_symlink; + } + else if (target->kind_on_disk == svn_node_file) { SVN_ERR(svn_io_file_open(&target->file, target->local_abspath, - APR_READ | APR_BINARY | APR_BUFFERED, + APR_READ | APR_BUFFERED, APR_OS_DEFAULT, result_pool)); SVN_ERR(svn_wc_text_modified_p2(&target->local_mods, wc_ctx, target->local_abspath, FALSE, @@ -927,17 +1080,34 @@ init_patch_target(patch_target_t **patch_target, else if (patch->operation == svn_diff_op_deleted) target->deleted = TRUE; - /* 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)); + 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)); - /* Put the write callback in place. */ - content->write = write_file; - content->write_baton = target->patched_file; + content->write_baton = (void*)target->patched_path; + + content->write = write_symlink; + } /* Open a temporary file to write rejected hunks to. */ SVN_ERR(svn_io_open_unique_file3(&target->reject_file, @@ -975,8 +1145,7 @@ init_patch_target(patch_target_t **patch_target, prop_patch->operation, wc_ctx, target->local_abspath, result_pool, scratch_pool)); - apr_hash_set(target->prop_targets, prop_name, - APR_HASH_KEY_STRING, prop_target); + svn_hash_sets(target->prop_targets, prop_name, prop_target); } } } @@ -986,7 +1155,9 @@ init_patch_target(patch_target_t **patch_target, } /* Read a *LINE from CONTENT. If the line has not been read before - * mark the line in CONTENT->LINES. Allocate *LINE in RESULT_POOL. + * mark the line in CONTENT->LINES. + * If a line could be read successfully, increase CONTENT->CURRENT_LINE, + * and allocate *LINE in RESULT_POOL. * Do temporary allocations in SCRATCH_POOL. */ static svn_error_t * @@ -997,6 +1168,7 @@ readline(target_content_t *content, { svn_stringbuf_t *line_raw; const char *eol_str; + svn_linenum_t max_line = (svn_linenum_t)content->lines->nelts + 1; if (content->eof || content->readline == NULL) { @@ -1004,8 +1176,8 @@ readline(target_content_t *content, return SVN_NO_ERROR; } - SVN_ERR_ASSERT(content->current_line <= content->lines->nelts + 1); - if (content->current_line == content->lines->nelts + 1) + SVN_ERR_ASSERT(content->current_line <= max_line); + if (content->current_line == max_line) { apr_off_t offset; @@ -1020,14 +1192,22 @@ readline(target_content_t *content, if (content->eol_style == svn_subst_eol_style_none) content->eol_str = eol_str; - /* Contract keywords. */ - SVN_ERR(svn_subst_translate_cstring2(line_raw->data, line, - NULL, FALSE, - content->keywords, FALSE, - result_pool)); - if (! content->eof) + if (line_raw) + { + /* Contract keywords. */ + SVN_ERR(svn_subst_translate_cstring2(line_raw->data, line, + NULL, FALSE, + content->keywords, FALSE, + result_pool)); + } + else + *line = ""; + + if ((line_raw && line_raw->len > 0) || eol_str) content->current_line++; + SVN_ERR_ASSERT(content->current_line > 0); + return SVN_NO_ERROR; } @@ -1050,7 +1230,7 @@ seek_to_line(target_content_t *content, svn_linenum_t line, saved_line = content->current_line; saved_eof = content->eof; - if (line <= content->lines->nelts) + if (line <= (svn_linenum_t)content->lines->nelts) { apr_off_t offset; @@ -1359,26 +1539,45 @@ get_hunk_info(hunk_info_t **hi, patch_target_t *target, * the hunk applies at line 1. If the file already exists, the hunk * is rejected, unless the file is versioned and its content matches * the file the patch wants to create. */ - if (original_start == 0 && ! is_prop_hunk) + if (original_start == 0 && fuzz > 0) + { + matched_line = 0; /* reject any fuzz for new files */ + } + else if (original_start == 0 && ! is_prop_hunk) { if (target->kind_on_disk == svn_node_file) { - if (target->db_kind == svn_node_file) + const svn_io_dirent2_t *dirent; + SVN_ERR(svn_io_stat_dirent2(&dirent, target->local_abspath, FALSE, + TRUE, scratch_pool, scratch_pool)); + + if (dirent->kind == svn_node_file + && !dirent->special + && dirent->filesize == 0) { - svn_boolean_t file_matches; + matched_line = 1; /* Matched an on-disk empty file */ + } + else + { + if (target->db_kind == svn_node_file) + { + svn_boolean_t file_matches; - SVN_ERR(match_existing_target(&file_matches, content, hunk, + /* ### I can't reproduce anything but a no-match here. + The content is already at eof, so any hunk fails */ + SVN_ERR(match_existing_target(&file_matches, content, hunk, scratch_pool)); - if (file_matches) - { - matched_line = 1; - already_applied = TRUE; + if (file_matches) + { + matched_line = 1; + already_applied = TRUE; + } + else + matched_line = 0; /* reject */ } else matched_line = 0; /* reject */ } - else - matched_line = 0; /* reject */ } else matched_line = 1; @@ -1494,7 +1693,7 @@ get_hunk_info(hunk_info_t **hi, patch_target_t *target, matched_line = 0; } - (*hi) = apr_palloc(result_pool, sizeof(hunk_info_t)); + (*hi) = apr_pcalloc(result_pool, sizeof(hunk_info_t)); (*hi)->hunk = hunk; (*hi)->matched_line = matched_line; (*hi)->rejected = (matched_line == 0); @@ -1858,6 +2057,56 @@ send_patch_notification(const patch_target_t *target, return SVN_NO_ERROR; } +static void +svn_sort__array(apr_array_header_t *array, + int (*comparison_func)(const void *, + const void *)) +{ + qsort(array->elts, array->nelts, array->elt_size, comparison_func); +} + +/* Implements the callback for svn_sort__array. Puts hunks that match + before hunks that do not match, puts hunks that match in order + based on postion matched, puts hunks that do not match in order + based on original position. */ +static int +sort_matched_hunks(const void *a, const void *b) +{ + const hunk_info_t *item1 = *((const hunk_info_t * const *)a); + const hunk_info_t *item2 = *((const hunk_info_t * const *)b); + svn_boolean_t matched1 = !item1->rejected && !item1->already_applied; + svn_boolean_t matched2 = !item2->rejected && !item2->already_applied; + svn_linenum_t original1, original2; + + if (matched1 && matched2) + { + /* Both match so use order matched in file. */ + if (item1->matched_line > item2->matched_line) + return 1; + else if (item1->matched_line == item2->matched_line) + return 0; + else + return -1; + } + else if (matched2) + /* Only second matches, put it before first. */ + return 1; + else if (matched1) + /* Only first matches, put it before second. */ + return -1; + + /* Neither matches, sort by original_start. */ + original1 = svn_diff_hunk_get_original_start(item1->hunk); + original2 = svn_diff_hunk_get_original_start(item2->hunk); + if (original1 > original2) + return 1; + else if (original1 == original2) + return 0; + else + return -1; +} + + /* Apply a PATCH to a working copy at ABS_WC_PATH and put the result * into temporary files, to be installed in the working copy later. * Return information about the patch target in *PATCH_TARGET, allocated @@ -1939,6 +2188,10 @@ apply_one_patch(patch_target_t **patch_target, svn_patch_t *patch, APR_ARRAY_PUSH(target->content->hunks, hunk_info_t *) = hi; } + /* Hunks are applied in the order determined by the matched line and + this may be different from the order of the original lines. */ + svn_sort__array(target->content->hunks, sort_matched_hunks); + /* Apply or reject hunks. */ for (i = 0; i < target->content->hunks->nelts; i++) { @@ -1990,8 +2243,7 @@ apply_one_patch(patch_target_t **patch_target, svn_patch_t *patch, target->is_special = TRUE; /* We'll store matched hunks in prop_content. */ - prop_target = apr_hash_get(target->prop_targets, prop_name, - APR_HASH_KEY_STRING); + prop_target = svn_hash_gets(target->prop_targets, prop_name); for (i = 0; i < prop_patch->hunks->nelts; i++) { @@ -2070,13 +2322,17 @@ apply_one_patch(patch_target_t **patch_target, svn_patch_t *patch, svn_pool_destroy(iterpool); - /* Now close files we don't need any longer to get their contents - * flushed to disk. - * But we're not closing the reject file -- it still needed and - * 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)); + if (!target->is_symlink) + { + /* Now close files we don't need any longer to get their contents + * flushed to disk. + * But we're not closing the reject file -- it still needed and + * 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)); + } if (! target->skipped) { @@ -2087,10 +2343,10 @@ apply_one_patch(patch_target_t **patch_target, svn_patch_t *patch, * We'll need those to figure out whether we should delete the * patched file. */ SVN_ERR(svn_io_stat(&patched_file, target->patched_path, - APR_FINFO_SIZE, scratch_pool)); + APR_FINFO_SIZE | APR_FINFO_LINK, scratch_pool)); if (target->kind_on_disk == svn_node_file) SVN_ERR(svn_io_stat(&working_file, target->local_abspath, - APR_FINFO_SIZE, scratch_pool)); + APR_FINFO_SIZE | APR_FINFO_LINK, scratch_pool)); else working_file.size = 0; @@ -2159,47 +2415,36 @@ create_missing_parents(patch_target_t *target, { const char *component; svn_node_kind_t wc_kind, disk_kind; - svn_boolean_t is_deleted; svn_pool_clear(iterpool); component = APR_ARRAY_IDX(components, i, const char *); local_abspath = svn_dirent_join(local_abspath, component, scratch_pool); - SVN_ERR(svn_wc_read_kind(&wc_kind, ctx->wc_ctx, local_abspath, TRUE, - iterpool)); + SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, local_abspath, + FALSE, TRUE, iterpool)); SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, iterpool)); - if (wc_kind != svn_node_none) - SVN_ERR(svn_wc__node_is_status_deleted(&is_deleted, - ctx->wc_ctx, - local_abspath, - iterpool)); - else - is_deleted = FALSE; - - if (disk_kind == svn_node_file - || (wc_kind == svn_node_file && !is_deleted)) + if (disk_kind == svn_node_file || wc_kind == svn_node_file) { /* on-disk files and missing files are obstructions */ target->skipped = TRUE; break; } - else if (wc_kind == svn_node_dir) + else if (disk_kind == svn_node_dir) { - if (is_deleted) + if (wc_kind == svn_node_dir) + present_components++; + else { target->skipped = TRUE; break; } - - /* continue one level deeper */ - present_components++; } - else if (disk_kind == svn_node_dir) + else if (wc_kind != svn_node_none) { - /* Obstructed. ### BH: why? We can just add a directory */ + /* Node is missing */ target->skipped = TRUE; break; } @@ -2210,7 +2455,6 @@ create_missing_parents(patch_target_t *target, break; } } - if (! target->skipped) { local_abspath = abs_wc_path; @@ -2264,9 +2508,10 @@ create_missing_parents(patch_target_t *target, if (ctx->cancel_func) SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); - SVN_ERR(svn_wc_add_from_disk(ctx->wc_ctx, local_abspath, - ctx->notify_func2, ctx->notify_baton2, - iterpool)); + SVN_ERR(svn_wc_add_from_disk2(ctx->wc_ctx, local_abspath, + NULL /*props*/, + ctx->notify_func2, ctx->notify_baton2, + iterpool)); } } } @@ -2301,37 +2546,53 @@ install_patched_target(patch_target_t *target, const char *abs_wc_path, else { svn_node_kind_t parent_db_kind; - - if (target->added) + if (target->added || target->replaced) { + const char *parent_abspath; + + parent_abspath = svn_dirent_dirname(target->local_abspath, + pool); /* If the target's parent directory does not yet exist * we need to create it before we can copy the patched * result in place. */ - SVN_ERR(svn_wc_read_kind(&parent_db_kind, ctx->wc_ctx, - svn_dirent_dirname(target->local_abspath, - pool), - FALSE, pool)); - - /* We don't allow targets to be added under dirs scheduled for - * deletion. */ - if (parent_db_kind == svn_node_dir) + SVN_ERR(svn_wc_read_kind2(&parent_db_kind, ctx->wc_ctx, + parent_abspath, FALSE, FALSE, pool)); + + /* We can't add targets under nodes scheduled for delete, so add + a new directory if needed. */ + if (parent_db_kind == svn_node_dir + || parent_db_kind == svn_node_file) { - const char *parent_abspath; - svn_boolean_t is_deleted; - - parent_abspath = svn_dirent_dirname(target->local_abspath, - pool); - SVN_ERR(svn_wc__node_is_status_deleted(&is_deleted, ctx->wc_ctx, - parent_abspath, pool)); - if (is_deleted) + if (parent_db_kind != svn_node_dir) + target->skipped = TRUE; + else { - target->skipped = TRUE; - return SVN_NO_ERROR; + svn_node_kind_t disk_kind; + + SVN_ERR(svn_io_check_path(parent_abspath, &disk_kind, pool)); + if (disk_kind != svn_node_dir) + target->skipped = TRUE; } } else SVN_ERR(create_missing_parents(target, abs_wc_path, ctx, dry_run, pool)); + + } + else + { + svn_node_kind_t wc_kind; + + /* The target should exist */ + SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, + target->local_abspath, + FALSE, FALSE, pool)); + + if (target->kind_on_disk == svn_node_none + || wc_kind != target->kind_on_disk) + { + target->skipped = TRUE; + } } if (! dry_run && ! target->skipped) @@ -2378,8 +2639,9 @@ install_patched_target(patch_target_t *target, const char *abs_wc_path, * Suppress notification, we'll do that later (and also * during dry-run). Don't allow cancellation because * we'd rather notify about what we did before aborting. */ - SVN_ERR(svn_wc_add_from_disk(ctx->wc_ctx, target->local_abspath, - NULL, NULL, pool)); + SVN_ERR(svn_wc_add_from_disk2(ctx->wc_ctx, target->local_abspath, + NULL /*props*/, + NULL, NULL, pool)); } /* Restore the target's executable bit if necessary. */ @@ -2470,10 +2732,11 @@ install_patched_prop_targets(patch_target_t *target, { SVN_ERR(svn_io_file_create(target->local_abspath, "", scratch_pool)); - SVN_ERR(svn_wc_add_from_disk(ctx->wc_ctx, target->local_abspath, - /* suppress notification */ - NULL, NULL, - iterpool)); + SVN_ERR(svn_wc_add_from_disk2(ctx->wc_ctx, target->local_abspath, + NULL /*props*/, + /* suppress notification */ + NULL, NULL, + iterpool)); } target->added = TRUE; } @@ -2547,318 +2810,164 @@ install_patched_prop_targets(patch_target_t *target, return SVN_NO_ERROR; } -/* Baton for find_existing_children() */ -struct status_baton +/* Baton for can_delete_callback */ +struct can_delete_baton_t { - apr_array_header_t *existing_targets; - const char *parent_path; - apr_pool_t *result_pool; + svn_boolean_t must_keep; + const apr_array_header_t *targets_info; + const char *local_abspath; }; /* Implements svn_wc_status_func4_t. */ static svn_error_t * -find_existing_children(void *baton, - const char *abspath, - const svn_wc_status3_t *status, - apr_pool_t *pool) +can_delete_callback(void *baton, + const char *abspath, + const svn_wc_status3_t *status, + apr_pool_t *pool) { - struct status_baton *btn = baton; - - if (status->node_status != svn_wc_status_none - && status->node_status != svn_wc_status_deleted - && strcmp(abspath, btn->parent_path)) - { - APR_ARRAY_PUSH(btn->existing_targets, - const char *) = apr_pstrdup(btn->result_pool, - abspath); - } - - return SVN_NO_ERROR; -} - -/* Indicate in *EMPTY whether the directory at LOCAL_ABSPATH has any - * versioned or unversioned children. Consider any DELETED_TARGETS, - * as well as paths occuring as keys of DELETED_ABSPATHS_HASH (which may - * be NULL) as already deleted. Use WC_CTX as the working copy context. - * Do temporary allocations in SCRATCH_POOL. */ -static svn_error_t * -check_dir_empty(svn_boolean_t *empty, const char *local_abspath, - svn_wc_context_t *wc_ctx, - apr_array_header_t *deleted_targets, - apr_hash_t *deleted_abspath_hash, - apr_pool_t *scratch_pool) -{ - struct status_baton btn; - svn_boolean_t is_wc_root; + struct can_delete_baton_t *cb = baton; int i; - /* Working copy root cannot be deleted, so never consider it empty. */ - SVN_ERR(svn_wc__strictly_is_wc_root(&is_wc_root, wc_ctx, local_abspath, - scratch_pool)); - if (is_wc_root) + switch(status->node_status) { - *empty = FALSE; - return SVN_NO_ERROR; - } + case svn_wc_status_none: + case svn_wc_status_deleted: + return SVN_NO_ERROR; - /* Find existing children of the directory. */ - btn.existing_targets = apr_array_make(scratch_pool, 0, - sizeof(patch_target_t *)); - btn.parent_path = local_abspath; - btn.result_pool = scratch_pool; - SVN_ERR(svn_wc_walk_status(wc_ctx, local_abspath, svn_depth_immediates, - TRUE, TRUE, FALSE, NULL, find_existing_children, - &btn, NULL, NULL, scratch_pool)); - *empty = TRUE; - - /* Do we delete all children? */ - for (i = 0; i < btn.existing_targets->nelts; i++) - { - int j; - const char *found; - svn_boolean_t deleted; + default: + if (! strcmp(cb->local_abspath, abspath)) + return SVN_NO_ERROR; /* Only interested in descendants */ - deleted = FALSE; - found = APR_ARRAY_IDX(btn.existing_targets, i, const char *); + for (i = 0; i < cb->targets_info->nelts; i++) + { + const patch_target_info_t *target_info = + APR_ARRAY_IDX(cb->targets_info, i, const patch_target_info_t *); - for (j = 0; j < deleted_targets->nelts; j++) - { - patch_target_info_t *target_info; + if (! strcmp(target_info->local_abspath, abspath)) + { + if (target_info->deleted) + return SVN_NO_ERROR; - target_info = APR_ARRAY_IDX(deleted_targets, j, - patch_target_info_t *); - if (! svn_path_compare_paths(found, target_info->local_abspath)) - { - deleted = TRUE; - break; - } - } - if (! deleted && deleted_abspath_hash) - { - apr_hash_index_t *hi; + break; /* Cease invocation; must keep */ + } + } - for (hi = apr_hash_first(scratch_pool, deleted_abspath_hash); - hi; - hi = apr_hash_next(hi)) - { - const char *abspath; - - abspath = svn__apr_hash_index_key(hi); - if (! svn_path_compare_paths(found, abspath)) - { - deleted = TRUE; - break; - } - } - } - if (! deleted) - { - *empty = FALSE; - break; - } - } + cb->must_keep = TRUE; - return SVN_NO_ERROR; + return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL); + } } -/* Delete all directories from the working copy which are left empty - * by deleted TARGETS. Use client context CTX. - * If DRY_RUN is TRUE, do not modify the working copy. - * Do temporary allocations in SCRATCH_POOL. */ static svn_error_t * -delete_empty_dirs(apr_array_header_t *targets_info, svn_client_ctx_t *ctx, - svn_boolean_t dry_run, apr_pool_t *scratch_pool) +check_ancestor_delete(const char *deleted_target, + apr_array_header_t *targets_info, + const char *apply_root, + svn_boolean_t dry_run, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) { - apr_hash_t *empty_dirs; - apr_hash_t *non_empty_dirs; - apr_array_header_t *deleted_targets; - apr_pool_t *iterpool; - svn_boolean_t again; - int i; - apr_hash_index_t *hi; - - /* Get a list of all deleted targets. */ - deleted_targets = apr_array_make(scratch_pool, 0, sizeof(patch_target_t *)); - for (i = 0; i < targets_info->nelts; i++) - { - patch_target_info_t *target_info; - - target_info = APR_ARRAY_IDX(targets_info, i, patch_target_info_t *); - if (target_info->deleted) - APR_ARRAY_PUSH(deleted_targets, patch_target_info_t *) = target_info; - } - - /* We have nothing to do if there aren't any deleted targets. */ - if (deleted_targets->nelts == 0) - return SVN_NO_ERROR; - - /* Look for empty parent directories of deleted targets. */ - empty_dirs = apr_hash_make(scratch_pool); - non_empty_dirs = apr_hash_make(scratch_pool); - iterpool = svn_pool_create(scratch_pool); - for (i = 0; i < deleted_targets->nelts; i++) - { - svn_boolean_t parent_empty; - patch_target_info_t *target_info; - const char *parent; - - svn_pool_clear(iterpool); - - if (ctx->cancel_func) - SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); - - target_info = APR_ARRAY_IDX(deleted_targets, i, patch_target_info_t *); - - parent = svn_dirent_dirname(target_info->local_abspath, iterpool); + struct can_delete_baton_t cb; + svn_error_t *err; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); - if (apr_hash_get(non_empty_dirs, parent, APR_HASH_KEY_STRING)) - continue; - else if (apr_hash_get(empty_dirs, parent, APR_HASH_KEY_STRING)) - continue; + const char *dir_abspath = svn_dirent_dirname(deleted_target, scratch_pool); - SVN_ERR(check_dir_empty(&parent_empty, parent, ctx->wc_ctx, - deleted_targets, NULL, iterpool)); - if (parent_empty) - apr_hash_set(empty_dirs, apr_pstrdup(scratch_pool, parent), - APR_HASH_KEY_STRING, ""); - else - apr_hash_set(non_empty_dirs, apr_pstrdup(scratch_pool, parent), - APR_HASH_KEY_STRING, ""); - } - - /* We have nothing to do if there aren't any empty directories. */ - if (apr_hash_count(empty_dirs) == 0) + while (svn_dirent_is_child(apply_root, dir_abspath, iterpool)) { - svn_pool_destroy(iterpool); - return SVN_NO_ERROR; - } - - /* Determine the minimal set of empty directories we need to delete. */ - do - { - apr_hash_t *empty_dirs_copy; - svn_pool_clear(iterpool); - if (ctx->cancel_func) - SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); + cb.local_abspath = dir_abspath; + cb.must_keep = FALSE; + cb.targets_info = targets_info; - /* Rebuild the empty dirs list, replacing empty dirs which have - * an empty parent with their parent. */ - again = FALSE; - empty_dirs_copy = apr_hash_copy(iterpool, empty_dirs); - SVN_ERR(svn_hash__clear(empty_dirs, iterpool)); + err = svn_wc_walk_status(ctx->wc_ctx, dir_abspath, svn_depth_infinity, + TRUE, FALSE, FALSE, NULL, + can_delete_callback, &cb, + ctx->cancel_func, ctx->cancel_baton, + iterpool); - for (hi = apr_hash_first(iterpool, empty_dirs_copy); - hi; - hi = apr_hash_next(hi)) + if (err) { - svn_boolean_t parent_empty; - const char *empty_dir; - const char *parent; + if (err->apr_err != SVN_ERR_CEASE_INVOCATION) + return svn_error_trace(err); - empty_dir = svn__apr_hash_index_key(hi); - parent = svn_dirent_dirname(empty_dir, iterpool); + svn_error_clear(err); + } - if (apr_hash_get(empty_dirs, parent, APR_HASH_KEY_STRING)) - continue; + if (cb.must_keep) + { + break; + } - SVN_ERR(check_dir_empty(&parent_empty, parent, ctx->wc_ctx, - deleted_targets, empty_dirs_copy, - iterpool)); - if (parent_empty) - { - again = TRUE; - apr_hash_set(empty_dirs, apr_pstrdup(scratch_pool, parent), - APR_HASH_KEY_STRING, ""); - } - else - apr_hash_set(empty_dirs, apr_pstrdup(scratch_pool, empty_dir), - APR_HASH_KEY_STRING, ""); + if (! dry_run) + { + SVN_ERR(svn_wc_delete4(ctx->wc_ctx, dir_abspath, FALSE, FALSE, + ctx->cancel_func, ctx->cancel_baton, + NULL, NULL, + scratch_pool)); } - } - while (again); - /* Finally, delete empty directories. */ - for (hi = apr_hash_first(scratch_pool, empty_dirs); - hi; - hi = apr_hash_next(hi)) - { - const char *empty_dir; + { + patch_target_info_t *pti = apr_pcalloc(result_pool, sizeof(*pti)); - svn_pool_clear(iterpool); + pti->local_abspath = apr_pstrdup(result_pool, dir_abspath); + pti->deleted = TRUE; + + APR_ARRAY_PUSH(targets_info, patch_target_info_t *) = pti; + } - if (ctx->cancel_func) - SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); - empty_dir = svn__apr_hash_index_key(hi); - if (! dry_run) - SVN_ERR(svn_wc_delete4(ctx->wc_ctx, empty_dir, FALSE, FALSE, - ctx->cancel_func, ctx->cancel_baton, - NULL, NULL, /* no duplicate notification */ - iterpool)); if (ctx->notify_func2) { svn_wc_notify_t *notify; - notify = svn_wc_create_notify(empty_dir, svn_wc_notify_delete, - iterpool); - (*ctx->notify_func2)(ctx->notify_baton2, notify, iterpool); + notify = svn_wc_create_notify(dir_abspath, svn_wc_notify_delete, + iterpool); + notify->kind = svn_node_dir; + + ctx->notify_func2(ctx->notify_baton2, notify, iterpool); } + + /* And check if we must also delete the parent */ + dir_abspath = svn_dirent_dirname(dir_abspath, scratch_pool); } + svn_pool_destroy(iterpool); return SVN_NO_ERROR; } -/* Baton for apply_patches(). */ -typedef struct apply_patches_baton_t { - /* The path to the patch file. */ - const char *patch_abspath; - - /* The abspath to the working copy the patch should be applied to. */ - const char *abs_wc_path; - - /* Indicates whether we're doing a dry run. */ - svn_boolean_t dry_run; - - /* Number of leading components to strip from patch target paths. */ - int strip_count; - - /* Whether to apply the patch in reverse. */ - svn_boolean_t reverse; - - /* Indicates whether we should ignore whitespace when matching context - * lines */ - svn_boolean_t ignore_whitespace; - - /* As in svn_client_patch(). */ - svn_boolean_t remove_tempfiles; - - /* As in svn_client_patch(). */ - svn_client_patch_func_t patch_func; - void *patch_baton; - - /* The client context. */ - svn_client_ctx_t *ctx; -} apply_patches_baton_t; - -/* Callback for use with svn_wc__call_with_write_lock(). - * This function is the main entry point into the patch code. */ +/* This function is the main entry point into the patch code. */ static svn_error_t * -apply_patches(void *baton, - apr_pool_t *result_pool, +apply_patches(/* The path to the patch file. */ + const char *patch_abspath, + /* The abspath to the working copy the patch should be applied to. */ + const char *abs_wc_path, + /* Indicates whether we're doing a dry run. */ + svn_boolean_t dry_run, + /* Number of leading components to strip from patch target paths. */ + int strip_count, + /* Whether to apply the patch in reverse. */ + svn_boolean_t reverse, + /* Whether to ignore whitespace when matching context lines. */ + svn_boolean_t ignore_whitespace, + /* As in svn_client_patch(). */ + svn_boolean_t remove_tempfiles, + /* As in svn_client_patch(). */ + svn_client_patch_func_t patch_func, + void *patch_baton, + /* The client context. */ + svn_client_ctx_t *ctx, apr_pool_t *scratch_pool) { svn_patch_t *patch; apr_pool_t *iterpool; svn_patch_file_t *patch_file; apr_array_header_t *targets_info; - apply_patches_baton_t *btn = baton; /* Try to open the patch file. */ - SVN_ERR(svn_diff_open_patch_file(&patch_file, btn->patch_abspath, - scratch_pool)); + SVN_ERR(svn_diff_open_patch_file(&patch_file, patch_abspath, scratch_pool)); /* Apply patches. */ targets_info = apr_array_make(scratch_pool, 0, @@ -2868,29 +2977,27 @@ apply_patches(void *baton, { svn_pool_clear(iterpool); - if (btn->ctx->cancel_func) - SVN_ERR(btn->ctx->cancel_func(btn->ctx->cancel_baton)); + if (ctx->cancel_func) + SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file, - btn->reverse, btn->ignore_whitespace, + reverse, ignore_whitespace, iterpool, iterpool)); if (patch) { patch_target_t *target; - SVN_ERR(apply_one_patch(&target, patch, btn->abs_wc_path, - btn->ctx->wc_ctx, btn->strip_count, - btn->ignore_whitespace, - btn->remove_tempfiles, - btn->patch_func, btn->patch_baton, - btn->ctx->cancel_func, - btn->ctx->cancel_baton, + SVN_ERR(apply_one_patch(&target, patch, abs_wc_path, + ctx->wc_ctx, strip_count, + ignore_whitespace, remove_tempfiles, + patch_func, patch_baton, + ctx->cancel_func, ctx->cancel_baton, iterpool, iterpool)); if (! target->filtered) { /* Save info we'll still need when we're done patching. */ patch_target_info_t *target_info = - apr_palloc(scratch_pool, sizeof(patch_target_info_t)); + apr_pcalloc(scratch_pool, sizeof(patch_target_info_t)); target_info->local_abspath = apr_pstrdup(scratch_pool, target->local_abspath); target_info->deleted = target->deleted; @@ -2903,28 +3010,29 @@ apply_patches(void *baton, if (target->has_text_changes || target->added || target->deleted) - SVN_ERR(install_patched_target(target, btn->abs_wc_path, - btn->ctx, btn->dry_run, - iterpool)); + SVN_ERR(install_patched_target(target, abs_wc_path, + ctx, dry_run, iterpool)); if (target->has_prop_changes && (!target->deleted)) - SVN_ERR(install_patched_prop_targets(target, btn->ctx, - btn->dry_run, - iterpool)); + SVN_ERR(install_patched_prop_targets(target, ctx, + dry_run, iterpool)); - SVN_ERR(write_out_rejected_hunks(target, btn->dry_run, - iterpool)); + SVN_ERR(write_out_rejected_hunks(target, dry_run, iterpool)); + } + SVN_ERR(send_patch_notification(target, ctx, iterpool)); + + if (target->deleted && !target->skipped) + { + SVN_ERR(check_ancestor_delete(target_info->local_abspath, + targets_info, abs_wc_path, + dry_run, ctx, + scratch_pool, iterpool)); } - SVN_ERR(send_patch_notification(target, btn->ctx, iterpool)); } } } while (patch); - /* Delete directories which are empty after patching, if any. */ - SVN_ERR(delete_empty_dirs(targets_info, btn->ctx, btn->dry_run, - scratch_pool)); - SVN_ERR(svn_diff_close_patch_file(patch_file, iterpool)); svn_pool_destroy(iterpool); @@ -2944,7 +3052,6 @@ svn_client_patch(const char *patch_abspath, svn_client_ctx_t *ctx, apr_pool_t *scratch_pool) { - apply_patches_baton_t baton; svn_node_kind_t kind; if (strip_count < 0) @@ -2981,19 +3088,10 @@ svn_client_patch(const char *patch_abspath, svn_dirent_local_style(wc_dir_abspath, scratch_pool)); - baton.patch_abspath = patch_abspath; - baton.abs_wc_path = wc_dir_abspath; - baton.dry_run = dry_run; - baton.ctx = ctx; - baton.strip_count = strip_count; - baton.reverse = reverse; - baton.ignore_whitespace = ignore_whitespace; - baton.remove_tempfiles = remove_tempfiles; - baton.patch_func = patch_func; - baton.patch_baton = patch_baton; - - return svn_error_trace( - svn_wc__call_with_write_lock(apply_patches, &baton, - ctx->wc_ctx, wc_dir_abspath, FALSE, - scratch_pool, scratch_pool)); + SVN_WC__CALL_WITH_WRITE_LOCK( + apply_patches(patch_abspath, wc_dir_abspath, dry_run, strip_count, + reverse, ignore_whitespace, remove_tempfiles, + patch_func, patch_baton, ctx, scratch_pool), + ctx->wc_ctx, wc_dir_abspath, FALSE /* lock_anchor */, scratch_pool); + return SVN_NO_ERROR; } |