Index: subversion/libsvn_wc/log.c =================================================================== --- subversion/libsvn_wc/log.c (revision 27364) +++ subversion/libsvn_wc/log.c (working copy) @@ -570,7 +570,7 @@ log_do_merge(struct log_runner *loggy, /* Now do the merge with our full paths. */ err = svn_wc__merge_internal(&log_accum, &merge_outcome, - left, right, name, loggy->adm_access, + left, right, name, NULL, loggy->adm_access, left_label, right_label, target_label, FALSE, loggy->diff3_cmd, NULL, NULL, NULL, NULL, loggy->pool); Index: subversion/libsvn_wc/merge.c =================================================================== --- subversion/libsvn_wc/merge.c (revision 27364) +++ subversion/libsvn_wc/merge.c (working copy) @@ -274,6 +274,7 @@ svn_wc__merge_internal(svn_stringbuf_t * const char *left, const char *right, const char *merge_target, + const char *copyfrom_text, svn_wc_adm_access_t *adm_access, const char *left_label, const char *right_label, @@ -286,17 +287,18 @@ svn_wc__merge_internal(svn_stringbuf_t * void *conflict_baton, apr_pool_t *pool) { - const char *tmp_target, *result_target; + const char *tmp_target, *result_target, *working_text; const char *adm_path = svn_wc_adm_access_path(adm_access); apr_file_t *result_f; - svn_boolean_t is_binary; + svn_boolean_t is_binary = FALSE; const svn_wc_entry_t *entry; svn_boolean_t contains_conflicts; const svn_prop_t *mimeprop; - /* Sanity check: the merge target must be under revision control. */ + /* Sanity check: the merge target must be under revision control (unless + this is an add-with-history). */ SVN_ERR(svn_wc_entry(&entry, merge_target, adm_access, FALSE, pool)); - if (! entry) + if (! entry && ! copyfrom_text) { *merge_outcome = svn_wc_merge_no_merge; return SVN_NO_ERROR; @@ -306,10 +308,11 @@ svn_wc__merge_internal(svn_stringbuf_t * if ((mimeprop = get_prop(prop_diff, SVN_PROP_MIME_TYPE)) && mimeprop->value) is_binary = svn_mime_type_is_binary(mimeprop->value->data); - else + else if (! copyfrom_text) SVN_ERR(svn_wc_has_binary_prop(&is_binary, merge_target, adm_access, pool)); - SVN_ERR(detranslate_wc_file(&tmp_target, merge_target, adm_access, + working_text = copyfrom_text ? copyfrom_text : merge_target; + SVN_ERR(detranslate_wc_file(&tmp_target, working_text, adm_access, (! is_binary) && diff3_cmd != NULL, prop_diff, pool)); @@ -612,6 +615,10 @@ svn_wc__merge_internal(svn_stringbuf_t * { *merge_outcome = svn_wc_merge_conflict; } /* end of conflict handling */ + else if (copyfrom_text) + { + *merge_outcome = svn_wc_merge_merged; + } else { svn_boolean_t same; @@ -847,6 +854,7 @@ svn_wc_merge3(enum svn_wc_merge_outcome_ SVN_ERR(svn_wc__merge_internal(&log_accum, merge_outcome, left, right, merge_target, + NULL, adm_access, left_label, right_label, target_label, dry_run, Index: subversion/libsvn_wc/props.c =================================================================== --- subversion/libsvn_wc/props.c (revision 27364) +++ subversion/libsvn_wc/props.c (working copy) @@ -1121,7 +1121,7 @@ svn_wc_merge_props2(svn_wc_notify_state_ /* Note that while this routine does the "real" work, it's only prepping tempfiles and writing log commands. */ - SVN_ERR(svn_wc__merge_props(state, adm_access, path, baseprops, + SVN_ERR(svn_wc__merge_props(state, adm_access, path, baseprops, NULL, NULL, propchanges, base_merge, dry_run, conflict_func, conflict_baton, pool, &log_accum)); @@ -1803,6 +1803,8 @@ svn_wc__merge_props(svn_wc_notify_state_ svn_wc_adm_access_t *adm_access, const char *path, apr_hash_t *server_baseprops, + apr_hash_t *base_props, + apr_hash_t *working_props, const apr_array_header_t *propchanges, svn_boolean_t base_merge, svn_boolean_t dry_run, @@ -1814,9 +1816,6 @@ svn_wc__merge_props(svn_wc_notify_state_ int i; svn_boolean_t is_dir; - apr_hash_t *working_props; /* all `working' properties */ - apr_hash_t *base_props; /* all `pristine' properties */ - const char *reject_path = NULL; apr_file_t *reject_tmp_fp = NULL; /* the temporary conflicts file */ const char *reject_tmp_path = NULL; @@ -1826,9 +1825,11 @@ svn_wc__merge_props(svn_wc_notify_state_ else is_dir = FALSE; - /* Load the base & working property files into hashes */ - SVN_ERR(svn_wc__load_props(&base_props, &working_props, NULL, - adm_access, path, pool)); + /* If not provided, load the base & working property files into hashes */ + if (! base_props || ! working_props) + SVN_ERR(svn_wc__load_props(base_props ? NULL : &base_props, + working_props ? NULL : &working_props, + NULL, adm_access, path, pool)); if (!server_baseprops) server_baseprops = base_props; Index: subversion/libsvn_wc/props.h =================================================================== --- subversion/libsvn_wc/props.h (revision 27364) +++ subversion/libsvn_wc/props.h (working copy) @@ -50,6 +50,9 @@ svn_error_t *svn_wc__has_props(svn_boole SERVER_BASEPROPS, merge the changes into the working copy. Append all necessary log entries to ENTRY_ACCUM. + If BASE_PROPS or WORKING_PROPS is NULL, use the props from the + working copy. + If SERVER_BASEPROPS is NULL then use base props as PROPCHANGES base. @@ -68,6 +71,8 @@ svn_error_t *svn_wc__merge_props(svn_wc_ svn_wc_adm_access_t *adm_access, const char *path, apr_hash_t *server_baseprops, + apr_hash_t *base_props, + apr_hash_t *working_props, const apr_array_header_t *propchanges, svn_boolean_t base_merge, svn_boolean_t dry_run, Index: subversion/libsvn_wc/update_editor.c =================================================================== --- subversion/libsvn_wc/update_editor.c (revision 27364) +++ subversion/libsvn_wc/update_editor.c (working copy) @@ -741,6 +741,9 @@ struct file_baton /* Set if this file is new. */ svn_boolean_t added; + /* Set if this file is new with history. */ + svn_boolean_t added_with_history; + /* Set if this file is skipped because it was in conflict. */ svn_boolean_t skipped; @@ -752,10 +755,6 @@ struct file_baton scheduled for addition without history. */ svn_boolean_t add_existed; - /* Whether or not we should call the notification callback when - close_file() is invoked on this baton. */ - svn_boolean_t send_notification; - /* The path to the current text base, if any. This gets set if there are file content changes. */ const char *text_base_path; @@ -764,6 +763,26 @@ struct file_baton the code that syncs up the adm dir and working copy. */ const char *new_text_base_path; + /* If this file was added with history, this is the path to a copy + of the text base of the copyfrom file (in the temporary area). */ + const char *copied_text_base; + + /* If this file was added with history, and the copyfrom had local + mods, this is the path to a copy of the user's version with local + mods (in the temporary area). */ + const char *copied_working_text; + + /* If this file was added with history, this hash contains the base + properties of the copied file. */ + apr_hash_t *copied_base_props; + + /* If this file was added with history, this hash contains the working + properties of the copied file. */ + apr_hash_t *copied_working_props; + + /* Set if we've received an apply_textdelta for this file. */ + svn_boolean_t received_textdelta; + /* An array of svn_prop_t structures, representing all the property changes to be applied to this file. */ apr_array_header_t *propchanges; @@ -876,7 +895,6 @@ make_file_baton(struct file_baton **f_p, f->added = adding; f->existed = FALSE; f->add_existed = FALSE; - f->send_notification = TRUE; f->dir_baton = pb; f->ambient_depth = svn_depth_empty; @@ -909,7 +927,11 @@ window_handler(svn_txdelta_window_t *win case, clean up the handler. */ if (hb->source) { - err2 = svn_wc__close_text_base(hb->source, fb->path, 0, hb->pool); + if (fb->copied_text_base) + err2 = svn_io_file_close(hb->source, hb->pool); + else + err2 = svn_wc__close_text_base(hb->source, fb->path, 0, hb->pool); + if (err2 && !err) err = err2; else @@ -1797,6 +1819,7 @@ close_directory(void *dir_baton, SVN_ERR_W(svn_wc__merge_props(&prop_state, adm_access, db->path, NULL /* use baseprops */, + NULL, NULL, regular_props, TRUE, FALSE, db->edit_baton->conflict_func, db->edit_baton->conflict_baton, @@ -2108,6 +2131,59 @@ open_file(const char *path, return SVN_NO_ERROR; } +/* Fills out the text_base_path and new_text_base_path fields in + FILE_BATON (to text-base or revert-base); if CHECKSUM_P is non-NULL + and the path already has an entry, sets *CHECKSUM_P to the checksum + from its entry. If non-NULL, set *REPLACED_P and *USE_REVERT_BASE_P + to whether or not the entry is replaced and whether or not it needs + to use the revert base (ie, it is replaced with history) + respectively. */ +static svn_error_t * +choose_base_paths(const char **checksum_p, + svn_boolean_t *replaced_p, + svn_boolean_t *use_revert_base_p, + struct file_baton *fb, + apr_pool_t *pool) +{ + struct edit_baton *eb = fb->edit_baton; + svn_wc_adm_access_t *adm_access; + const svn_wc_entry_t *ent; + svn_boolean_t replaced, use_revert_base; + + SVN_ERR(svn_wc_adm_retrieve(&adm_access, eb->adm_access, + svn_path_dirname(fb->path, pool), pool)); + SVN_ERR(svn_wc_entry(&ent, fb->path, adm_access, FALSE, pool)); + + replaced = ent && ent->schedule == svn_wc_schedule_replace; + use_revert_base = replaced && (ent->copyfrom_url != NULL); + if (use_revert_base) + { + fb->text_base_path = svn_wc__text_revert_path(fb->path, FALSE, fb->pool); + fb->new_text_base_path = svn_wc__text_revert_path(fb->path, TRUE, + fb->pool); + } + else + { + fb->text_base_path = svn_wc__text_base_path(fb->path, FALSE, fb->pool); + fb->new_text_base_path = svn_wc__text_base_path(fb->path, TRUE, + fb->pool); + } + + if (checksum_p) + { + *checksum_p = NULL; + if (ent) + *checksum_p = ent->checksum; + } + if (replaced_p) + *replaced_p = replaced; + if (use_revert_base_p) + *use_revert_base_p = use_revert_base; + + return SVN_NO_ERROR; +} + + static svn_error_t * apply_textdelta(void *file_baton, @@ -2117,12 +2193,10 @@ apply_textdelta(void *file_baton, void **handler_baton) { struct file_baton *fb = file_baton; - struct edit_baton *eb = fb->edit_baton; apr_pool_t *handler_pool = svn_pool_create(fb->pool); struct handler_baton *hb = apr_palloc(handler_pool, sizeof(*hb)); svn_error_t *err; - svn_wc_adm_access_t *adm_access; - const svn_wc_entry_t *ent; + const char *checksum; svn_boolean_t replaced; svn_boolean_t use_revert_base; @@ -2133,34 +2207,20 @@ apply_textdelta(void *file_baton, return SVN_NO_ERROR; } + fb->received_textdelta = TRUE; + /* Before applying incoming svndiff data to text base, make sure text base hasn't been corrupted, and that its checksum matches the expected base checksum. */ - SVN_ERR(svn_wc_adm_retrieve(&adm_access, eb->adm_access, - svn_path_dirname(fb->path, pool), pool)); - SVN_ERR(svn_wc_entry(&ent, fb->path, adm_access, FALSE, pool)); - - replaced = ent && ent->schedule == svn_wc_schedule_replace; - use_revert_base = replaced && (ent->copyfrom_url != NULL); - if (use_revert_base) - { - fb->text_base_path = svn_wc__text_revert_path(fb->path, FALSE, fb->pool); - fb->new_text_base_path = svn_wc__text_revert_path(fb->path, TRUE, - fb->pool); - } - else - { - fb->text_base_path = svn_wc__text_base_path(fb->path, FALSE, fb->pool); - fb->new_text_base_path = svn_wc__text_base_path(fb->path, TRUE, - fb->pool); - } + SVN_ERR(choose_base_paths(&checksum, &replaced, &use_revert_base, + fb, pool)); /* Only compare checksums if this file has an entry, and the entry has a checksum. If there's no entry, it just means the file is created in this update, so there won't be any previously recorded checksum to compare against. If no checksum, well, for backwards compatibility we assume that no checksum always matches. */ - if (ent && ent->checksum) + if (checksum) { unsigned char digest[APR_MD5_DIGESTSIZE]; const char *hex_digest; @@ -2181,13 +2241,12 @@ apply_textdelta(void *file_baton, hex_digest); } - if ((ent && ent->checksum) && ! replaced && - strcmp(hex_digest, ent->checksum) != 0) + if (! replaced && strcmp(hex_digest, checksum) != 0) { return svn_error_createf (SVN_ERR_WC_CORRUPT_TEXT_BASE, NULL, _("Checksum mismatch for '%s'; recorded: '%s', actual: '%s'"), - svn_path_local_style(fb->text_base_path, pool), ent->checksum, + svn_path_local_style(fb->text_base_path, pool), checksum, hex_digest); } } @@ -2219,7 +2278,13 @@ apply_textdelta(void *file_baton, } else - hb->source = NULL; + { + if (fb->copied_text_base) + SVN_ERR(svn_io_file_open(&hb->source, fb->copied_text_base, + APR_READ, APR_OS_DEFAULT, handler_pool)); + else + hb->source = NULL; + } /* Open the text base for writing (this will get us a temporary file). */ @@ -2292,7 +2357,8 @@ change_file_prop(void *file_baton, properties as well as entryprops and wcprops. Update *PROP_STATE to reflect the result of the regular prop merge. Make *LOCK_STATE reflect the possible removal of a lock token from FILE_PATH's - entryprops. + entryprops. BASE_PROPS and WORKING_PROPS are hashes of the base and + working props of the file; if NULL they are read from the wc. CONFICT_FUNC/BATON is a callback which allows the client to possibly resolve a property conflict interactively. @@ -2306,6 +2372,8 @@ merge_props(svn_stringbuf_t *log_accum, svn_wc_adm_access_t *adm_access, const char *file_path, const apr_array_header_t *prop_changes, + apr_hash_t *base_props, + apr_hash_t *working_props, svn_wc_conflict_resolver_func_t conflict_func, void *conflict_baton, apr_pool_t *pool) @@ -2329,7 +2397,9 @@ merge_props(svn_stringbuf_t *log_accum, props. */ SVN_ERR(svn_wc__merge_props(prop_state, adm_access, file_path, - NULL /* use base props */, + NULL /* update, not merge */, + base_props, + working_props, regular_props, TRUE, FALSE, conflict_func, conflict_baton, pool, &log_accum)); @@ -2494,14 +2564,18 @@ merge_file(svn_wc_notify_state_t *conten which case we want the new entryprops to be in place. */ SVN_ERR(merge_props(log_accum, prop_state, lock_state, adm_access, fb->path, fb->propchanges, + fb->copied_base_props, fb->copied_working_props, eb->conflict_func, eb->conflict_baton, pool)); /* Has the user made local mods to the working file? Note that this compares to the current pristine file, which is different from fb->old_text_base_path if we have a replaced-with-history file. However, in the case we had an obstruction, we check against the - new text base. */ - if (! fb->existed) + new text base. (And if we're doing an add-with-history and we've already + saved a copy of a locally-modified file, then there certainly are mods.) */ + if (fb->copied_working_text) + is_locally_modified = TRUE; + else if (! fb->existed) SVN_ERR(svn_wc__text_modified_internal_p(&is_locally_modified, fb->path, FALSE, adm_access, FALSE, pool)); else if (fb->new_text_base_path) @@ -2568,9 +2642,10 @@ merge_file(svn_wc_notify_state_t *conten svn_node_kind_t wfile_kind = svn_node_unknown; SVN_ERR(svn_io_check_path(fb->path, &wfile_kind, pool)); - if (wfile_kind == svn_node_none) /* working file is missing?! */ + if (wfile_kind == svn_node_none && ! fb->added_with_history) { - /* Just copy the new text-base to the file. */ + /* working file is missing?! + Just copy the new text-base to the file. */ SVN_ERR(svn_wc__loggy_copy(&log_accum, NULL, adm_access, svn_wc__copy_translate, fb->new_text_base_path, @@ -2602,10 +2677,17 @@ merge_file(svn_wc_notify_state_t *conten /* Create strings representing the revisions of the old and new text-bases. */ - oldrev_str = apr_psprintf(pool, ".r%ld%s%s", - entry->revision, - *path_ext ? "." : "", - *path_ext ? path_ext : ""); + /* Either an old version, or an add-with-history */ + if (fb->added_with_history) + oldrev_str = apr_psprintf(pool, ".copied%s%s", + *path_ext ? "." : "", + *path_ext ? path_ext : ""); + else + oldrev_str = apr_psprintf(pool, ".r%ld%s%s", + entry->revision, + *path_ext ? "." : "", + *path_ext ? path_ext : ""); + newrev_str = apr_psprintf(pool, ".r%ld%s%s", *eb->target_revision, *path_ext ? "." : "", @@ -2622,6 +2704,8 @@ merge_file(svn_wc_notify_state_t *conten svn_io_file_del_none, pool)); } + else if (fb->copied_text_base) + merge_left = fb->copied_text_base; else merge_left = fb->text_base_path; @@ -2633,6 +2717,7 @@ merge_file(svn_wc_notify_state_t *conten merge_left, fb->new_text_base_path, fb->path, + fb->copied_working_text, adm_access, oldrev_str, newrev_str, mine_str, FALSE, eb->diff3_cmd, NULL, fb->propchanges, @@ -2642,6 +2727,12 @@ merge_file(svn_wc_notify_state_t *conten if (merge_left != fb->text_base_path) SVN_ERR(svn_wc__loggy_remove(&log_accum, adm_access, merge_left, pool)); + + /* And clean up add-with-history-related temp file too. */ + if (fb->copied_working_text) + SVN_ERR(svn_wc__loggy_remove(&log_accum, adm_access, + fb->copied_working_text, pool)); + } /* end: working file exists and has mods */ } /* end: working file has mods */ } /* end: "textual" merging process */ @@ -2731,6 +2822,13 @@ merge_file(svn_wc_notify_state_t *conten (&log_accum, adm_access, fb->path, pool)); } + /* Clean up add-with-history temp file. */ + if (fb->copied_text_base) + SVN_ERR(svn_wc__loggy_remove(&log_accum, adm_access, + fb->copied_text_base, + pool)); + + /* Set the returned content state. */ /* This is kind of interesting. Even if no new text was @@ -2776,6 +2874,24 @@ close_file(void *file_baton, return SVN_NO_ERROR; } + /* Was this an add-with-history, with no apply_textdelta? */ + if (fb->added_with_history && ! fb->received_textdelta) + { + assert(! fb->text_base_path && ! fb->new_text_base_path + && fb->copied_text_base); + + /* Set up the base paths like apply_textdelta does. */ + SVN_ERR(choose_base_paths(NULL, NULL, NULL, fb, pool)); + + /* Now simulate applying a trivial delta. */ + SVN_ERR(svn_io_copy_file(fb->copied_text_base, + fb->new_text_base_path, + TRUE, pool)); + SVN_ERR(svn_io_file_checksum(fb->digest, + fb->new_text_base_path, + pool)); + } + /* window-handler assembles new pristine text in .svn/tmp/text-base/ */ if (fb->new_text_base_path && text_checksum) { @@ -2796,8 +2912,7 @@ close_file(void *file_baton, if (((content_state != svn_wc_notify_state_unchanged) || (prop_state != svn_wc_notify_state_unchanged) || (lock_state != svn_wc_notify_lock_state_unchanged)) - && eb->notify_func - && fb->send_notification) + && eb->notify_func) { svn_wc_notify_t *notify; svn_wc_notify_action_t action = svn_wc_notify_update_update; @@ -3007,6 +3122,31 @@ locate_copyfrom(const char *copyfrom_pat } +static apr_hash_t * +copy_non_entry_props(apr_hash_t *props_in, + apr_pool_t *pool) +{ + apr_hash_t *props_out = apr_hash_make(pool); + apr_hash_index_t *hi; + + for (hi = apr_hash_first(pool, props_in); hi; hi = apr_hash_next(hi)) + { + const void *key; + void *val; + const char *propname; + svn_string_t *propval; + apr_hash_this(hi, &key, NULL, &val); + propname = key; + propval = val; + + if (svn_property_kind(NULL, propname) == svn_prop_entry_kind) + continue; + apr_hash_set(props_out, propname, APR_HASH_KEY_STRING, propval); + } + return props_out; +} + + /* Similar to add_file(), but not actually part of the editor vtable. Attempt to locate COPYFROM_PATH@COPYFROM_REV within the existing @@ -3032,17 +3172,19 @@ add_file_with_history(const char *path, struct edit_baton *eb = pb->edit_baton; svn_wc_adm_access_t *adm_access, *src_access; const char *src_path; - apr_hash_t *base_props; - apr_hash_index_t *hi; - apr_file_t *textbase_file; + apr_hash_t *base_props, *working_props; const svn_wc_entry_t *path_entry; svn_error_t *err; - /* First, fake an add_file() call, just to generate a temporary - file_baton that we can push data at. Notice that we don't send - any copyfrom args, lest we end up infinitely recursing. :-) */ + /* ### TODO: consider, a la add_file, doing temporary allocations + ### that don't need to stick around with the baton in a + ### subpool */ + + /* First, fake an add_file() call. Notice that we don't send any + copyfrom args, lest we end up infinitely recursing. :-) */ SVN_ERR(add_file(path, parent_baton, NULL, SVN_INVALID_REVNUM, pool, &fb)); tfb = (struct file_baton *)fb; + tfb->added_with_history = TRUE; if (tfb->ambient_depth == svn_depth_exclude) { @@ -3050,11 +3192,6 @@ add_file_with_history(const char *path, return SVN_NO_ERROR; } - /* Initialize the text-bases for the new file baton, the same way - apply_textdelta() does. */ - tfb->text_base_path = svn_wc__text_base_path(tfb->path, FALSE, tfb->pool); - tfb->new_text_base_path = svn_wc__text_base_path(tfb->path, TRUE, tfb->pool); - /* Attempt to locate the copyfrom_path in the working copy first. */ SVN_ERR(svn_wc_entry(&path_entry, pb->path, eb->adm_access, FALSE, pool)); err = locate_copyfrom(copyfrom_path, copyfrom_rev, @@ -3065,21 +3202,32 @@ add_file_with_history(const char *path, else if (err) return err; + SVN_ERR(svn_wc_adm_retrieve(&adm_access, pb->edit_baton->adm_access, + pb->path, pb->pool)); + /* Make a unique file name for the copyfrom text-base. */ + SVN_ERR(svn_wc_create_tmp_file2(NULL, &tfb->copied_text_base, + svn_wc_adm_access_path(adm_access), + svn_io_file_del_none, + pool)); + if (src_path != NULL) /* Found a file to copy */ { /* Copy the existing file's text-base over to the (temporary) new text-base, where the file baton expects it to be. */ const char *src_text_base_path = svn_wc__text_base_path(src_path, FALSE, pool); - SVN_ERR(svn_io_copy_file(src_text_base_path, tfb->new_text_base_path, + SVN_ERR(svn_io_copy_file(src_text_base_path, tfb->copied_text_base, TRUE, pool)); /* Grab the existing file's base-props into memory. */ - SVN_ERR(svn_wc__load_props(&base_props, NULL, NULL, + SVN_ERR(svn_wc__load_props(&base_props, &working_props, NULL, src_access, src_path, pool)); } else /* Couldn't find a file to copy */ { + apr_file_t *textbase_file; + svn_stream_t *textbase_stream; + /* Fall back to fetching it from the repository instead. */ if (! eb->fetch_func) @@ -3088,100 +3236,47 @@ add_file_with_history(const char *path, /* Fetch the repository file's text-base and base-props; svn_stream_close() automatically closes the text-base file for us. */ - SVN_ERR(svn_wc__open_text_base(&textbase_file, tfb->path, - (APR_WRITE | APR_TRUNCATE | APR_CREATE), - pool)); + SVN_ERR(svn_io_file_open(&textbase_file, tfb->copied_text_base, + (APR_WRITE | APR_TRUNCATE | APR_CREATE), + APR_OS_DEFAULT, pool)); + textbase_stream = svn_stream_from_aprfile2(textbase_file, FALSE, pool); /* copyfrom_path is a absolute path, fetch_func requires a path relative to the root of the repository so skip the first '/'. */ SVN_ERR(eb->fetch_func(eb->fetch_baton, copyfrom_path + 1, copyfrom_rev, - svn_stream_from_aprfile(textbase_file, pool), + textbase_stream, NULL, &base_props, pool)); + SVN_ERR(svn_stream_close(textbase_stream)); + working_props = base_props; } - /* Loop over whatever base-props we have in memory, faking change_file_prop() - calls against the file baton. */ - for (hi = apr_hash_first(pool, base_props); hi; hi = apr_hash_next(hi)) - { - const void *key; - void *val; - const char *propname; - svn_string_t *propval; - apr_hash_this(hi, &key, NULL, &val); - propname = key; - propval = val; - - if (svn_property_kind(NULL, propname) == svn_prop_entry_kind) - continue; - SVN_ERR(change_file_prop(tfb, propname, propval, pool)); - } - - /* Now 'install' the file baton in the usual loggy way. */ - SVN_ERR(close_file(tfb, NULL, pool)); - - /* Execute the parent-dir's logs, so that the file *actually* comes - into existence, rather than at close_directory() time. */ - SVN_ERR(svn_wc_adm_retrieve(&adm_access, pb->edit_baton->adm_access, - pb->path, pb->pool)); - SVN_ERR(flush_log(pb, pool)); - SVN_ERR(svn_wc__run_log(adm_access, pb->edit_baton->diff3_cmd, pb->pool)); - pb->log_number = 0; + /* Loop over whatever props we have in memory, and add any + non-entry-specific props to hashes in the baton. */ + tfb->copied_base_props = copy_non_entry_props(base_props, pool); + tfb->copied_working_props = copy_non_entry_props(working_props, pool); if (src_path != NULL) { /* If we copied an existing file over, we need copy its working text and props too, to preserve any local mods. */ - svn_boolean_t text_changed, props_changed; + svn_boolean_t text_changed; SVN_ERR(svn_wc_text_modified_p(&text_changed, src_path, FALSE, src_access, pool)); - SVN_ERR(svn_wc_props_modified_p(&props_changed, src_path, - src_access, pool)); if (text_changed) - SVN_ERR(svn_io_copy_file(src_path, tfb->path, TRUE, pool)); - - if (props_changed) { - /* Apparently just copying the working-props file over - isn't a guaranteed-correct way to migrate the local - propchanges. So we'll do it programmatically. */ - int i; - apr_array_header_t *propchanges; - apr_pool_t *iterpool = svn_pool_create(pool); - - SVN_ERR(svn_wc_get_prop_diffs(&propchanges, NULL, src_path, - src_access, pool)); - for (i = 0; i < propchanges->nelts; i++) - { - const svn_prop_t *change = &APR_ARRAY_IDX(propchanges, - i, svn_prop_t); - SVN_ERR(svn_wc_prop_set2(change->name, change->value, - tfb->path, adm_access, FALSE, iterpool)); - svn_pool_clear(iterpool); - } - } - } - - /* At this point we've successfully simulated the normal addition of - a file. However, any forthcoming apply_textdelta() calls from - the server are deltas against this new file. This means the - editor-driver needs a file_baton that can be safely passed to - apply_textdelta(), which means re-opening the file. */ - SVN_ERR(open_file(path, parent_baton, SVN_INVALID_REVNUM, pool, &fb)); - tfb = (struct file_baton *)fb; + /* Make a unique file name for the copied_working_text. */ + SVN_ERR(svn_wc_create_tmp_file2(NULL, &tfb->copied_working_text, + svn_wc_adm_access_path(adm_access), + svn_io_file_del_none, + pool)); - if (tfb->ambient_depth == svn_depth_exclude) - { - *file_baton = tfb; - return SVN_NO_ERROR; + SVN_ERR(svn_io_copy_file(src_path, tfb->copied_working_text, TRUE, + pool)); + } } - /* We don't want this trailing open_file()/close_file() combo to be - signaled to the client's output, however. The general policy is - to call the notification callback only -once- per file. */ - tfb->send_notification = FALSE; - *file_baton = tfb; return SVN_NO_ERROR; } Index: subversion/libsvn_wc/wc.h =================================================================== --- subversion/libsvn_wc/wc.h (revision 27364) +++ subversion/libsvn_wc/wc.h (working copy) @@ -223,6 +223,10 @@ svn_wc__text_modified_internal_p(svn_boo /* Merge the difference between LEFT and RIGHT into MERGE_TARGET, accumulating instructions to update the working copy into LOG_ACCUM. + If COPYFROM_TEXT is not NULL, the "local mods" text should be taken + from the path named their instead of MERGE_TARGET (but the merge + should still be installed into MERGE_TARGET). + The merge result is stored in *MERGE_OUTCOME and merge conflicts are marked in MERGE_RESULT using LEFT_LABEL, RIGHT_LABEL and TARGET_LABEL. @@ -249,6 +253,7 @@ svn_wc__merge_internal(svn_stringbuf_t * const char *left, const char *right, const char *merge_target, + const char *copyfrom_text, svn_wc_adm_access_t *adm_access, const char *left_label, const char *right_label,