Index: subversion/libsvn_fs_fs/tree.c =================================================================== --- subversion/libsvn_fs_fs/tree.c (revision 20434) +++ subversion/libsvn_fs_fs/tree.c (working copy) @@ -1458,9 +1458,13 @@ directly. */ svn_fs_txn_t *txn; + /* For file nodes under '/' we receive path as just the file names + * without a '/' at the begining. This happens for single file merges, + * so canonicalize the path to canon_path */ + const char *canon_path = svn_fs_fs__canonicalize_abspath(path, pool); SVN_ERR(svn_fs_open_txn(&txn, root->fs, txn_id, pool)); - SVN_ERR(svn_fs_fs__change_txn_mergeinfo(txn, path, value, pool)); + SVN_ERR(svn_fs_fs__change_txn_mergeinfo(txn, canon_path, value, pool)); SVN_ERR(svn_fs_fs__change_txn_prop(txn, SVN_FS_PROP_TXN_CONTAINS_MERGEINFO, Index: subversion/libsvn_client/diff.c =================================================================== --- subversion/libsvn_client/diff.c (revision 20434) +++ subversion/libsvn_client/diff.c (working copy) @@ -44,6 +44,17 @@ #include "svn_private_config.h" +/* Sanity check -- ensure that we have valid revisions to look at. */ +#define ENSURE_VALID_REVISION_KINDS(rev1_kind, rev2_kind) \ + if ((rev1_kind == svn_opt_revision_unspecified) \ + || (rev2_kind == svn_opt_revision_unspecified)) \ + { \ + return svn_error_create \ + (SVN_ERR_CLIENT_BAD_REVISION, NULL, \ + _("Not all required revisions are specified")); \ + } + + /* * Constant separator strings */ @@ -1803,14 +1814,8 @@ svn_opt_revision_t *revision1, *revision2; int i; - /* Sanity check -- ensure that we have valid revisions to look at. */ - if ((initial_revision1->kind == svn_opt_revision_unspecified) - || (initial_revision2->kind == svn_opt_revision_unspecified)) - { - return svn_error_create - (SVN_ERR_CLIENT_BAD_REVISION, NULL, - _("Not all required revisions are specified")); - } + ENSURE_VALID_REVISION_KINDS(initial_revision1->kind, + initial_revision2->kind); /* If we are performing a pegged merge, we need to find out what our actual URLs will be. */ @@ -1947,28 +1952,21 @@ in POOL. */ static svn_error_t * single_file_merge_get_file(const char **filename, + svn_ra_session_t *ra_session, apr_hash_t **props, - svn_revnum_t *rev, + svn_revnum_t rev, const char *url, - const char *path, - const svn_opt_revision_t *revision, struct merge_cmd_baton *merge_b, apr_pool_t *pool) { - svn_ra_session_t *ra_session; apr_file_t *fp; svn_stream_t *stream; - SVN_ERR(svn_client__open_ra_session_internal(&ra_session, url, NULL, - NULL, NULL, FALSE, TRUE, - merge_b->ctx, pool)); - SVN_ERR(svn_client__get_revision_number(rev, ra_session, revision, - path, pool)); SVN_ERR(svn_io_open_unique_file2(&fp, filename, merge_b->target, ".tmp", svn_io_file_del_none, pool)); stream = svn_stream_from_aprfile2(fp, FALSE, pool); - SVN_ERR(svn_ra_get_file(ra_session, "", *rev, + SVN_ERR(svn_ra_get_file(ra_session, "", rev, stream, NULL, props, pool)); SVN_ERR(svn_stream_close(stream)); @@ -1977,7 +1975,6 @@ /* The single-file, simplified version of do_merge. */ -/* ### FIXME: To handle merge tracking, follow pattern from do_merge(). */ static svn_error_t * do_single_file_merge(const char *initial_URL1, const char *initial_path1, @@ -1993,16 +1990,26 @@ { apr_hash_t *props1, *props2; const char *tmpfile1, *tmpfile2; - svn_revnum_t rev1, rev2; const char *mimetype1, *mimetype2; svn_string_t *pval; apr_array_header_t *propchanges; svn_wc_notify_state_t prop_state = svn_wc_notify_state_unknown; svn_wc_notify_state_t text_state = svn_wc_notify_state_unknown; - const char *URL1, *path1, *URL2, *path2; + svn_client_ctx_t *ctx = merge_b->ctx; + const char *URL1, *path1, *URL2, *path2, *rel_path; svn_opt_revision_t *revision1, *revision2; svn_error_t *err; + svn_merge_range_t range; + svn_ra_session_t *ra_session1, *ra_session2; + svn_boolean_t is_revert; + apr_hash_t *target_mergeinfo; + apr_array_header_t *remaining_ranges; + svn_boolean_t from_same_repos; + int i; + ENSURE_VALID_REVISION_KINDS(initial_revision1->kind, + initial_revision2->kind); + /* If we are performing a pegged merge, we need to find out what our actual URLs will be. */ if (peg_revision->kind != svn_opt_revision_unspecified) @@ -2015,7 +2022,7 @@ peg_revision, initial_revision1, initial_revision2, - merge_b->ctx, pool)); + ctx, pool)); merge_b->url = URL2; merge_b->path = NULL; @@ -2033,60 +2040,126 @@ revision2 = apr_pcalloc(pool, sizeof(*revision2)); *revision2 = *initial_revision2; } - - /* ### heh, funny. we could be fetching two fulltexts from two - *totally* different repositories here. :-) */ - SVN_ERR(single_file_merge_get_file(&tmpfile1, &props1, &rev1, - URL1, path1, revision1, - merge_b, pool)); - SVN_ERR(single_file_merge_get_file(&tmpfile2, &props2, &rev2, - URL2, path2, revision2, - merge_b, pool)); + { + const char *uuid1, *uuid2; + SVN_ERR(svn_client_uuid_from_url(&uuid1, URL1, ctx, pool)); + SVN_ERR(svn_client_uuid_from_url(&uuid2, URL2, ctx, pool)); + from_same_repos = (strcmp(uuid1, uuid2) == 0); + } - /* Discover any svn:mime-type values in the proplists */ - pval = apr_hash_get(props1, SVN_PROP_MIME_TYPE, strlen(SVN_PROP_MIME_TYPE)); - mimetype1 = pval ? pval->data : NULL; + /* Establish RA session to URL1. */ + SVN_ERR(svn_client__open_ra_session_internal(&ra_session1, URL1, NULL, + NULL, NULL, FALSE, TRUE, + ctx, pool)); + SVN_ERR(svn_client__open_ra_session_internal(&ra_session2, URL2, NULL, + NULL, NULL, FALSE, TRUE, + ctx, pool)); - pval = apr_hash_get(props2, SVN_PROP_MIME_TYPE, strlen(SVN_PROP_MIME_TYPE)); - mimetype2 = pval ? pval->data : NULL; + /* Resolve the revision numbers, and store them as a merge range. + Note that the "start" of a merge range is inclusive. */ + SVN_ERR(svn_client__get_revision_number + (&range.start, ra_session1, revision1, path1, pool)); + SVN_ERR(svn_client__get_revision_number + (&range.end, ra_session2, revision2, path2, pool)); + if (range.start == range.end) + /* No merge to perform. */ + return SVN_NO_ERROR; + is_revert = (range.start > range.end); + if (is_revert) + range.end += 1; + else + range.start += 1; - /* Deduce property diffs. */ - SVN_ERR(svn_prop_diffs(&propchanges, props2, props1, pool)); + if (from_same_repos) + { + /* Look at the merge info prop of the WC target to see what's + already been merged into it. */ + SVN_ERR(parse_merge_info(&target_mergeinfo, target_wcpath, adm_access, ctx, + pool)); - SVN_ERR(merge_file_changed(adm_access, - &text_state, &prop_state, - merge_b->target, - tmpfile1, - tmpfile2, - rev1, - rev2, - mimetype1, mimetype2, - propchanges, props1, - merge_b)); + SVN_ERR(svn_client__path_relative_to_root(&rel_path, URL1, NULL, + ra_session1, adm_access, pool)); + SVN_ERR(calculate_merge_ranges(&remaining_ranges, rel_path, target_mergeinfo, + &range, is_revert, pool)); + } + else + { + /* Files come from different repositories. */ + remaining_ranges = apr_array_make(pool, 1, sizeof(&range)); + APR_ARRAY_PUSH(remaining_ranges, svn_merge_range_t *) = ⦥ + } - /* Ignore if temporary file not found. It may have been renamed. */ - err = svn_io_remove_file(tmpfile1, pool); - if (err && ! APR_STATUS_IS_ENOENT(err->apr_err)) - return err; - svn_error_clear(err); - err = svn_io_remove_file(tmpfile2, pool); - if (err && ! APR_STATUS_IS_ENOENT(err->apr_err)) - return err; - svn_error_clear(err); + /* ### FIXME: Handle notification callbacks for multiple merges into + ### a single versioned resource. */ + for (i = 0; i < remaining_ranges->nelts; i++) + { + /* When using this merge range, account for the exclusivity of + its low value (which is indicated by this operation being a + merge vs. revert). */ + svn_merge_range_t *r = APR_ARRAY_IDX(remaining_ranges, i, + svn_merge_range_t *); + + /* While we currently don't allow it, in theory we could be + fetching two fulltexts from two different repositories here. */ + SVN_ERR(single_file_merge_get_file(&tmpfile1, ra_session1, &props1, + is_revert ? r->start : r->start - 1, + URL1, merge_b, pool)); + + SVN_ERR(single_file_merge_get_file(&tmpfile2, ra_session2, &props2, + is_revert ? r->end - 1 : r->end, + URL2, merge_b, pool)); + + /* Discover any svn:mime-type values in the proplists */ + pval = apr_hash_get(props1, SVN_PROP_MIME_TYPE, + strlen(SVN_PROP_MIME_TYPE)); + mimetype1 = pval ? pval->data : NULL; + + pval = apr_hash_get(props2, SVN_PROP_MIME_TYPE, + strlen(SVN_PROP_MIME_TYPE)); + mimetype2 = pval ? pval->data : NULL; + + /* Deduce property diffs. */ + SVN_ERR(svn_prop_diffs(&propchanges, props2, props1, pool)); + + SVN_ERR(merge_file_changed(adm_access, + &text_state, &prop_state, + merge_b->target, + tmpfile1, + tmpfile2, + is_revert ? r->start : r->start - 1, + is_revert ? r->end - 1 : r->end, + mimetype1, mimetype2, + propchanges, props1, + merge_b)); + + /* Ignore if temporary file not found. It may have been renamed. */ + err = svn_io_remove_file(tmpfile1, pool); + if (err && ! APR_STATUS_IS_ENOENT(err->apr_err)) + return err; + svn_error_clear(err); + err = svn_io_remove_file(tmpfile2, pool); + if (err && ! APR_STATUS_IS_ENOENT(err->apr_err)) + return err; + svn_error_clear(err); - if (merge_b->ctx->notify_func2) - { - svn_wc_notify_t *notify - = svn_wc_create_notify(merge_b->target, svn_wc_notify_update_update, - pool); - notify->kind = svn_node_file; - notify->content_state = text_state; - notify->prop_state = prop_state; - (*merge_b->ctx->notify_func2)(merge_b->ctx->notify_baton2, notify, - pool); + if (ctx->notify_func2) + { + svn_wc_notify_t *notify + = svn_wc_create_notify(merge_b->target, svn_wc_notify_update_update, + pool); + notify->kind = svn_node_file; + notify->content_state = text_state; + notify->prop_state = prop_state; + (*ctx->notify_func2)(ctx->notify_baton2, notify, pool); + } } + if (!merge_b->dry_run && from_same_repos && remaining_ranges->nelts > 0) + SVN_ERR(update_wc_merge_info(target_wcpath, target_mergeinfo, rel_path, + remaining_ranges, is_revert, adm_access, + pool)); + return SVN_NO_ERROR; }