Modified: subversion/branches/reuse-ra-session/subversion/include/svn_props.h URL: http://svn.apache.org/viewvc/subversion/branches/reuse-ra-session/subversion/include/svn_props.h?rev=1662177&r1=1662176&r2=1662177&view=diff ============================================================================== --- subversion/branches/reuse-ra-session/subversion/include/svn_props.h (original) +++ subversion/branches/reuse-ra-session/subversion/include/svn_props.h Wed Feb 25 08:15:39 2015 @@ -440,13 +440,20 @@ svn_prop_name_is_valid(const char *prop_ * @verbatim /trunk: 1-6,9,37-38 /trunk/foo: 10 @endverbatim + * @since New in 1.5. */ #define SVN_PROP_MERGEINFO SVN_PROP_PREFIX "mergeinfo" -/** Property used to record inheritable configuration auto-props. */ +/** Property used to record inheritable configuration auto-props. + * + * @since New in 1.8. + */ #define SVN_PROP_INHERITABLE_AUTO_PROPS SVN_PROP_PREFIX "auto-props" -/** Property used to record inheritable configuration ignores. */ +/** Property used to record inheritable configuration ignores. + * + * @since New in 1.8. + */ #define SVN_PROP_INHERITABLE_IGNORES SVN_PROP_PREFIX "global-ignores" /** Meta-data properties.
Modified: subversion/branches/reuse-ra-session/subversion/include/svn_ra.h URL: http://svn.apache.org/viewvc/subversion/branches/reuse-ra-session/subversion/include/svn_ra.h?rev=1662177&r1=1662176&r2=1662177&view=diff ============================================================================== --- subversion/branches/reuse-ra-session/subversion/include/svn_ra.h (original) +++ subversion/branches/reuse-ra-session/subversion/include/svn_ra.h Wed Feb 25 08:15:39 2015 @@ -289,7 +289,7 @@ typedef svn_boolean_t (*svn_ra_check_tun * This function will be called when the pool that owns the tunnel * connection is cleared or destroyed. * - * @a tunnel_context is the baton as returned from the + * @a tunnel_context is the baton as returned from the * svn_ra_open_tunnel_func_t. * * @a tunnel_baton was returned by the open-tunnel callback. Modified: subversion/branches/reuse-ra-session/subversion/include/svn_repos.h URL: http://svn.apache.org/viewvc/subversion/branches/reuse-ra-session/subversion/include/svn_repos.h?rev=1662177&r1=1662176&r2=1662177&view=diff ============================================================================== --- subversion/branches/reuse-ra-session/subversion/include/svn_repos.h (original) +++ subversion/branches/reuse-ra-session/subversion/include/svn_repos.h Wed Feb 25 08:15:39 2015 @@ -1522,7 +1522,8 @@ svn_repos_replay(svn_fs_root_t *root, * If @a commit_callback is non-NULL, then before @c close_edit returns (but * after the commit has succeeded) @c close_edit will invoke * @a commit_callback with a filled-in #svn_commit_info_t *, @a commit_baton, - * and @a pool or some subpool thereof as arguments. If @a commit_callback + * and @a pool or some subpool thereof as arguments. The @c repos_root field + * of the #svn_commit_info_t is null. If @a commit_callback * returns an error, that error will be returned from @c close_edit, * otherwise if there was a post-commit hook failure, then that error * will be returned with code SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED. @@ -1534,7 +1535,7 @@ svn_repos_replay(svn_fs_root_t *root, * NULL). Callers who supply their own transactions are responsible * for cleaning them up (either by committing them, or aborting them). * - * @since New in 1.5. + * @since New in 1.5. Since 1.6, @a commit_callback can be null. * * @note Yes, @a repos_url_decoded is a <em>decoded</em> URL. We realize * that's sorta wonky. Sorry about that. @@ -3749,7 +3750,7 @@ svn_repos_authz_check_access(svn_authz_t typedef enum svn_repos_revision_access_level_t { /** no access allowed to the revision properties and all changed-paths - * information. */ + * information. */ svn_repos_revision_access_none, /** access granted to some (svn:date and svn:author) revision properties and * changed-paths information on paths the read has access to. */ Modified: subversion/branches/reuse-ra-session/subversion/include/svn_string.h URL: http://svn.apache.org/viewvc/subversion/branches/reuse-ra-session/subversion/include/svn_string.h?rev=1662177&r1=1662176&r2=1662177&view=diff ============================================================================== --- subversion/branches/reuse-ra-session/subversion/include/svn_string.h (original) +++ subversion/branches/reuse-ra-session/subversion/include/svn_string.h Wed Feb 25 08:15:39 2015 @@ -443,7 +443,7 @@ svn_string_compare_stringbuf(const svn_s */ /** Divide @a input into substrings, interpreting any char from @a sep - * as a token separator. + * as a token separator. * * Return an array of copies of those substrings (plain const char*), * allocating both the array and the copies in @a pool. Modified: subversion/branches/reuse-ra-session/subversion/include/svn_version.h URL: http://svn.apache.org/viewvc/subversion/branches/reuse-ra-session/subversion/include/svn_version.h?rev=1662177&r1=1662176&r2=1662177&view=diff ============================================================================== --- subversion/branches/reuse-ra-session/subversion/include/svn_version.h (original) +++ subversion/branches/reuse-ra-session/subversion/include/svn_version.h Wed Feb 25 08:15:39 2015 @@ -61,7 +61,7 @@ extern "C" { * Modify when new functionality is added or new interfaces are * defined, but all changes are backward compatible. */ -#define SVN_VER_MINOR 9 +#define SVN_VER_MINOR 10 /** * Patch number. Modified: subversion/branches/reuse-ra-session/subversion/include/svn_wc.h URL: http://svn.apache.org/viewvc/subversion/branches/reuse-ra-session/subversion/include/svn_wc.h?rev=1662177&r1=1662176&r2=1662177&view=diff ============================================================================== --- subversion/branches/reuse-ra-session/subversion/include/svn_wc.h (original) +++ subversion/branches/reuse-ra-session/subversion/include/svn_wc.h Wed Feb 25 08:15:39 2015 @@ -2185,6 +2185,14 @@ typedef struct svn_wc_conflict_result_t NULL) in the user's working copy. */ svn_boolean_t save_merged; + /** If not NULL, this is the new merged property, used when choosing + * #svn_wc_conflict_choose_merged. This value is prefered over using + * merged_file. + * + * @since New in 1.9. + */ + const svn_string_t *merged_value; + } svn_wc_conflict_result_t; @@ -2194,7 +2202,8 @@ typedef struct svn_wc_conflict_result_t * * Set the @c choice field of the structure to @a choice, @c merged_file * to @a merged_file, and @c save_merged to false. Make only a shallow - * copy of the pointer argument @a merged_file. + * copy of the pointer argument @a merged_file. @a merged_file may be + * NULL if setting merged_file is not needed. * * @since New in 1.5. */ @@ -3826,6 +3835,13 @@ typedef struct svn_wc_status3_t * @since New in 1.8. */ svn_boolean_t file_external; + + /** The actual kind of the node in the working copy. May differ from kind + * on obstructions, deletes, etc. svn_node_unknown if unavailable. + * + * @since New in 1.9 */ + svn_node_kind_t actual_kind; + /* NOTE! Please update svn_wc_dup_status3() when adding new fields here. */ } svn_wc_status3_t; Modified: subversion/branches/reuse-ra-session/subversion/libsvn_client/add.c URL: http://svn.apache.org/viewvc/subversion/branches/reuse-ra-session/subversion/libsvn_client/add.c?rev=1662177&r1=1662176&r2=1662177&view=diff ============================================================================== --- subversion/branches/reuse-ra-session/subversion/libsvn_client/add.c (original) +++ subversion/branches/reuse-ra-session/subversion/libsvn_client/add.c Wed Feb 25 08:15:39 2015 @@ -768,59 +768,6 @@ svn_client__get_all_auto_props(apr_hash_ return SVN_NO_ERROR; } -svn_error_t *svn_client__get_inherited_ignores(apr_array_header_t **ignores, - const char *path_or_url, - svn_client_ctx_t *ctx, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - svn_opt_revision_t rev; - apr_hash_t *explicit_ignores; - apr_array_header_t *inherited_ignores; - svn_boolean_t target_is_url = svn_path_is_url(path_or_url); - svn_string_t *explicit_prop; - int i; - - if (target_is_url) - rev.kind = svn_opt_revision_head; - else - rev.kind = svn_opt_revision_working; - - SVN_ERR(svn_client_propget5(&explicit_ignores, &inherited_ignores, - SVN_PROP_INHERITABLE_IGNORES, path_or_url, - &rev, &rev, NULL, svn_depth_empty, NULL, ctx, - scratch_pool, scratch_pool)); - - explicit_prop = svn_hash_gets(explicit_ignores, path_or_url); - - if (explicit_prop) - { - svn_prop_inherited_item_t *new_iprop = - apr_palloc(scratch_pool, sizeof(*new_iprop)); - new_iprop->path_or_url = path_or_url; - new_iprop->prop_hash = apr_hash_make(scratch_pool); - svn_hash_sets(new_iprop->prop_hash, SVN_PROP_INHERITABLE_IGNORES, - explicit_prop); - APR_ARRAY_PUSH(inherited_ignores, - svn_prop_inherited_item_t *) = new_iprop; - } - - *ignores = apr_array_make(result_pool, 16, sizeof(const char *)); - - for (i = 0; i < inherited_ignores->nelts; i++) - { - svn_prop_inherited_item_t *elt = APR_ARRAY_IDX( - inherited_ignores, i, svn_prop_inherited_item_t *); - svn_string_t *ignore_val = svn_hash_gets(elt->prop_hash, - SVN_PROP_INHERITABLE_IGNORES); - if (ignore_val) - svn_cstring_split_append(*ignores, ignore_val->data, "\n\r\t\v ", - FALSE, result_pool); - } - - return SVN_NO_ERROR; -} - /* The main logic of the public svn_client_add5. * * EXISTING_PARENT_ABSPATH is the absolute path to the first existing Modified: subversion/branches/reuse-ra-session/subversion/libsvn_client/blame.c URL: http://svn.apache.org/viewvc/subversion/branches/reuse-ra-session/subversion/libsvn_client/blame.c?rev=1662177&r1=1662176&r2=1662177&view=diff ============================================================================== --- subversion/branches/reuse-ra-session/subversion/libsvn_client/blame.c (original) +++ subversion/branches/reuse-ra-session/subversion/libsvn_client/blame.c Wed Feb 25 08:15:39 2015 @@ -34,6 +34,7 @@ #include "svn_dirent_uri.h" #include "svn_path.h" #include "svn_props.h" +#include "svn_hash.h" #include "svn_sorts.h" #include "private/svn_wc_private.h" @@ -76,6 +77,7 @@ struct diff_baton { /* The baton used for a file revision. Lives the entire operation */ struct file_rev_baton { svn_revnum_t start_rev, end_rev; + svn_boolean_t backwards; const char *target; svn_client_ctx_t *ctx; const svn_diff_file_options_t *diff_options; @@ -96,6 +98,14 @@ struct file_rev_baton { /* pools for files which may need to persist for more than one rev. */ apr_pool_t *filepool; apr_pool_t *prevfilepool; + + svn_boolean_t check_mime_type; + + /* When blaming backwards we have to use the changes + on the *next* revision, as the interesting change + happens when we move to the previous revision */ + svn_revnum_t last_revnum; + apr_hash_t *last_props; }; /* The baton used by the txdelta window handler. Allocated per revision */ @@ -431,6 +441,26 @@ file_rev_handler(void *baton, const char /* Clear the current pool. */ svn_pool_clear(frb->currpool); + if (frb->check_mime_type) + { + apr_hash_t *props = svn_prop_array_to_hash(prop_diffs, frb->currpool); + const char *value; + + frb->check_mime_type = FALSE; /* Only check first */ + + value = svn_prop_get_value(props, SVN_PROP_MIME_TYPE); + + if (value && svn_mime_type_is_binary(value)) + { + return svn_error_createf( + SVN_ERR_CLIENT_IS_BINARY_FILE, NULL, + _("Cannot calculate blame information for binary file '%s'"), + (svn_path_is_url(frb->target) + ? frb->target + : svn_dirent_local_style(frb->target, pool))); + } + } + if (frb->ctx->notify_func2) { svn_wc_notify_t *notify @@ -489,18 +519,24 @@ file_rev_handler(void *baton, const char /* Create the rev structure. */ delta_baton->rev = apr_pcalloc(frb->mainpool, sizeof(struct rev)); - if (revnum < MIN(frb->start_rev, frb->end_rev)) + if (frb->backwards) { - /* We shouldn't get more than one revision before the starting - revision (unless of including merged revisions). */ - SVN_ERR_ASSERT((frb->last_filename == NULL) - || frb->include_merged_revisions); + /* Use from last round... + SVN_INVALID_REVNUM on first, which is exactly + what we want */ + delta_baton->rev->revision = frb->last_revnum; + delta_baton->rev->rev_props = frb->last_props; - /* The file existed before start_rev; generate no blame info for - lines from this revision (or before). */ - delta_baton->rev->revision = SVN_INVALID_REVNUM; + /* Store for next delta */ + if (revnum >= MIN(frb->start_rev, frb->end_rev)) + { + frb->last_revnum = revnum; + frb->last_props = svn_prop_hash_dup(rev_props, frb->mainpool); + } + /* Else: Not needed on last rev */ } - else + else if (merged_revision + || (revnum >= MIN(frb->start_rev, frb->end_rev))) { /* 1+ for the "youngest to oldest" blame */ SVN_ERR_ASSERT(revnum <= 1 + MAX(frb->end_rev, frb->start_rev)); @@ -509,6 +545,20 @@ file_rev_handler(void *baton, const char delta_baton->rev->revision = revnum; delta_baton->rev->rev_props = svn_prop_hash_dup(rev_props, frb->mainpool); } + else + { + /* We shouldn't get more than one revision outside the + specified range (unless we alsoe receive merged revisions) */ + SVN_ERR_ASSERT((frb->last_filename == NULL) + || frb->include_merged_revisions); + + /* The file existed before start_rev; generate no blame info for + lines from this revision (or before). + + This revision specifies the state as it was at the start revision */ + + delta_baton->rev->revision = SVN_INVALID_REVNUM; + } if (frb->include_merged_revisions) delta_baton->rev->path = apr_pstrdup(frb->mainpool, path); @@ -626,7 +676,6 @@ svn_client_blame5(const char *target, svn_stream_t *last_stream; svn_stream_t *stream; const char *target_abspath_or_url; - svn_revnum_t youngest; if (start->kind == svn_opt_revision_unspecified || end->kind == svn_opt_revision_unspecified) @@ -662,35 +711,54 @@ svn_client_blame5(const char *target, target, peg_revision, &younger_end, ctx, pool)); - + /* Make the session point to the real URL. */ SVN_ERR(svn_ra_reparent(ra_session, loc->url, pool)); } /* We check the mime-type of the yougest revision before getting all the older revisions. */ - if (!ignore_mime_type) + if (!ignore_mime_type + && start_revnum < end_revnum) { apr_hash_t *props; - apr_hash_index_t *hi; + const char *mime_type = NULL; + + if (svn_path_is_url(target) + || start_revnum > end_revnum + || (end->kind != svn_opt_revision_working + && end->kind != svn_opt_revision_base)) + { + SVN_ERR(svn_ra_get_file(ra_session, "", end_revnum, NULL, NULL, + &props, pool)); - SVN_ERR(svn_client_propget5(&props, NULL, SVN_PROP_MIME_TYPE, - target_abspath_or_url, peg_revision, - end, NULL, svn_depth_empty, NULL, ctx, - pool, pool)); - - /* props could be keyed on URLs or paths depending on the - peg_revision and end values so avoid using the key. */ - hi = apr_hash_first(pool, props); - if (hi) + mime_type = svn_prop_get_value(props, SVN_PROP_MIME_TYPE); + } + else { - svn_string_t *value; + const svn_string_t *value; + + if (end->kind == svn_opt_revision_working) + SVN_ERR(svn_wc_prop_get2(&value, ctx->wc_ctx, + target_abspath_or_url, + SVN_PROP_MIME_TYPE, + pool, pool)); + else + { + SVN_ERR(svn_wc_get_pristine_props(&props, ctx->wc_ctx, + target_abspath_or_url, + pool, pool)); + + value = props ? svn_hash_gets(props, SVN_PROP_MIME_TYPE) + : NULL; + } - /* Should only be one value */ - SVN_ERR_ASSERT(apr_hash_count(props) == 1); + mime_type = value ? value->data : NULL; + } - value = apr_hash_this_val(hi); - if (value && svn_mime_type_is_binary(value->data)) + if (mime_type) + { + if (svn_mime_type_is_binary(mime_type)) return svn_error_createf (SVN_ERR_CLIENT_IS_BINARY_FILE, 0, _("Cannot calculate blame information for binary file '%s'"), @@ -719,6 +787,10 @@ svn_client_blame5(const char *target, frb.merged_chain->avail = NULL; frb.merged_chain->pool = pool; } + frb.backwards = (frb.start_rev > frb.end_rev); + frb.last_revnum = SVN_INVALID_REVNUM; + frb.last_props = NULL; + frb.check_mime_type = (frb.backwards && !ignore_mime_type); SVN_ERR(svn_ra_get_repos_root2(ra_session, &frb.repos_root_url, pool)); @@ -738,12 +810,11 @@ svn_client_blame5(const char *target, We need to ensure that we get one revision before the start_rev, if available so that we can know what was actually changed in the start revision. */ - SVN_ERR(svn_ra_get_latest_revnum(ra_session, &youngest, frb.currpool)); SVN_ERR(svn_ra_get_file_revs2(ra_session, "", - start_revnum - - (0 < start_revnum && start_revnum <= end_revnum ? 1 : 0) - + (youngest > start_revnum && start_revnum > end_revnum ? 1 : 0), - end_revnum, include_merged_revisions, + frb.backwards ? start_revnum + : MAX(0, start_revnum-1), + end_revnum, + include_merged_revisions, file_rev_handler, &frb, pool)); if (end->kind == svn_opt_revision_working) Modified: subversion/branches/reuse-ra-session/subversion/libsvn_client/client.h URL: http://svn.apache.org/viewvc/subversion/branches/reuse-ra-session/subversion/libsvn_client/client.h?rev=1662177&r1=1662176&r2=1662177&view=diff ============================================================================== --- subversion/branches/reuse-ra-session/subversion/libsvn_client/client.h (original) +++ subversion/branches/reuse-ra-session/subversion/libsvn_client/client.h Wed Feb 25 08:15:39 2015 @@ -462,17 +462,6 @@ svn_error_t *svn_client__get_all_auto_pr apr_pool_t *result_pool, apr_pool_t *scratch_pool); -/* Get a list of ignore patterns defined by the svn:global-ignores - properties set on, or inherited by, PATH_OR_URL. Store the collected - patterns as const char * elements in the array *IGNORES. Allocate - *IGNORES and its contents in RESULT_POOL. Use SCRATCH_POOL for - temporary allocations. */ -svn_error_t *svn_client__get_inherited_ignores(apr_array_header_t **ignores, - const char *path_or_url, - svn_client_ctx_t *ctx, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool); - /* The main logic for client deletion from a working copy. Deletes PATH from CTX->WC_CTX. If PATH (or any item below a directory PATH) is modified the delete will fail and return an error unless FORCE or KEEP_LOCAL @@ -966,11 +955,6 @@ svn_client__get_copy_committables(svn_cl apr_pool_t *result_pool, apr_pool_t *scratch_pool); -/* A qsort()-compatible sort routine for sorting an array of - svn_client_commit_item_t *'s by their URL member. */ -int svn_client__sort_commit_item_urls(const void *a, const void *b); - - /* Rewrite the COMMIT_ITEMS array to be sorted by URL. Also, discover a common *BASE_URL for the items in the array, and rewrite those items' URLs to be relative to that *BASE_URL. @@ -1215,6 +1199,41 @@ svn_client__arbitrary_nodes_diff(const c apr_pool_t *scratch_pool); +/* Helper for the remote case of svn_client_propget. + * + * If PROPS is not null, then get the value of property PROPNAME in + * REVNUM, using RA_SESSION. Store the value ('svn_string_t *') in + * PROPS, under the path key "TARGET_PREFIX/TARGET_RELATIVE" + * ('const char *'). + * + * If INHERITED_PROPS is not null, then set *INHERITED_PROPS to a + * depth-first ordered array of svn_prop_inherited_item_t * structures + * representing the PROPNAME properties inherited by the target. If + * INHERITABLE_PROPS in not null and no inheritable properties are found, + * then set *INHERITED_PROPS to an empty array. + * + * Recurse according to DEPTH, similarly to svn_client_propget3(). + * + * KIND is the kind of the node at "TARGET_PREFIX/TARGET_RELATIVE". + * Yes, caller passes this; it makes the recursion more efficient :-). + * + * Allocate PROPS and *INHERITED_PROPS in RESULT_POOL, but do all temporary + * work in SCRATCH_POOL. The two pools can be the same; recursive + * calls may use a different SCRATCH_POOL, however. + */ +svn_error_t * +svn_client__remote_propget(apr_hash_t *props, + apr_array_header_t **inherited_props, + const char *propname, + const char *target_prefix, + const char *target_relative, + svn_node_kind_t kind, + svn_revnum_t revnum, + svn_ra_session_t *ra_session, + svn_depth_t depth, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + #ifdef __cplusplus } #endif /* __cplusplus */ Modified: subversion/branches/reuse-ra-session/subversion/libsvn_client/commit.c URL: http://svn.apache.org/viewvc/subversion/branches/reuse-ra-session/subversion/libsvn_client/commit.c?rev=1662177&r1=1662176&r2=1662177&view=diff ============================================================================== --- subversion/branches/reuse-ra-session/subversion/libsvn_client/commit.c (original) +++ subversion/branches/reuse-ra-session/subversion/libsvn_client/commit.c Wed Feb 25 08:15:39 2015 @@ -240,10 +240,12 @@ post_process_commit_item(svn_wc_committe loop_recurse = TRUE; remove_lock = (! keep_locks && (item->state_flags - & SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN)); + & (SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN + | SVN_CLIENT_COMMIT_ITEM_ADD + | SVN_CLIENT_COMMIT_ITEM_DELETE))); - /* When the node was deleted (or replaced), we need to always remove the - locks, as they're invalidated on the server. We cannot honor the + /* When the node was deleted (or replaced), we need to always remove the + locks, as they're invalidated on the server. We cannot honor the SVN_CLIENT_COMMIT_ITEM_LOCK_TOKEN flag here because it does not tell us whether we have locked children. */ if (item->state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE) @@ -252,7 +254,7 @@ post_process_commit_item(svn_wc_committe return svn_error_trace( svn_wc_queue_committed4(queue, wc_ctx, item->path, loop_recurse, - 0 != (item->state_flags & + 0 != (item->state_flags & (SVN_CLIENT_COMMIT_ITEM_ADD | SVN_CLIENT_COMMIT_ITEM_DELETE | SVN_CLIENT_COMMIT_ITEM_TEXT_MODS @@ -532,6 +534,7 @@ svn_client_commit6(const apr_array_heade const char *current_abspath; const char *notify_prefix; int depth_empty_after = -1; + apr_hash_t *move_youngest = NULL; int i; SVN_ERR_ASSERT(depth != svn_depth_unknown && depth != svn_depth_exclude); @@ -699,62 +702,12 @@ svn_client_commit6(const apr_array_heade if (cmt_err) goto cleanup; - if (moved_from_abspath && delete_op_root_abspath && - strcmp(moved_from_abspath, delete_op_root_abspath) == 0) - + if (moved_from_abspath && delete_op_root_abspath) { - svn_boolean_t found_delete_half = - (svn_hash_gets(committables->by_path, delete_op_root_abspath) - != NULL); + svn_client_commit_item3_t *delete_half = + svn_hash_gets(committables->by_path, delete_op_root_abspath); - if (!found_delete_half) - { - const char *delete_half_parent_abspath; - - /* The delete-half isn't in the commit target list. - * However, it might itself be the child of a deleted node, - * either because of another move or a deletion. - * - * For example, consider: mv A/B B; mv B/C C; commit; - * C's moved-from A/B/C is a child of the deleted A/B. - * A/B/C does not appear in the commit target list, but - * A/B does appear. - * (Note that moved-from information is always stored - * relative to the BASE tree, so we have 'C moved-from - * A/B/C', not 'C moved-from B/C'.) - * - * An example involving a move and a delete would be: - * mv A/B C; rm A; commit; - * Now C is moved-from A/B which does not appear in the - * commit target list, but A does appear. - */ - - /* Scan upwards for a deletion op-root from the - * delete-half's parent directory. */ - delete_half_parent_abspath = - svn_dirent_dirname(delete_op_root_abspath, iterpool); - if (strcmp(delete_op_root_abspath, - delete_half_parent_abspath) != 0) - { - const char *parent_delete_op_root_abspath; - - cmt_err = svn_error_trace( - svn_wc__node_get_deleted_ancestor( - &parent_delete_op_root_abspath, - ctx->wc_ctx, delete_half_parent_abspath, - iterpool, iterpool)); - if (cmt_err) - goto cleanup; - - if (parent_delete_op_root_abspath) - found_delete_half = - (svn_hash_gets(committables->by_path, - parent_delete_op_root_abspath) - != NULL); - } - } - - if (!found_delete_half) + if (!delete_half) { cmt_err = svn_error_createf( SVN_ERR_ILLEGAL_TARGET, NULL, @@ -779,6 +732,17 @@ svn_client_commit6(const apr_array_heade goto cleanup; } + else if (delete_half->revision == item->copyfrom_rev) + { + /* Ok, now we know that we perform an out-of-date check + on the copyfrom location. Remember this for a fixup + round right before committing. */ + + if (!move_youngest) + move_youngest = apr_hash_make(pool); + + svn_hash_sets(move_youngest, item->path, item); + } } } @@ -877,6 +841,37 @@ svn_client_commit6(const apr_array_heade if (cmt_err) goto cleanup; + if (move_youngest != NULL) + { + apr_hash_index_t *hi; + svn_revnum_t youngest; + + SVN_ERR(svn_ra_get_latest_revnum(ra_session, &youngest, pool)); + + for (hi = apr_hash_first(iterpool, move_youngest); + hi; + hi = apr_hash_next(hi)) + { + svn_client_commit_item3_t *item = apr_hash_this_val(hi); + + /* We delete the original side with its original revision and will + receive an out-of-date error if that node changed since that + revision. + + The copy is of that same revision and we know that this revision + didn't change between this revision and youngest. So we can just + as well commit a copy from youngest. + + Note that it is still possible to see gaps between the delete and + copy revisions as the repository might handle multiple commits + at the same time (or when an out of date proxy is involved), but + in general it should decrease the number of gaps. */ + + if (item->copyfrom_rev < youngest) + item->copyfrom_rev = youngest; + } + } + cmt_err = svn_error_trace( get_ra_editor(&editor, &edit_baton, ra_session, ctx, log_msg, commit_items, revprop_table, Modified: subversion/branches/reuse-ra-session/subversion/libsvn_client/commit_util.c URL: http://svn.apache.org/viewvc/subversion/branches/reuse-ra-session/subversion/libsvn_client/commit_util.c?rev=1662177&r1=1662176&r2=1662177&view=diff ============================================================================== --- subversion/branches/reuse-ra-session/subversion/libsvn_client/commit_util.c (original) +++ subversion/branches/reuse-ra-session/subversion/libsvn_client/commit_util.c Wed Feb 25 08:15:39 2015 @@ -467,10 +467,12 @@ harvest_not_present_for_copy(svn_wc_cont apr_pool_t *iterpool = svn_pool_create(scratch_pool); int i; + SVN_ERR_ASSERT(commit_relpath != NULL); + /* A function to retrieve not present children would be nice to have */ - SVN_ERR(svn_wc__node_get_children_of_working_node( - &children, wc_ctx, local_abspath, TRUE, - scratch_pool, iterpool)); + SVN_ERR(svn_wc__node_get_not_present_children(&children, wc_ctx, + local_abspath, + scratch_pool, iterpool)); for (i = 0; i < children->nelts; i++) { @@ -486,13 +488,10 @@ harvest_not_present_for_copy(svn_wc_cont this_abspath, FALSE, scratch_pool)); if (!not_present) - continue; + continue; /* Node is replaced */ - if (commit_relpath == NULL) - this_commit_relpath = NULL; - else - this_commit_relpath = svn_relpath_join(commit_relpath, name, - iterpool); + this_commit_relpath = svn_relpath_join(commit_relpath, name, + iterpool); /* We should check if we should really add a delete operation */ if (check_url_func) @@ -1380,7 +1379,10 @@ svn_client__get_copy_committables(svn_cl } -int svn_client__sort_commit_item_urls(const void *a, const void *b) +/* A svn_sort__array()/qsort()-compatible sort routine for sorting + an array of svn_client_commit_item_t *'s by their URL member. */ +static int +sort_commit_item_urls(const void *a, const void *b) { const svn_client_commit_item3_t *item1 = *((const svn_client_commit_item3_t * const *) a); @@ -1404,7 +1406,7 @@ svn_client__condense_commit_items(const SVN_ERR_ASSERT(ci && ci->nelts); /* Sort our commit items by their URLs. */ - svn_sort__array(ci, svn_client__sort_commit_item_urls); + svn_sort__array(ci, sort_commit_item_urls); /* Loop through the URLs, finding the longest usable ancestor common to all of them, and making sure there are no duplicate URLs. */ @@ -1559,7 +1561,7 @@ do_item_commit(void **dir_baton, file_pool = pool; /* Subpools are cheap, but memory isn't */ - file_pool = svn_pool_create(file_pool); + file_pool = svn_pool_create(file_pool); /* Call the cancellation function. */ if (ctx->cancel_func) Modified: subversion/branches/reuse-ra-session/subversion/libsvn_client/copy.c URL: http://svn.apache.org/viewvc/subversion/branches/reuse-ra-session/subversion/libsvn_client/copy.c?rev=1662177&r1=1662176&r2=1662177&view=diff ============================================================================== --- subversion/branches/reuse-ra-session/subversion/libsvn_client/copy.c (original) +++ subversion/branches/reuse-ra-session/subversion/libsvn_client/copy.c Wed Feb 25 08:15:39 2015 @@ -177,12 +177,487 @@ get_copy_pair_ancestors(const apr_array_ return SVN_NO_ERROR; } +/* Quote a string if it would be handled as multiple or different tokens + during externals parsing */ +static const char * +maybe_quote(const char *value, + apr_pool_t *result_pool) +{ + apr_status_t status; + char **argv; + + status = apr_tokenize_to_argv(value, &argv, result_pool); + + if (!status && argv[0] && !argv[1] && strcmp(argv[0], value) == 0) + return apr_pstrdup(result_pool, value); + + { + svn_stringbuf_t *sb = svn_stringbuf_create_empty(result_pool); + const char *c; + + svn_stringbuf_appendbyte(sb, '\"'); + + for (c = value; *c; c++) + { + if (*c == '\\' || *c == '\"' || *c == '\'') + svn_stringbuf_appendbyte(sb, '\\'); + + svn_stringbuf_appendbyte(sb, *c); + } + + svn_stringbuf_appendbyte(sb, '\"'); + +#ifdef SVN_DEBUG + status = apr_tokenize_to_argv(sb->data, &argv, result_pool); + + SVN_ERR_ASSERT_NO_RETURN(!status && argv[0] && !argv[1] + && !strcmp(argv[0], value)); +#endif + + return sb->data; + } +} + +/* In *NEW_EXTERNALS_DESCRIPTION, return a new external description for + * use as a line in an svn:externals property, based on the external item + * ITEM and the additional parser information in INFO. Pin the external + * to EXTERNAL_PEGREV. Use POOL for all allocations. */ +static svn_error_t * +make_external_description(const char **new_external_description, + const char *local_abspath_or_url, + svn_wc_external_item2_t *item, + svn_wc__externals_parser_info_t *info, + svn_opt_revision_t external_pegrev, + apr_pool_t *pool) +{ + const char *rev_str; + const char *peg_rev_str; + + switch (info->format) + { + case svn_wc__external_description_format_1: + if (external_pegrev.kind == svn_opt_revision_unspecified) + { + /* If info->rev_str is NULL, this yields an empty string. */ + rev_str = apr_pstrcat(pool, info->rev_str, " ", SVN_VA_NULL); + } + else if (info->rev_str && item->revision.kind != svn_opt_revision_head) + rev_str = apr_psprintf(pool, "%s ", info->rev_str); + else + { + /* ### can't handle svn_opt_revision_date without info->rev_str */ + SVN_ERR_ASSERT(external_pegrev.kind == svn_opt_revision_number); + rev_str = apr_psprintf(pool, "-r%ld ", + external_pegrev.value.number); + } + + *new_external_description = + apr_psprintf(pool, "%s %s%s\n", maybe_quote(item->target_dir, pool), + rev_str, + maybe_quote(item->url, pool)); + break; + + case svn_wc__external_description_format_2: + if (external_pegrev.kind == svn_opt_revision_unspecified) + { + /* If info->rev_str is NULL, this yields an empty string. */ + rev_str = apr_pstrcat(pool, info->rev_str, " ", SVN_VA_NULL); + } + else if (info->rev_str && item->revision.kind != svn_opt_revision_head) + rev_str = apr_psprintf(pool, "%s ", info->rev_str); + else + rev_str = ""; + + if (external_pegrev.kind == svn_opt_revision_unspecified) + peg_rev_str = info->peg_rev_str ? info->peg_rev_str : ""; + else if (info->peg_rev_str && + item->peg_revision.kind != svn_opt_revision_head) + peg_rev_str = info->peg_rev_str; + else + { + /* ### can't handle svn_opt_revision_date without info->rev_str */ + SVN_ERR_ASSERT(external_pegrev.kind == svn_opt_revision_number); + peg_rev_str = apr_psprintf(pool, "@%ld", + external_pegrev.value.number); + } + + *new_external_description = + apr_psprintf(pool, "%s%s %s\n", rev_str, + maybe_quote(apr_psprintf(pool, "%s%s", item->url, + peg_rev_str), + pool), + maybe_quote(item->target_dir, pool)); + break; + + default: + return svn_error_createf( + SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL, + _("%s property defined at '%s' is using an unsupported " + "syntax"), SVN_PROP_EXTERNALS, + svn_dirent_local_style(local_abspath_or_url, pool)); + } + + return SVN_NO_ERROR; +} + +/* Pin all externals listed in EXTERNALS_PROP_VAL to their last-changed + * revision. Return a new property value in *PINNED_EXTERNALS allocated + * in RESULT_POOL. LOCAL_ABSPATH_OR_URL is the path or URL defining the + * svn:externals property. Use SCRATCH_POOL for temporary allocations. + */ +static svn_error_t * +pin_externals_prop(svn_string_t **pinned_externals, + svn_string_t *externals_prop_val, + const apr_hash_t *externals_to_pin, + const char *repos_root_url, + const char *local_abspath_or_url, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_stringbuf_t *buf; + apr_array_header_t *external_items; + apr_array_header_t *parser_infos; + apr_array_header_t *items_to_pin; + int i; + apr_pool_t *iterpool; + + SVN_ERR(svn_wc__parse_externals_description(&external_items, + &parser_infos, + local_abspath_or_url, + externals_prop_val->data, + FALSE /* canonicalize_url */, + scratch_pool)); + + if (externals_to_pin) + items_to_pin = svn_hash_gets((apr_hash_t *)externals_to_pin, + local_abspath_or_url); + else + items_to_pin = NULL; + + buf = svn_stringbuf_create_empty(scratch_pool); + iterpool = svn_pool_create(scratch_pool); + for (i = 0; i < external_items->nelts; i++) + { + svn_wc_external_item2_t *item; + svn_wc__externals_parser_info_t *info; + svn_opt_revision_t external_pegrev; + const char *pinned_desc; + + svn_pool_clear(iterpool); + + item = APR_ARRAY_IDX(external_items, i, svn_wc_external_item2_t *); + info = APR_ARRAY_IDX(parser_infos, i, svn_wc__externals_parser_info_t *); + + if (items_to_pin) + { + int j; + svn_wc_external_item2_t *item_to_pin = NULL; + + for (j = 0; j < items_to_pin->nelts; j++) + { + svn_wc_external_item2_t *const current = + APR_ARRAY_IDX(items_to_pin, j, svn_wc_external_item2_t *); + + + if (current + && 0 == strcmp(item->url, current->url) + && 0 == strcmp(item->target_dir, current->target_dir)) + { + item_to_pin = current; + break; + } + } + + /* If this item is not in our list of external items to pin then + * simply keep the external at its original value. */ + if (!item_to_pin) + { + const char *desc; + + external_pegrev.kind = svn_opt_revision_unspecified; + SVN_ERR(make_external_description(&desc, local_abspath_or_url, + item, info, external_pegrev, + iterpool)); + svn_stringbuf_appendcstr(buf, desc); + continue; + } + } + + if (item->peg_revision.kind == svn_opt_revision_date) + { + external_pegrev.kind = svn_opt_revision_date; + external_pegrev.value.date = item->peg_revision.value.date; + } + else if (item->peg_revision.kind == svn_opt_revision_number) + { + external_pegrev.kind = svn_opt_revision_number; + external_pegrev.value.number = item->peg_revision.value.number; + } + else + { + SVN_ERR_ASSERT( + item->peg_revision.kind == svn_opt_revision_head || + item->peg_revision.kind == svn_opt_revision_unspecified); + + if (svn_path_is_url(local_abspath_or_url)) + { + const char *resolved_url; + svn_ra_session_t *external_ra_session; + svn_revnum_t latest_revnum; + + SVN_ERR(svn_wc__resolve_relative_external_url( + &resolved_url, item, repos_root_url, + local_abspath_or_url, iterpool, iterpool)); + SVN_ERR(svn_client__open_ra_session_internal(&external_ra_session, + NULL, resolved_url, + NULL, NULL, FALSE, + FALSE, ctx, + iterpool, + iterpool)); + SVN_ERR(svn_ra_get_latest_revnum(external_ra_session, + &latest_revnum, + iterpool)); + + external_pegrev.kind = svn_opt_revision_number; + external_pegrev.value.number = latest_revnum; + } + else + { + const char *external_abspath; + svn_node_kind_t external_kind; + svn_revnum_t external_checked_out_rev; + + external_abspath = svn_dirent_join(local_abspath_or_url, + item->target_dir, + iterpool); + SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL, + NULL, NULL, ctx->wc_ctx, + local_abspath_or_url, + external_abspath, TRUE, + iterpool, + iterpool)); + if (external_kind == svn_node_none) + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, + NULL, + _("Cannot pin external '%s' defined " + "in %s at '%s' because it is not " + "checked out in the working copy " + "at '%s'"), + item->url, SVN_PROP_EXTERNALS, + svn_dirent_local_style( + local_abspath_or_url, iterpool), + svn_dirent_local_style( + external_abspath, iterpool)); + else if (external_kind == svn_node_dir) + { + svn_boolean_t is_switched; + svn_boolean_t is_modified; + svn_revnum_t min_rev; + svn_revnum_t max_rev; + + /* Perform some sanity checks on the checked-out external. */ + + SVN_ERR(svn_wc__has_switched_subtrees(&is_switched, + ctx->wc_ctx, + external_abspath, NULL, + iterpool)); + if (is_switched) + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, + NULL, + _("Cannot pin external '%s' defined " + "in %s at '%s' because '%s' has " + "switched subtrees (switches " + "cannot be represented in %s)"), + item->url, SVN_PROP_EXTERNALS, + svn_dirent_local_style( + local_abspath_or_url, iterpool), + svn_dirent_local_style( + external_abspath, iterpool), + SVN_PROP_EXTERNALS); + + SVN_ERR(svn_wc__has_local_mods(&is_modified, ctx->wc_ctx, + external_abspath, TRUE, + ctx->cancel_func, + ctx->cancel_baton, + iterpool)); + if (is_modified) + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, + NULL, + _("Cannot pin external '%s' defined " + "in %s at '%s' because '%s' has " + "local modifications (local " + "modifications cannot be " + "represented in %s)"), + item->url, SVN_PROP_EXTERNALS, + svn_dirent_local_style( + local_abspath_or_url, iterpool), + svn_dirent_local_style( + external_abspath, iterpool), + SVN_PROP_EXTERNALS); + + SVN_ERR(svn_wc__min_max_revisions(&min_rev, &max_rev, ctx->wc_ctx, + external_abspath, FALSE, + iterpool)); + if (min_rev != max_rev) + return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, + NULL, + _("Cannot pin external '%s' defined " + "in %s at '%s' because '%s' is a " + "mixed-revision working copy " + "(mixed-revisions cannot be " + "represented in %s)"), + item->url, SVN_PROP_EXTERNALS, + svn_dirent_local_style( + local_abspath_or_url, iterpool), + svn_dirent_local_style( + external_abspath, iterpool), + SVN_PROP_EXTERNALS); + external_checked_out_rev = min_rev; + } + else + { + SVN_ERR_ASSERT(external_kind == svn_node_file); + SVN_ERR(svn_wc__node_get_repos_info(&external_checked_out_rev, + NULL, NULL, NULL, + ctx->wc_ctx, external_abspath, + iterpool, iterpool)); + } + + external_pegrev.kind = svn_opt_revision_number; + external_pegrev.value.number = external_checked_out_rev; + } + } + + SVN_ERR_ASSERT(external_pegrev.kind == svn_opt_revision_date || + external_pegrev.kind == svn_opt_revision_number); + + SVN_ERR(make_external_description(&pinned_desc, local_abspath_or_url, + item, info, external_pegrev, iterpool)); + + svn_stringbuf_appendcstr(buf, pinned_desc); + } + svn_pool_destroy(iterpool); + + *pinned_externals = svn_string_create_from_buf(buf, result_pool); + + return SVN_NO_ERROR; +} + +/* Return, in *NEW_EXTERNALS, a new hash of externals definitions, some or + * which all of which are pinned. If EXTERNALS_TO_PIN is NULL, pin all + * externals, else pin the externals mentioned in EXTERNALS_TO_PIN. + * The pinning operation takes place as part of the copy operation for + * the source/destination pair PAIR. Use RA_SESSION and REPOS_ROOT_URL + * to contact the repository containing the externals definition, if neccesary. + * Use CX to fopen additional RA sessions to external repositories, if + * neccessary. Allocate *NEW_EXTERNALS in RESULT_POOL. + * Use SCRATCH_POOL for temporary allocations. */ +static svn_error_t * +resolve_pinned_externals(apr_hash_t **new_externals, + const apr_hash_t *externals_to_pin, + svn_client__copy_pair_t *pair, + svn_ra_session_t *ra_session, + const char *repos_root_url, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *old_url = NULL; + apr_hash_t *externals_props; + apr_hash_index_t *hi; + apr_pool_t *iterpool; + + *new_externals = apr_hash_make(result_pool); + + if (svn_path_is_url(pair->src_abspath_or_url)) + { + SVN_ERR(svn_client__ensure_ra_session_url(&old_url, ra_session, + pair->src_abspath_or_url, + scratch_pool)); + externals_props = apr_hash_make(scratch_pool); + SVN_ERR(svn_client__remote_propget(externals_props, NULL, + SVN_PROP_EXTERNALS, + pair->src_abspath_or_url, "", + svn_node_dir, + pair->src_revnum, + ra_session, + svn_depth_infinity, + scratch_pool, + scratch_pool)); + } + else + { + SVN_ERR(svn_wc__externals_gather_definitions(&externals_props, NULL, + ctx->wc_ctx, + pair->src_abspath_or_url, + svn_depth_infinity, + scratch_pool, scratch_pool)); + + /* ### gather_definitions returns propvals as const char * */ + for (hi = apr_hash_first(scratch_pool, externals_props); + hi; + hi = apr_hash_next(hi)) + { + const char *local_abspath_or_url = apr_hash_this_key(hi); + const char *propval = apr_hash_this_val(hi); + svn_string_t *new_propval = svn_string_create(propval, scratch_pool); + + svn_hash_sets(externals_props, local_abspath_or_url, new_propval); + } + } + + if (apr_hash_count(externals_props) == 0) + { + if (old_url) + SVN_ERR(svn_ra_reparent(ra_session, old_url, scratch_pool)); + return SVN_NO_ERROR; + } + + iterpool = svn_pool_create(scratch_pool); + for (hi = apr_hash_first(scratch_pool, externals_props); + hi; + hi = apr_hash_next(hi)) + { + const char *local_abspath_or_url = apr_hash_this_key(hi); + svn_string_t *externals_propval = apr_hash_this_val(hi); + const char *relpath; + svn_string_t *new_propval; + + svn_pool_clear(iterpool); + + SVN_ERR(pin_externals_prop(&new_propval, externals_propval, + externals_to_pin, + repos_root_url, local_abspath_or_url, ctx, + result_pool, iterpool)); + if (svn_path_is_url(pair->src_abspath_or_url)) + relpath = svn_uri_skip_ancestor(pair->src_abspath_or_url, + local_abspath_or_url, + result_pool); + else + relpath = svn_dirent_skip_ancestor(pair->src_abspath_or_url, + local_abspath_or_url); + SVN_ERR_ASSERT(relpath); + svn_hash_sets(*new_externals, relpath, new_propval); + } + svn_pool_destroy(iterpool); + + if (old_url) + SVN_ERR(svn_ra_reparent(ra_session, old_url, scratch_pool)); + + return SVN_NO_ERROR; +} + + /* The guts of do_wc_to_wc_copies */ static svn_error_t * do_wc_to_wc_copies_with_write_lock(svn_boolean_t *timestamp_sleep, const apr_array_header_t *copy_pairs, const char *dst_parent, + svn_boolean_t metadata_only, + svn_boolean_t pin_externals, + const apr_hash_t *externals_to_pin, svn_client_ctx_t *ctx, apr_pool_t *scratch_pool) { @@ -195,22 +670,63 @@ do_wc_to_wc_copies_with_write_lock(svn_b const char *dst_abspath; svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *); + apr_hash_t *pinned_externals = NULL; + svn_pool_clear(iterpool); /* Check for cancellation */ if (ctx->cancel_func) SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); + if (pin_externals) + { + const char *repos_root_url; + + SVN_ERR(svn_wc__node_get_origin(NULL, NULL, NULL, &repos_root_url, + NULL, NULL, NULL, ctx->wc_ctx, + pair->src_abspath_or_url, FALSE, + scratch_pool, iterpool)); + SVN_ERR(resolve_pinned_externals(&pinned_externals, + externals_to_pin, pair, NULL, + repos_root_url, ctx, + iterpool, iterpool)); + } + /* Perform the copy */ dst_abspath = svn_dirent_join(pair->dst_parent_abspath, pair->base_name, iterpool); *timestamp_sleep = TRUE; err = svn_wc_copy3(ctx->wc_ctx, pair->src_abspath_or_url, dst_abspath, - FALSE /* metadata_only */, + metadata_only, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, iterpool); if (err) break; + + if (pinned_externals) + { + apr_hash_index_t *hi; + + for (hi = apr_hash_first(iterpool, pinned_externals); + hi; + hi = apr_hash_next(hi)) + { + const char *dst_relpath = apr_hash_this_key(hi); + svn_string_t *externals_propval = apr_hash_this_val(hi); + const char *local_abspath; + + local_abspath = svn_dirent_join(pair->dst_abspath_or_url, + dst_relpath, iterpool); + /* ### use a work queue? */ + SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, local_abspath, + SVN_PROP_EXTERNALS, externals_propval, + svn_depth_empty, TRUE /* skip_checks */, + NULL /* changelist_filter */, + ctx->cancel_func, ctx->cancel_baton, + NULL, NULL, /* no extra notification */ + iterpool)); + } + } } svn_pool_destroy(iterpool); @@ -223,6 +739,9 @@ do_wc_to_wc_copies_with_write_lock(svn_b static svn_error_t * do_wc_to_wc_copies(svn_boolean_t *timestamp_sleep, const apr_array_header_t *copy_pairs, + svn_boolean_t metadata_only, + svn_boolean_t pin_externals, + const apr_hash_t *externals_to_pin, svn_client_ctx_t *ctx, apr_pool_t *pool) { @@ -236,7 +755,8 @@ do_wc_to_wc_copies(svn_boolean_t *timest SVN_WC__CALL_WITH_WRITE_LOCK( do_wc_to_wc_copies_with_write_lock(timestamp_sleep, copy_pairs, dst_parent, - ctx, pool), + metadata_only, pin_externals, + externals_to_pin, ctx, pool), ctx->wc_ctx, dst_parent_abspath, FALSE, pool); return SVN_NO_ERROR; @@ -594,6 +1114,8 @@ typedef struct path_driver_info_t svn_boolean_t resurrection; svn_boolean_t dir_add; svn_string_t *mergeinfo; /* the new mergeinfo for the target */ + svn_string_t *externals; /* new externals definitions for the target */ + svn_boolean_t only_pin_externals; } path_driver_info_t; @@ -631,7 +1153,7 @@ path_driver_cb_func(void **dir_baton, with such, the code is just plain wrong. */ SVN_ERR_ASSERT(! svn_path_is_empty(path)); - /* Check to see if we need to add the path as a directory. */ + /* Check to see if we need to add the path as a parent directory. */ if (path_info->dir_add) { return cb_baton->editor->add_directory(path, parent_baton, NULL, @@ -662,7 +1184,7 @@ path_driver_cb_func(void **dir_baton, /* Not a move? This must just be the copy addition. */ else { - do_add = TRUE; + do_add = !path_info->only_pin_externals; } } @@ -702,6 +1224,18 @@ path_driver_cb_func(void **dir_baton, pool)); } } + + if (path_info->externals) + { + if (*dir_baton == NULL) + SVN_ERR(cb_baton->editor->open_directory(path, parent_baton, + SVN_INVALID_REVNUM, + pool, dir_baton)); + + SVN_ERR(cb_baton->editor->change_dir_prop(*dir_baton, SVN_PROP_EXTERNALS, + path_info->externals, pool)); + } + return SVN_NO_ERROR; } @@ -786,6 +1320,79 @@ find_absent_parents2(svn_ra_session_t *r return SVN_NO_ERROR; } +/* Queue property changes for pinning svn:externals properties set on + * descendants of the path corresponding to PARENT_INFO. PINNED_EXTERNALS + * is keyed by the relative path of each descendant which should have some + * or all of its externals pinned, with the corresponding pinned svn:externals + * properties as values. Property changes are queued in a new list of path + * infos *NEW_PATH_INFOS, or in an existing item of the PATH_INFOS list if an + * existing item is found for the descendant. Allocate results in RESULT_POOL. + * Use SCRATCH_POOL for temporary allocations. */ +static svn_error_t * +queue_externals_change_path_infos(apr_array_header_t *new_path_infos, + apr_array_header_t *path_infos, + apr_hash_t *pinned_externals, + path_driver_info_t *parent_info, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + apr_hash_index_t *hi; + + for (hi = apr_hash_first(scratch_pool, pinned_externals); + hi; + hi = apr_hash_next(hi)) + { + const char *dst_relpath = apr_hash_this_key(hi); + svn_string_t *externals_prop = apr_hash_this_val(hi); + const char *src_url; + path_driver_info_t *info; + int i; + + svn_pool_clear(iterpool); + + src_url = svn_path_url_add_component2(parent_info->src_url, + dst_relpath, iterpool); + + /* Try to find a path info the external change can be applied to. */ + info = NULL; + for (i = 0; i < path_infos->nelts; i++) + { + path_driver_info_t *existing_info; + + existing_info = APR_ARRAY_IDX(path_infos, i, path_driver_info_t *); + if (strcmp(src_url, existing_info->src_url) == 0) + { + info = existing_info; + break; + } + } + + if (info == NULL) + { + /* A copied-along child needs its externals pinned. + Create a new path info for this property change. */ + info = apr_pcalloc(result_pool, sizeof(*info)); + info->src_url = svn_path_url_add_component2( + parent_info->src_url, dst_relpath, + result_pool); + info->src_path = NULL; /* Only needed on copied dirs */ + info->dst_path = svn_relpath_join(parent_info->dst_path, + dst_relpath, + result_pool); + info->src_kind = svn_node_dir; + info->only_pin_externals = TRUE; + APR_ARRAY_PUSH(new_path_infos, path_driver_info_t *) = info; + } + + info->externals = externals_prop; + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + static svn_error_t * repos_to_repos_copy(const apr_array_header_t *copy_pairs, svn_boolean_t make_parents, @@ -794,6 +1401,8 @@ repos_to_repos_copy(const apr_array_head void *commit_baton, svn_client_ctx_t *ctx, svn_boolean_t is_move, + svn_boolean_t pin_externals, + const apr_hash_t *externals_to_pin, apr_pool_t *pool) { svn_error_t *err; @@ -809,6 +1418,7 @@ repos_to_repos_copy(const apr_array_head struct path_driver_cb_baton cb_baton; apr_array_header_t *new_dirs = NULL; apr_hash_t *commit_revprops; + apr_array_header_t *pin_externals_only_infos = NULL; int i; svn_client__copy_pair_t *first_pair = APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *); @@ -1067,12 +1677,31 @@ repos_to_repos_copy(const apr_array_head } /* More info for our INFO structure. */ - info->src_path = src_rel; + info->src_path = src_rel; /* May be NULL, if outside RA session scope */ info->dst_path = dst_rel; svn_hash_sets(action_hash, info->dst_path, info); if (is_move && (! info->resurrection)) svn_hash_sets(action_hash, info->src_path, info); + + if (pin_externals) + { + apr_hash_t *pinned_externals; + + SVN_ERR(resolve_pinned_externals(&pinned_externals, + externals_to_pin, pair, + ra_session, repos_root, + ctx, pool, pool)); + if (pin_externals_only_infos == NULL) + { + pin_externals_only_infos = + apr_array_make(pool, 0, sizeof(path_driver_info_t *)); + } + SVN_ERR(queue_externals_change_path_infos(pin_externals_only_infos, + path_infos, + pinned_externals, + info, pool, pool)); + } } if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx)) @@ -1164,6 +1793,19 @@ repos_to_repos_copy(const apr_array_head APR_ARRAY_PUSH(paths, const char *) = info->src_path; } + /* Add any items which only need their externals pinned. */ + if (pin_externals_only_infos) + { + for (i = 0; i < pin_externals_only_infos->nelts; i++) + { + path_driver_info_t *info; + + info = APR_ARRAY_IDX(pin_externals_only_infos, i, path_driver_info_t *); + APR_ARRAY_PUSH(paths, const char *) = info->dst_path; + svn_hash_sets(action_hash, info->dst_path, info); + } + } + SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table, message, ctx, pool)); @@ -1244,6 +1886,62 @@ check_url_kind(void *baton, return SVN_NO_ERROR; } +/* Queue a property change on a copy of LOCAL_ABSPATH to COMMIT_URL + * in the COMMIT_ITEMS list. + * If the list does not already have a commit item for COMMIT_URL + * add a new commit item for the property change. + * Allocate results in RESULT_POOL. + * Use SCRATCH_POOL for temporary allocations. */ +static svn_error_t * +queue_prop_change_commit_items(const char *local_abspath, + const char *commit_url, + apr_array_header_t *commit_items, + const char *propname, + svn_string_t *propval, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_client_commit_item3_t *item = NULL; + svn_prop_t *prop; + int i; + + for (i = 0; i < commit_items->nelts; i++) + { + svn_client_commit_item3_t *existing_item; + + existing_item = APR_ARRAY_IDX(commit_items, i, + svn_client_commit_item3_t *); + if (strcmp(existing_item->url, commit_url) == 0) + { + item = existing_item; + break; + } + } + + if (item == NULL) + { + item = svn_client_commit_item3_create(result_pool); + item->path = local_abspath; + item->url = commit_url; + item->kind = svn_node_dir; + item->state_flags = SVN_CLIENT_COMMIT_ITEM_PROP_MODS; + APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item; + } + else + item->state_flags |= SVN_CLIENT_COMMIT_ITEM_PROP_MODS; + + if (item->outgoing_prop_changes == NULL) + item->outgoing_prop_changes = apr_array_make(result_pool, 1, + sizeof(svn_prop_t *)); + + prop = apr_palloc(result_pool, sizeof(*prop)); + prop->name = propname; + prop->value = propval; + APR_ARRAY_PUSH(item->outgoing_prop_changes, svn_prop_t *) = prop; + + return SVN_NO_ERROR; +} + /* ### Copy ... * COMMIT_INFO_P is ... * COPY_PAIRS is ... such that each 'src_abspath_or_url' is a local abspath @@ -1257,6 +1955,8 @@ wc_to_repos_copy(const apr_array_header_ const apr_hash_t *revprop_table, svn_commit_callback2_t commit_callback, void *commit_baton, + svn_boolean_t pin_externals, + const apr_hash_t *externals_to_pin, svn_client_ctx_t *ctx, apr_pool_t *scratch_pool) { @@ -1453,6 +2153,43 @@ wc_to_repos_copy(const apr_array_header_ APR_ARRAY_PUSH(item->outgoing_prop_changes, svn_prop_t *) = mergeinfo_prop; } + + if (pin_externals) + { + apr_hash_t *pinned_externals; + apr_hash_index_t *hi; + + SVN_ERR(resolve_pinned_externals(&pinned_externals, + externals_to_pin, pair, + ra_session, cukb.repos_root_url, + ctx, scratch_pool, iterpool)); + for (hi = apr_hash_first(scratch_pool, pinned_externals); + hi; + hi = apr_hash_next(hi)) + { + const char *dst_relpath = apr_hash_this_key(hi); + svn_string_t *externals_propval = apr_hash_this_val(hi); + const char *dst_url; + const char *commit_url; + const char *src_abspath; + + if (svn_path_is_url(pair->dst_abspath_or_url)) + dst_url = pair->dst_abspath_or_url; + else + SVN_ERR(svn_wc__node_get_url(&dst_url, ctx->wc_ctx, + pair->dst_abspath_or_url, + scratch_pool, iterpool)); + commit_url = svn_path_url_add_component2(dst_url, dst_relpath, + scratch_pool); + src_abspath = svn_dirent_join(pair->src_abspath_or_url, + dst_relpath, iterpool); + SVN_ERR(queue_prop_change_commit_items(src_abspath, + commit_url, commit_items, + SVN_PROP_EXTERNALS, + externals_propval, + scratch_pool, iterpool)); + } + } } if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx)) @@ -1574,6 +2311,8 @@ repos_to_wc_copy_single(svn_boolean_t *t svn_client__copy_pair_t *pair, svn_boolean_t same_repositories, svn_boolean_t ignore_externals, + svn_boolean_t pin_externals, + const apr_hash_t *externals_to_pin, svn_ra_session_t *ra_session, svn_client_ctx_t *ctx, apr_pool_t *pool) @@ -1630,6 +2369,14 @@ repos_to_wc_copy_single(svn_boolean_t *t ctx->notify_func2 = notification_adjust_func; ctx->notify_baton2 = &nb; + /* Avoid a chicken-and-egg problem: + * If pinning externals we'll need to adjust externals + * properties before checking out any externals. + * But copy needs to happen before pinning because else there + * are no svn:externals properties to pin. */ + if (pin_externals) + ignore_externals = TRUE; + err = svn_client__checkout_internal(&pair->src_revnum, timestamp_sleep, pair->src_original, tmp_abspath, @@ -1682,6 +2429,61 @@ repos_to_wc_copy_single(svn_boolean_t *t return SVN_NO_ERROR; } + + if (pin_externals) + { + apr_hash_t *pinned_externals; + apr_hash_index_t *hi; + apr_pool_t *iterpool; + const char *repos_root_url; + apr_hash_t *new_externals; + apr_hash_t *new_depths; + + SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url, pool)); + SVN_ERR(resolve_pinned_externals(&pinned_externals, + externals_to_pin, pair, + ra_session, repos_root_url, + ctx, pool, pool)); + + iterpool = svn_pool_create(pool); + for (hi = apr_hash_first(pool, pinned_externals); + hi; + hi = apr_hash_next(hi)) + { + const char *dst_relpath = apr_hash_this_key(hi); + svn_string_t *externals_propval = apr_hash_this_val(hi); + const char *local_abspath; + + svn_pool_clear(iterpool); + + local_abspath = svn_dirent_join(pair->dst_abspath_or_url, + dst_relpath, iterpool); + /* ### use a work queue? */ + SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, local_abspath, + SVN_PROP_EXTERNALS, externals_propval, + svn_depth_empty, TRUE /* skip_checks */, + NULL /* changelist_filter */, + ctx->cancel_func, ctx->cancel_baton, + NULL, NULL, /* no extra notification */ + iterpool)); + } + + /* Now update all externals in the newly created copy. */ + SVN_ERR(svn_wc__externals_gather_definitions(&new_externals, + &new_depths, + ctx->wc_ctx, + dst_abspath, + svn_depth_infinity, + iterpool, iterpool)); + SVN_ERR(svn_client__handle_externals(new_externals, + new_depths, + repos_root_url, dst_abspath, + svn_depth_infinity, + timestamp_sleep, + ra_session, + ctx, iterpool)); + svn_pool_destroy(iterpool); + } } /* end directory case */ else if (pair->src_kind == svn_node_file) @@ -1741,6 +2543,8 @@ repos_to_wc_copy_locked(svn_boolean_t *t const apr_array_header_t *copy_pairs, const char *top_dst_path, svn_boolean_t ignore_externals, + svn_boolean_t pin_externals, + const apr_hash_t *externals_to_pin, svn_ra_session_t *ra_session, svn_client_ctx_t *ctx, apr_pool_t *scratch_pool) @@ -1806,6 +2610,7 @@ repos_to_wc_copy_locked(svn_boolean_t *t svn_client__copy_pair_t *), same_repositories, ignore_externals, + pin_externals, externals_to_pin, ra_session, ctx, iterpool)); } svn_pool_destroy(iterpool); @@ -1818,6 +2623,8 @@ repos_to_wc_copy(svn_boolean_t *timestam const apr_array_header_t *copy_pairs, svn_boolean_t make_parents, svn_boolean_t ignore_externals, + svn_boolean_t pin_externals, + const apr_hash_t *externals_to_pin, svn_client_ctx_t *ctx, apr_pool_t *pool) { @@ -1926,6 +2733,7 @@ repos_to_wc_copy(svn_boolean_t *timestam SVN_WC__CALL_WITH_WRITE_LOCK( repos_to_wc_copy_locked(timestamp_sleep, copy_pairs, top_dst_path, ignore_externals, + pin_externals, externals_to_pin, ra_session, ctx, pool), ctx->wc_ctx, lock_abspath, FALSE, pool); @@ -1954,6 +2762,8 @@ try_copy(svn_boolean_t *timestamp_sleep, svn_boolean_t metadata_only, svn_boolean_t make_parents, svn_boolean_t ignore_externals, + svn_boolean_t pin_externals, + const apr_hash_t *externals_to_pin, const apr_hash_t *revprop_table, svn_commit_callback2_t commit_callback, void *commit_baton, @@ -2254,30 +3064,35 @@ try_copy(svn_boolean_t *timestamp_sleep, else { /* We ignore these values, so assert the default value */ - SVN_ERR_ASSERT(allow_mixed_revisions && !metadata_only); + SVN_ERR_ASSERT(allow_mixed_revisions); return svn_error_trace(do_wc_to_wc_copies(timestamp_sleep, - copy_pairs, ctx, pool)); + copy_pairs, + metadata_only, + pin_externals, + externals_to_pin, + ctx, pool)); } } else if ((! srcs_are_urls) && (dst_is_url)) { return svn_error_trace( wc_to_repos_copy(copy_pairs, make_parents, revprop_table, - commit_callback, commit_baton, ctx, pool)); + commit_callback, commit_baton, + pin_externals, externals_to_pin, ctx, pool)); } else if ((srcs_are_urls) && (! dst_is_url)) { return svn_error_trace( repos_to_wc_copy(timestamp_sleep, copy_pairs, make_parents, ignore_externals, - ctx, pool)); + pin_externals, externals_to_pin, ctx, pool)); } else { return svn_error_trace( repos_to_repos_copy(copy_pairs, make_parents, revprop_table, commit_callback, commit_baton, ctx, is_move, - pool)); + pin_externals, externals_to_pin, pool)); } } @@ -2285,11 +3100,14 @@ try_copy(svn_boolean_t *timestamp_sleep, /* Public Interfaces */ svn_error_t * -svn_client_copy6(const apr_array_header_t *sources, +svn_client_copy7(const apr_array_header_t *sources, const char *dst_path, svn_boolean_t copy_as_child, svn_boolean_t make_parents, svn_boolean_t ignore_externals, + svn_boolean_t metadata_only, + svn_boolean_t pin_externals, + const apr_hash_t *externals_to_pin, const apr_hash_t *revprop_table, svn_commit_callback2_t commit_callback, void *commit_baton, @@ -2308,9 +3126,11 @@ svn_client_copy6(const apr_array_header_ sources, dst_path, FALSE /* is_move */, TRUE /* allow_mixed_revisions */, - FALSE /* metadata_only */, + metadata_only, make_parents, ignore_externals, + pin_externals, + externals_to_pin, revprop_table, commit_callback, commit_baton, ctx, @@ -2342,9 +3162,11 @@ svn_client_copy6(const apr_array_header_ sources, dst_path, FALSE /* is_move */, TRUE /* allow_mixed_revisions */, - FALSE /* metadata_only */, + metadata_only, make_parents, ignore_externals, + pin_externals, + externals_to_pin, revprop_table, commit_callback, commit_baton, ctx, @@ -2406,6 +3228,8 @@ svn_client_move7(const apr_array_header_ metadata_only, make_parents, FALSE /* ignore_externals */, + FALSE /* pin_externals */, + NULL /* externals_to_pin */, revprop_table, commit_callback, commit_baton, ctx, @@ -2439,6 +3263,8 @@ svn_client_move7(const apr_array_header_ metadata_only, make_parents, FALSE /* ignore_externals */, + FALSE /* pin_externals */, + NULL /* externals_to_pin */, revprop_table, commit_callback, commit_baton, ctx, Modified: subversion/branches/reuse-ra-session/subversion/libsvn_client/deprecated.c URL: http://svn.apache.org/viewvc/subversion/branches/reuse-ra-session/subversion/libsvn_client/deprecated.c?rev=1662177&r1=1662176&r2=1662177&view=diff ============================================================================== --- subversion/branches/reuse-ra-session/subversion/libsvn_client/deprecated.c (original) +++ subversion/branches/reuse-ra-session/subversion/libsvn_client/deprecated.c Wed Feb 25 08:15:39 2015 @@ -627,6 +627,28 @@ svn_client_commit(svn_client_commit_info /*** From copy.c ***/ svn_error_t * +svn_client_copy6(const apr_array_header_t *sources, + const char *dst_path, + svn_boolean_t copy_as_child, + svn_boolean_t make_parents, + svn_boolean_t ignore_externals, + const apr_hash_t *revprop_table, + svn_commit_callback2_t commit_callback, + void *commit_baton, + svn_client_ctx_t *ctx, + apr_pool_t *pool) +{ + return svn_error_trace(svn_client_copy7(sources, dst_path, copy_as_child, + make_parents, ignore_externals, + FALSE /* metadata_only */, + FALSE /* pin_externals */, + NULL /* externals_to_pin */, + revprop_table, + commit_callback, commit_baton, + ctx, pool)); +} + +svn_error_t * svn_client_copy5(svn_commit_info_t **commit_info_p, const apr_array_header_t *sources, const char *dst_path, Modified: subversion/branches/reuse-ra-session/subversion/libsvn_client/diff.c URL: http://svn.apache.org/viewvc/subversion/branches/reuse-ra-session/subversion/libsvn_client/diff.c?rev=1662177&r1=1662176&r2=1662177&view=diff ============================================================================== --- subversion/branches/reuse-ra-session/subversion/libsvn_client/diff.c (original) +++ subversion/branches/reuse-ra-session/subversion/libsvn_client/diff.c Wed Feb 25 08:15:39 2015 @@ -2209,7 +2209,7 @@ do_diff(const char **root_relpath, SVN_ERR(svn_dirent_get_absolute(&abspath1, path_or_url1, scratch_pool)); - SVN_ERR(svn_dirent_get_absolute(&abspath2, path_or_url2, + SVN_ERR(svn_dirent_get_absolute(&abspath2, path_or_url2, scratch_pool)); /* ### What about ddi? */ Modified: subversion/branches/reuse-ra-session/subversion/libsvn_client/externals.c URL: http://svn.apache.org/viewvc/subversion/branches/reuse-ra-session/subversion/libsvn_client/externals.c?rev=1662177&r1=1662176&r2=1662177&view=diff ============================================================================== --- subversion/branches/reuse-ra-session/subversion/libsvn_client/externals.c (original) +++ subversion/branches/reuse-ra-session/subversion/libsvn_client/externals.c Wed Feb 25 08:15:39 2015 @@ -171,7 +171,7 @@ switch_dir_external(const char *local_ab if (revision->kind == svn_opt_revision_number) external_rev = revision->value.number; - /* + /* * The code below assumes existing versioned paths are *not* part of * the external's defining working copy. * The working copy library does not support registering externals @@ -242,6 +242,20 @@ switch_dir_external(const char *local_ab FALSE, FALSE, FALSE, TRUE, FALSE, TRUE, ra_session, ctx, subpool)); + + /* We just decided that this existing directory is an external, + so update the external registry with this information, like + when checking out an external */ + SVN_ERR(svn_wc__external_register(ctx->wc_ctx, + defining_abspath, + local_abspath, svn_node_dir, + repos_root_url, repos_uuid, + svn_uri_skip_ancestor(repos_root_url, + url, pool), + external_peg_rev, + external_rev, + pool)); + svn_pool_destroy(subpool); goto cleanup; } Modified: subversion/branches/reuse-ra-session/subversion/libsvn_client/log.c URL: http://svn.apache.org/viewvc/subversion/branches/reuse-ra-session/subversion/libsvn_client/log.c?rev=1662177&r1=1662176&r2=1662177&view=diff ============================================================================== --- subversion/branches/reuse-ra-session/subversion/libsvn_client/log.c (original) +++ subversion/branches/reuse-ra-session/subversion/libsvn_client/log.c Wed Feb 25 08:15:39 2015 @@ -307,7 +307,7 @@ limit_receiver(void *baton, svn_log_entr The limitations on TARGETS specified by svn_client_log5 are enforced here. So TARGETS can only contain a single WC path or a URL and zero or more - relative paths -- anything else will raise an error. + relative paths -- anything else will raise an error. PEG_REVISION, TARGETS, and CTX are as per svn_client_log5. @@ -646,7 +646,7 @@ run_ra_get_log(apr_array_header_t *revis apr_array_header_t *log_segments, svn_client__pathrev_t *actual_loc, svn_ra_session_t *ra_session, - /* The following are as per svn_client_log5. */ + /* The following are as per svn_client_log5. */ const apr_array_header_t *targets, int limit, svn_boolean_t discover_changed_paths, @@ -765,7 +765,7 @@ run_ra_get_log(apr_array_header_t *revis So to be safe we handle that case. */ if (matching_segment == NULL) continue; - + /* A segment with a NULL path means there is gap in the history. We'll just proceed and let svn_ra_get_log2 fail with a useful error...*/
