Index: subversion/include/svn_client.h =================================================================== --- subversion/include/svn_client.h (revision 1405691) +++ subversion/include/svn_client.h (working copy) @@ -5280,7 +5280,7 @@ * @{ */ -/** The type of function invoked by svn_client_list2() to report the details +/** The type of function invoked by svn_client_list3() to report the details * of each directory entry being listed. * * @a baton is the baton that was passed to the caller. @a path is the @@ -5290,11 +5290,41 @@ * the entry's lock, if it is locked and if lock information is being * reported by the caller; otherwise @a lock is NULL. @a abs_path is the * repository path of the top node of the list operation; it is relative to - * the repository root and begins with "/". @a pool may be used for - * temporary allocations. + * the repository root and begins with "/". * - * @since New in 1.4. + * If svn_client_list3() was called with @a include_externals set to TRUE, + * @a notify_external_start, @a notify_external_end, @a external_parent_url + * and @a external_target will be set. @a notify_external_start and + * @a notify_external_end is used to control the list output of externals. + * @a external_parent_url is url of the directory which has the externals + * definitions. @a external_target is the target subdirectory of externals + * definitions. + + * @a pool may be used for temporary allocations. + * + * @since New in 1.8. */ +typedef svn_error_t *(*svn_client_list_func2_t)( + void *baton, + const char *path, + const svn_dirent_t *dirent, + const svn_lock_t *lock, + const char *abs_path, + svn_boolean_t notify_external_start, + svn_boolean_t notify_external_end, + const char *external_parent_url, + const char *external_target, + apr_pool_t *pool); + +/** + * Similar to #svn_client_list_func2_t, but without any information about + * externals definitions. + * + * @deprecated Provided for backward compatibility with the 1.7 API. + * + * @since New in 1.4 + * + * */ typedef svn_error_t *(*svn_client_list_func_t)(void *baton, const char *path, const svn_dirent_t *dirent, @@ -5318,6 +5348,10 @@ * * If @a fetch_locks is TRUE, include locks when reporting directory entries. * + * If @a include_externals is TRUE, also list all external items + * reached by recursion. @a depth value passed to the original list target + * applies for the externals also. + * * Use @a pool for temporary allocations. * * Use authentication baton cached in @a ctx to authenticate against the @@ -5334,8 +5368,30 @@ * otherwise simply bitwise OR together the combination of @c SVN_DIRENT_ * fields you care about. * + * @since New in 1.8. + */ +svn_error_t * +svn_client_list3(const char *path_or_url, + const svn_opt_revision_t *peg_revision, + const svn_opt_revision_t *revision, + svn_depth_t depth, + apr_uint32_t dirent_fields, + svn_boolean_t fetch_locks, + svn_boolean_t include_externals, + svn_client_list_func2_t list_func, + void *baton, + svn_client_ctx_t *ctx, + apr_pool_t *pool); + + +/** Similar to svn_client_list3(), but with @a include_externals set to FALSE, + * and using a #svn_client_list_func2_t as callback. + * + * @deprecated Provided for backwards compatibility with the 1.7 API. + * * @since New in 1.5. */ +SVN_DEPRECATED svn_error_t * svn_client_list2(const char *path_or_url, const svn_opt_revision_t *peg_revision, Index: subversion/libsvn_client/client.h =================================================================== --- subversion/libsvn_client/client.h (revision 1405691) +++ subversion/libsvn_client/client.h (working copy) @@ -1009,7 +1009,6 @@ svn_client_ctx_t *ctx, apr_pool_t *pool); - /* Perform status operations on each external in EXTERNAL_MAP, a const char * local_abspath of all externals mapping to the const char* defining_abspath. All other options are the same as those passed to svn_client_status(). */ @@ -1024,6 +1023,21 @@ void *status_baton, apr_pool_t *pool); +/* List external items defined on each external in EXTERNALS, a const char * + externals_parent_url(url of the directory which has the externals + definitions) of all externals mapping to the const char * externals_desc + (externals description text). All other options are the same as those + passed to svn_client_list(). */ +svn_error_t * +svn_client__list_externals(apr_hash_t *externals, + svn_depth_t depth, + apr_uint32_t dirent_fields, + svn_boolean_t fetch_locks, + svn_client_list_func2_t list_func, + void *baton, + svn_client_ctx_t *ctx, + apr_pool_t *pool); + /* Baton for svn_client__dirent_fetcher */ struct svn_client__dirent_fetcher_baton_t { Index: subversion/libsvn_client/deprecated.c =================================================================== --- subversion/libsvn_client/deprecated.c (revision 1405691) +++ subversion/libsvn_client/deprecated.c (working copy) @@ -1269,7 +1269,75 @@ } /*** From list.c ***/ + +/* Baton for use with wrap_list_func */ +struct list_func_wrapper_baton { + void *baton; + svn_client_list_func_t list_func; +}; + +/* This implements svn_client_list_func2_t */ +static svn_error_t * +list_func_wrapper(void *baton, + const char *path, + const svn_dirent_t *dirent, + const svn_lock_t *lock, + const char *abs_path, + svn_boolean_t notify_external_start, + svn_boolean_t notify_external_end, + const char *external_parent_url, + const char *external_target, + apr_pool_t *pool) +{ + struct list_func_wrapper_baton *lfwb = baton; + + if (lfwb->list_func) + return lfwb->list_func(lfwb->baton, path, dirent, lock, abs_path, pool); + + return SVN_NO_ERROR; +} + +static void +wrap_list_func(svn_client_list_func2_t *list_func2, + void **list_func2_baton, + svn_client_list_func_t list_func, + void *baton, + apr_pool_t *pool) +{ + struct list_func_wrapper_baton *lfwb = apr_palloc(pool, sizeof(*lfwb)); + + /* Set the user provided old format callback in the baton. */ + lfwb->baton = baton; + lfwb->list_func = list_func; + + *list_func2_baton = lfwb; + *list_func2 = list_func_wrapper; +} + svn_error_t * +svn_client_list2(const char *path_or_url, + const svn_opt_revision_t *peg_revision, + const svn_opt_revision_t *revision, + svn_depth_t depth, + apr_uint32_t dirent_fields, + svn_boolean_t fetch_locks, + svn_client_list_func_t list_func, + void *baton, + svn_client_ctx_t *ctx, + apr_pool_t *pool) +{ + svn_client_list_func2_t list_func2; + void *list_func2_baton; + + wrap_list_func(&list_func2, &list_func2_baton, list_func, baton, pool); + + return svn_client_list3(path_or_url, peg_revision, revision, depth, + dirent_fields, fetch_locks, + FALSE /* include externals */, + list_func2, list_func2_baton, ctx, pool); +} + +svn_error_t * svn_client_list(const char *path_or_url, const svn_opt_revision_t *peg_revision, const svn_opt_revision_t *revision, Index: subversion/libsvn_client/externals.c =================================================================== --- subversion/libsvn_client/externals.c (revision 1405691) +++ subversion/libsvn_client/externals.c (working copy) @@ -1180,3 +1180,95 @@ return SVN_NO_ERROR; } + +svn_error_t * +svn_client__list_externals(apr_hash_t *externals, + svn_depth_t depth, + apr_uint32_t dirent_fields, + svn_boolean_t fetch_locks, + svn_client_list_func2_t list_func, + void *baton, + svn_client_ctx_t *ctx, + apr_pool_t *pool) +{ + apr_pool_t *iterpool = svn_pool_create(pool); + apr_pool_t *sub_iterpool = svn_pool_create(pool); + apr_hash_index_t *hi; + + for (hi = apr_hash_first(pool, externals); + hi; + hi = apr_hash_next(hi)) + { + const char *externals_parent_url = svn__apr_hash_index_key(hi); + svn_string_t *externals_desc = svn__apr_hash_index_val(hi); + const char *externals_parent_repos_root_url; + apr_array_header_t *items; + int i; + + svn_pool_clear(iterpool); + + items = apr_array_make(iterpool, 1, sizeof(svn_wc_external_item2_t*)); + + SVN_ERR(svn_client_get_repos_root(&externals_parent_repos_root_url, + NULL /* uuid */, + externals_parent_url, ctx, + iterpool, iterpool)); + + SVN_ERR(svn_wc_parse_externals_description3(&items, + externals_parent_url, + externals_desc->data, + FALSE, iterpool)); + + if (! items->nelts) + continue; + + for (i = 0; i < items->nelts; i++) + { + const char *resolved_url; + + svn_wc_external_item2_t *item = + APR_ARRAY_IDX(items, i, svn_wc_external_item2_t *); + + svn_pool_clear(sub_iterpool); + + SVN_ERR(svn_wc__resolve_relative_external_url( + &resolved_url, + item, + externals_parent_repos_root_url, + externals_parent_url, + sub_iterpool, sub_iterpool)); + + /* Notify that we're about to handle an external. */ + SVN_ERR(list_func(baton, NULL, NULL, NULL, NULL, + TRUE /*notify_external_start */, + FALSE /*notify_external_end */, + externals_parent_url, + item->target_dir, iterpool)); + + /* List the external */ + SVN_ERR(wrap_external_error( + ctx, item->target_dir, + svn_client_list3(resolved_url, + &item->peg_revision, + &item->revision, + depth, dirent_fields, + fetch_locks, + TRUE, + list_func, baton, ctx, + sub_iterpool), + sub_iterpool)); + + /* Notify that we are done with external handling. It is helpful + when list is run in xml mode. */ + SVN_ERR(list_func(baton, NULL, NULL, NULL, NULL, + FALSE /*notify_external_start */, + TRUE /*notify_external_end */, + NULL, NULL, iterpool)); + } + } + svn_pool_destroy(sub_iterpool); + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + Index: subversion/libsvn_client/list.c =================================================================== --- subversion/libsvn_client/list.c (revision 1405691) +++ subversion/libsvn_client/list.c (working copy) @@ -47,7 +47,12 @@ LOCKS, if non-NULL, is a hash mapping const char * paths to svn_lock_t objects and FS_PATH is the absolute filesystem path of the RA session. - Use POOL for temporary allocations. + Use SCRATCH_POOL for temporary allocations. + + If INCLUDE_EXTERNALS is true, set *EXTERNALS to a hash table whose keys + are URLs of the directory which has external definitions, and whose values + are the externals description text. Allocate the hash's keys and + values in RESULT_POOL. */ static svn_error_t * get_dir_contents(apr_uint32_t dirent_fields, @@ -58,23 +63,30 @@ const char *fs_path, svn_depth_t depth, svn_client_ctx_t *ctx, - svn_client_list_func_t list_func, + svn_boolean_t include_externals, + apr_hash_t **externals, + svn_client_list_func2_t list_func, void *baton, - apr_pool_t *pool) + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) { apr_hash_t *tmpdirents; - apr_pool_t *iterpool = svn_pool_create(pool); + apr_pool_t *iterpool = svn_pool_create(scratch_pool); apr_array_header_t *array; svn_error_t *err; + apr_hash_t *prop_hash = NULL; + const svn_string_t *prop_val; int i; if (depth == svn_depth_empty) return SVN_NO_ERROR; - - /* Get the directory's entries, but not its props. Ignore any - not-authorized errors. */ - err = svn_ra_get_dir2(ra_session, &tmpdirents, NULL, NULL, - dir, rev, dirent_fields, pool); + + /* Get the directory's entries. If include_externals is set, get its + properties also. Ignore any not-authorized errors. */ + err = svn_ra_get_dir2(ra_session, &tmpdirents, NULL, + include_externals ? &prop_hash : NULL, + dir, rev, dirent_fields, scratch_pool); + if (err && ((err->apr_err == SVN_ERR_RA_NOT_AUTHORIZED) || (err->apr_err == SVN_ERR_RA_DAV_FORBIDDEN))) { @@ -82,12 +94,27 @@ return SVN_NO_ERROR; } SVN_ERR(err); + + if (prop_hash + && (prop_val = apr_hash_get(prop_hash, SVN_PROP_EXTERNALS, + APR_HASH_KEY_STRING))) + { + const char *url; + SVN_ERR(svn_ra_get_session_url(ra_session, &url, scratch_pool)); + + apr_hash_set(*externals, svn_path_url_add_component2(url, dir, + result_pool), + APR_HASH_KEY_STRING, svn_string_dup(prop_val, + result_pool)); + } + if (ctx->cancel_func) SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); /* Sort the hash, so we can call the callback in a "deterministic" order. */ - array = svn_sort__hash(tmpdirents, svn_sort_compare_items_lexically, pool); + array = svn_sort__hash(tmpdirents, svn_sort_compare_items_lexically, + scratch_pool); for (i = 0; i < array->nelts; ++i) { svn_sort__item_t *item = &APR_ARRAY_IDX(array, i, svn_sort__item_t); @@ -110,12 +137,17 @@ if (the_ent->kind == svn_node_file || depth == svn_depth_immediates || depth == svn_depth_infinity) - SVN_ERR(list_func(baton, path, the_ent, lock, fs_path, iterpool)); - + SVN_ERR(list_func(baton, path, the_ent, lock, fs_path, FALSE, FALSE, + NULL, NULL, iterpool)); + + /* If include_externals is set, populate the *externals hash table + recursively for all directory entries. */ if (depth == svn_depth_infinity && the_ent->kind == svn_node_dir) SVN_ERR(get_dir_contents(dirent_fields, path, rev, - ra_session, locks, fs_path, depth, ctx, - list_func, baton, iterpool)); + ra_session, locks, fs_path, depth, ctx, + include_externals, + include_externals ? externals : NULL, + list_func, baton, result_pool, iterpool)); } svn_pool_destroy(iterpool); @@ -224,14 +256,16 @@ return SVN_NO_ERROR; } + svn_error_t * -svn_client_list2(const char *path_or_url, +svn_client_list3(const char *path_or_url, const svn_opt_revision_t *peg_revision, const svn_opt_revision_t *revision, svn_depth_t depth, apr_uint32_t dirent_fields, svn_boolean_t fetch_locks, - svn_client_list_func_t list_func, + svn_boolean_t include_externals, + svn_client_list_func2_t list_func, void *baton, svn_client_ctx_t *ctx, apr_pool_t *pool) @@ -242,6 +276,7 @@ const char *fs_path; svn_error_t *err; apr_hash_t *locks; + apr_hash_t *externals = apr_hash_make(pool); /* We use the kind field to determine if we should recurse, so we always need it. */ @@ -284,14 +319,29 @@ SVN_ERR(list_func(baton, "", dirent, locks ? (apr_hash_get(locks, fs_path, APR_HASH_KEY_STRING)) - : NULL, fs_path, pool)); + : NULL, fs_path, FALSE, FALSE, NULL, NULL, pool)); if (dirent->kind == svn_node_dir && (depth == svn_depth_files || depth == svn_depth_immediates || depth == svn_depth_infinity)) SVN_ERR(get_dir_contents(dirent_fields, "", loc->rev, ra_session, locks, - fs_path, depth, ctx, list_func, baton, pool)); - + fs_path, depth, ctx, include_externals, + include_externals ? &externals : NULL, + list_func, baton, + pool, pool)); + + /* We handle externals after listing entries under path_or_url, so that + handling external items (and any errors therefrom) doesn't delay + the primary operation. */ + if (include_externals && externals && apr_hash_count(externals)) + { + /* The 'externals' hash populated by get_dir_contents() is processed + here. */ + SVN_ERR(svn_client__list_externals(externals, depth, dirent_fields, + fetch_locks, list_func, baton, + ctx, pool)); + } + return SVN_NO_ERROR; } Index: subversion/svn/list-cmd.c =================================================================== --- subversion/svn/list-cmd.c (revision 1405691) +++ subversion/svn/list-cmd.c (working copy) @@ -44,7 +44,7 @@ svn_client_ctx_t *ctx; }; -/* This implements the svn_client_list_func_t API, printing a single +/* This implements the svn_client_list_func2_t API, printing a single directory entry in text format. */ static svn_error_t * print_dirent(void *baton, @@ -52,6 +52,10 @@ const svn_dirent_t *dirent, const svn_lock_t *lock, const char *abs_path, + svn_boolean_t notify_external_start, + svn_boolean_t notify_external_end, + const char *external_parent_url, + const char *external_target, apr_pool_t *pool) { struct print_baton *pb = baton; @@ -59,6 +63,16 @@ static const char *time_format_long = NULL; static const char *time_format_short = NULL; + if (notify_external_start) + { + return svn_cmdline_printf(pool, "Externals on '%s - %s':\n", + external_parent_url, + external_target); + } + + if (notify_external_end) + return SVN_NO_ERROR; + if (time_format_long == NULL) time_format_long = _("%b %d %H:%M"); if (time_format_short == NULL) @@ -133,7 +147,7 @@ } -/* This implements the svn_client_list_func_t API, printing a single dirent +/* This implements the svn_client_list_func2_t API, printing a single dirent in XML format. */ static svn_error_t * print_dirent_xml(void *baton, @@ -141,12 +155,33 @@ const svn_dirent_t *dirent, const svn_lock_t *lock, const char *abs_path, + svn_boolean_t notify_external_start, + svn_boolean_t notify_external_end, + const char *external_parent_url, + const char *external_target, apr_pool_t *pool) { struct print_baton *pb = baton; const char *entryname; - svn_stringbuf_t *sb; + svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool); + if (notify_external_start) + { + svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "external", + "parent_url", external_parent_url, + "target", external_target, + NULL); + + return svn_cl__error_checked_fputs(sb->data, stdout); + } + + if (notify_external_end) + { + svn_xml_make_close_tag(&sb, pool, "external"); + + return svn_cl__error_checked_fputs(sb->data, stdout); + } + if (strcmp(path, "") == 0) { if (dirent->kind == svn_node_file) @@ -163,8 +198,6 @@ if (pb->ctx->cancel_func) SVN_ERR(pb->ctx->cancel_func(pb->ctx->cancel_baton)); - sb = svn_stringbuf_create_empty(pool); - svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "entry", "kind", svn_cl__node_kind_str_xml(dirent->kind), NULL); @@ -225,6 +258,8 @@ struct print_baton pb; svn_boolean_t seen_nonexistent_target = FALSE; svn_error_t *err; + svn_error_t *externals_err = SVN_NO_ERROR; + struct svn_cl__check_externals_failed_notify_baton nwb; SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, opt_state->targets, @@ -266,6 +301,15 @@ if (opt_state->depth == svn_depth_unknown) opt_state->depth = svn_depth_immediates; + if (opt_state->include_externals) + { + nwb.wrapped_func = ctx->notify_func2; + nwb.wrapped_baton = ctx->notify_baton2; + nwb.had_externals_error = FALSE; + ctx->notify_func2 = svn_cl__check_externals_failed_notify_wrapper; + ctx->notify_baton2 = &nwb; + } + /* For each target, try to list it. */ for (i = 0; i < targets->nelts; i++) { @@ -290,11 +334,12 @@ SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout)); } - err = svn_client_list2(truepath, &peg_revision, + err = svn_client_list3(truepath, &peg_revision, &(opt_state->start_revision), opt_state->depth, dirent_fields, (opt_state->xml || opt_state->verbose), + opt_state->include_externals, opt_state->xml ? print_dirent_xml : print_dirent, &pb, ctx, subpool); @@ -322,14 +367,24 @@ } svn_pool_destroy(subpool); + + if (opt_state->include_externals && nwb.had_externals_error) + { + externals_err = svn_error_create(SVN_ERR_CL_ERROR_PROCESSING_EXTERNALS, + NULL, + _("Failure occurred processing one or " + "more externals definitions")); + } if (opt_state->xml && ! opt_state->incremental) SVN_ERR(svn_cl__xml_print_footer("lists", pool)); if (seen_nonexistent_target) - return svn_error_create( - SVN_ERR_ILLEGAL_TARGET, NULL, - _("Could not list all targets because some targets don't exist")); + { + err = svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL, + _("Could not list all targets because some targets don't exist")); + return svn_error_compose_create(externals_err, err); + } else - return SVN_NO_ERROR; + return svn_error_compose_create(externals_err, err); } Index: subversion/svn/main.c =================================================================== --- subversion/svn/main.c (revision 1405691) +++ subversion/svn/main.c (working copy) @@ -356,11 +356,7 @@ " " "Please run 'svn update' instead.")}, {"include-externals", opt_include_externals, 0, - N_("Also commit file and dir externals reached by\n" - " " - "recursion. This does not include externals with a\n" - " " - "fixed revision. (See the svn:externals property)")}, + N_("include externals definitions")}, {"show-inherited-props", opt_show_inherited_props, 0, N_("retrieve target's inherited properties")}, {"search", opt_search, 1, @@ -622,7 +618,8 @@ " If locked, the letter 'O'. (Use 'svn info URL' to see details)\n" " Size (in bytes)\n" " Date and time of the last commit\n"), - {'r', 'v', 'R', opt_depth, opt_incremental, opt_xml} }, + {'r', 'v', 'R', opt_depth, opt_incremental, opt_xml, + opt_include_externals} }, { "lock", svn_cl__lock, {0}, N_ ("Lock working copy paths or URLs in the repository, so that\n" Index: subversion/tests/cmdline/externals_tests.py =================================================================== --- subversion/tests/cmdline/externals_tests.py (revision 1405691) +++ subversion/tests/cmdline/externals_tests.py (working copy) @@ -2131,7 +2131,7 @@ actions.run_and_verify_update(wc_dir, expected_output, expected_disk, expected_status, None, None, None, None, None, True, wc_dir) -def include_externals(sbox): +def commit_include_externals(sbox): "commit --include-externals" # svntest.factory.make(sbox, """ # mkdir Z @@ -2866,6 +2866,55 @@ "OUTPUT", expected_stdout, [], 0, 'copy', repo_url + '/A/C', sbox.ospath('External-WC-to-URL-Copy')) +@Issue(4225) +def list_include_externals(sbox): + "list with --include-externals" + + externals_test_setup(sbox) + + wc_dir = sbox.wc_dir + repo_url = sbox.repo_url + + svntest.actions.run_and_verify_svn(None, None, [], + 'checkout', + repo_url, wc_dir) + + B_path = sbox.ospath("A/B") + C_path = sbox.ospath("A/C") + + B_url = repo_url + "/A/B" + C_url = repo_url + "/A/C" + + expected_stdout = verify.UnorderedOutput([ + "E/" + "\n", + "F/" + "\n", + "lambda" + "\n", + "Externals on " + "'" + B_url + " - " + "gamma" + "'" + ":" + "\n", + "gamma" + "\n"]) + + exit_code, stdout, stderr = svntest.actions.run_and_verify_svn2( + "OUTPUT", expected_stdout, [], 0, 'ls', '--include-externals', B_path) + + exit_code, stdout, stderr = svntest.actions.run_and_verify_svn2( + "OUTPUT", expected_stdout, [], 0, 'ls', '--include-externals', B_url) + + expected_stdout = verify.UnorderedOutput([ + "Externals on " + "'" + C_url + " - " + "exdir_G" + "'" + ":" + "\n", + "pi" + "\n", + "rho" + "\n", + "tau" + "\n", + "Externals on " + "'" + C_url + " - " + "exdir_H" + "'" + ":" + "\n", + "chi" + "\n", + "omega" + "\n", + "psi" + "\n"]) + + exit_code, stdout, stderr = svntest.actions.run_and_verify_svn2( + "OUTPUT", expected_stdout, [], 0, 'ls', '--include-externals', C_path) + + exit_code, stdout, stderr = svntest.actions.run_and_verify_svn2( + "OUTPUT", expected_stdout, [], 0, 'ls', '--include-externals', C_url) + + ######################################################################## # Run the tests @@ -2907,12 +2956,13 @@ file_externals_different_url, file_external_in_unversioned, copy_file_externals, - include_externals, + commit_include_externals, include_immediate_dir_externals, shadowing, remap_file_external_with_prop_del, dir_external_with_dash_r_only, url_to_wc_copy_of_externals, + list_include_externals, ] if __name__ == '__main__':