On 11/04/2011 12:22 PM, Julian Foad wrote: > Hi Neels. Brief response. ...yet a first class review, thanks a lot Julian!
> Looks like a good improvement. > > svn_wc__committable_external_info_t: Use the new 'svn_kind_t' instead of > svn_node_kind_t. doh! > svn_client_commit6(): Could you extract the main chunk of added code as a > separate function? That would help me (the reader) quickly understand how > much of the local state it does *not* touch. Good idea, done that. > harvest_committables(): Would the 'is_explicit_target' parameter be better > named something like 'include_file_externals'? I'm not sure if that would > better reflect its purpose in that function; I haven't got my head around it > well enough, so just asking. Not sure -- while that's the only use for it currently, it does reflect a more general fact about how LOCAL_ABSPATH was reached. Tweaked the doc. Good? > svn_client_commit6(): In the doc string, of instead of "If A and B, all file > respectively dir externals as defined ...", I suggest "If A and/or B, all > file and/or dir externals (respectively) as defined by ...". Rationale: > "respectively" isn't used as a conjunction word in English: > <http://www.transblawg.eu/index.php?/archives/870-Resp.-and-other-non-existent-English-wordsNicht-existente-englische-Woerter.html>. heh interesting link; I didn't mean the German "Beziehungsweise", I meant the English "respectively" with its ordering meaning. I fixed its ill placement, hope it's intelligible now. > In the same doc string: mention the TODO about --depth=immediates skipping > dir externals, that you documented inside the function. Done. > svn_wc__committable_externals_below(): Document the 'immediates_only' > parameter. Refactored 'immediates_only' to a 'depth' parameter, foreshadowing future use and possibly clarifying things. 'immediates_only' is now only an arg on the __db_ function, where it is documented. ...I also caught a wrongly placed '{' and a missing 'static'. And a "New function" missing from the log. I'm still not committing because I have just suggested to flip default behavior and provide an --exclude-externals option instead, in the other mail... Have attached the current patch state FYI. Thanks again! ~Neels
Introduce the commit argument --include-externals. Most prominent changes: - File externals are no longer committed by default. - When option --include-externals is passed to 'svn commit', *all* externals reached by recursion are included in the commit (dir & file alike). - Dir externals nested "behind" unversioned dirs or "foreign" WCs are now reached by recursion. Previous default was to include all file externals, not dirs. On the API level, separate dir/file arguments allow for this previous behavior, mainly for keeping svn_client_commit5()'s behavior unchanged. See also http://svn.haxx.se/dev/archive-2011-08/0620.shtml ### The commit API can't handle file externals inside unversioned dirs properly yet. Additional SQL currently deliberately excludes them (see STMT_SELECT_COMMITTABLE_EXTERNALS_BELOW). * subversion/include/private/svn_wc_private.h (svn_wc__committable_external_info_t): New struct for: (svn_wc__committable_externals_below): New function. * subversion/include/svn_client.h, (svn_client_commit6): New function, adding INCLUDE_FILE_EXTERNALS and INCLUDE_DIR_EXTERNALS arguments over svn_client_commit5(). * subversion/libsvn_client/commit.c (append_externals_as_explicit_targets): New static function. (svn_client_commit6): New function, see svn_client.h ^. (svn_client_commit5): Call svn_client_commit6() with INCLUDE_FILE_EXTERNALS = TRUE and INCLUDE_DIR_EXTERNALS = FALSE. * subversion/libsvn_client/commit_util.c (harvest_committables): New argument IS_EXPLICIT_TARGET; pass as FALSE upon recursion. Skip file externals that are not explicit targets, so this function now skips all externals during recursion (see docstring). (svn_client__harvest_committables): Pass IS_EXPLICIT_TARGET as TRUE: initial harvest_committables() call. (harvest_copy_committables): Pass IS_EXPLICIT_TARGET as TRUE, but has no effect since, inside harvest_committables(), COPY_MODE will be TRUE and all file externals will be excluded anyway. * subversion/libsvn_wc/externals.c (is_external_rolled_out): New static function. (svn_wc__committable_externals_below): New function, see svn_wc_private.h ^. * subversion/libsvn_wc/wc_db.h, * subversion/libsvn_wc/wc_db.c (svn_wc__db_committable_externals_below): New function for STMT_SELECT_COMMITTABLE_EXTERNALS_BELOW. * subversion/libsvn_wc/wc-queries.sql (STMT_SELECT_COMMITTABLE_EXTERNALS_BELOW): New SQL. * subversion/svn/cl.h (svn_cl__opt_state_t): Add INCLUDE_EXTERNALS for --include-externals. * subversion/svn/commit-cmd.c (svn_cl__commit): Call svn_client_commit6() instead of &5(). * subversion/svn/main.c (svn_cl__longopt_t, svn_cl__options, svn_cl__cmd_table, main): Add OPT_INCLUDE_EXTERNALS, enable --include-externals for 'commit'. * subversion/tests/cmdline/externals_tests.py (include_externals): New test, PASS. (include_immediate_dir_externals): New test, XFAIL. (test_list): Append above two tests. Index: subversion/include/private/svn_wc_private.h =================================================================== --- subversion/include/private/svn_wc_private.h (revision 1197810) +++ subversion/include/private/svn_wc_private.h (working copy) @@ -131,6 +131,51 @@ svn_wc__read_external_info(svn_node_kind apr_pool_t *result_pool, apr_pool_t *scratch_pool); +/** See svn_wc__committable_externals_below(). */ +typedef struct svn_wc__committable_external_info_t { + + /* The local absolute path where the external should be checked out. */ + const char *local_abspath; + + /* The relpath part of the source URL the external should be checked out + * from. */ + const char *repos_relpath; + + /* The root URL part of the source URL the external should be checked out + * from. */ + const char *repos_root_url; + + /* Set to either svn_kind_file or svn_kind_dir. */ + svn_kind_t kind; + +} svn_wc__committable_external_info_t; + +/* Add svn_wc__committable_external_info_t* items to *EXTERNALS, describing + * 'committable' externals checked out below LOCAL_ABSPATH. Recursively find + * all nested externals (externals defined inside externals). + * + * In this context, a 'committable' external belongs to the same repository as + * LOCAL_ABSPATH, is not revision-pegged and is currently checked out in the + * WC. (Local modifications are not tested for.) + * + * *EXTERNALS must be initialized either to NULL or to a pointer created with + * apr_array_make(..., sizeof(svn_wc__committable_external_info_t *)). If + * *EXTERNALS is initialized to NULL, an array will be allocated from + * RESULT_POOL as necessary. If no committable externals are found, + * *EXTERNALS is left unchanged. + * + * DEPTH limits the recursion below LOCAL_ABSPATH. + * + * This function will not find externals defined in some parent WC above + * LOCAL_ABSPATH's WC-root. */ +svn_error_t * +svn_wc__committable_externals_below(apr_array_header_t **externals, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_depth_t depth, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + /* Gets a mapping from const char * local abspaths of externals to the const char * local abspath of where they are defined for all externals defined at or below LOCAL_ABSPATH. Index: subversion/include/svn_client.h =================================================================== --- subversion/include/svn_client.h (revision 1197810) +++ subversion/include/svn_client.h (working copy) @@ -1954,6 +1954,22 @@ svn_client_import(svn_client_commit_info * #TRUE, changes to descendants are only committed if they are itself * included via @a depth and targets. * + * If @a include_file_externals and/or @a include_dir_externals are #TRUE, + * also commit all file and/or dir externals (respectively) that are reached + * by recursion, with these exceptions: Never recurse to externals + * - that have a fixed revision or + * - that come from a different repository root URL (dir externals). + * These flags affect only recursion; externals that directly appear in @a + * targets are always included in the commit. + * + * ### TODO: currently, file externals hidden inside an unversioned dir are + * skipped deliberately, because we can't commit those yet. + * See STMT_SELECT_COMMITTABLE_EXTERNALS_BELOW. + * + * ### TODO: With @c depth_immediates, this function acts as if + * @a include_dir_externals was passed #FALSE, but caller expects + * immediate child dir externals to be included @c depth_empty. + * * When @a commit_as_operations is #TRUE it is possible to delete a node and * all its descendants by selecting just the root of the deletion. If it is * set to #FALSE this will raise an error. @@ -1967,6 +1983,26 @@ svn_client_import(svn_client_commit_info * * Use @a pool for any temporary allocations. * + * @since New in 1.8. + */ +svn_error_t * +svn_client_commit6(const apr_array_header_t *targets, + svn_depth_t depth, + svn_boolean_t keep_locks, + svn_boolean_t keep_changelists, + svn_boolean_t commit_as_operations, + svn_boolean_t include_file_externals, + svn_boolean_t include_dir_externals, + const apr_array_header_t *changelists, + const apr_hash_t *revprop_table, + svn_commit_callback2_t commit_callback, + void *commit_baton, + svn_client_ctx_t *ctx, + apr_pool_t *pool); + +/** + * Similar to svn_client_commit6(), but passes @a include_file_externals as + * TRUE and @a include_dir_externals as FALSE. * @since New in 1.7. */ svn_error_t * Index: subversion/libsvn_client/commit.c =================================================================== --- subversion/libsvn_client/commit.c (revision 1197810) +++ subversion/libsvn_client/commit.c (working copy) @@ -1168,12 +1168,113 @@ check_url_kind(void *baton, kind, scratch_pool)); } +/* Recurse into every target in REL_TARGETS, finding committable externals + * nested within. Append these to REL_TARGETS itself. The paths in REL_TARGETS + * are assumed to be / will be created relative to BASE_ABSPATH. The remaining + * arguments correspond to those of svn_client_commit6(). */ +static svn_error_t* +append_externals_as_explicit_targets(apr_array_header_t *rel_targets, + const char *base_abspath, + svn_boolean_t include_file_externals, + svn_boolean_t include_dir_externals, + svn_depth_t depth, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + int rel_targets_nelts_fixed; + int i; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + + /* Easy part of applying DEPTH to externals. */ + if (depth == svn_depth_empty) + { + /* Don't recurse. */ + return SVN_NO_ERROR; + } + else if (depth != svn_depth_infinity) + { + include_dir_externals = FALSE; + /* We slip in dir externals as explicit targets. When we do that, + * depth_immediates should become depth_empty for dir externals targets. + * But adding the dir external to the list of targets makes it get + * handled with depth_immediates itself, and thus will also include the + * immediate children of the dir external. So do dir externals only with + * depth_infinity or not at all. + * ### TODO: Maybe rework this (and svn_client_commit6()) into separate + * ### target lists, "duplicating" REL_TARGETS: one for the user's + * ### targets and one for the overlayed externals targets, and pass an + * ### appropriate depth for the externals targets in a separate call to + * ### svn_client__harvest_committables(). The only gain is correct + * ### handling of this very specific case: during 'svn commit + * ### --depth=immediates --include-externals', commit dir externals + * ### (only immediate children of a target) with depth_empty instead of + * ### not at all. No other effect. So not doing that for now. */ + } + + if (! (include_file_externals || include_dir_externals)) + return SVN_NO_ERROR; + + /* Iterate *and* grow REL_TARGETS at the same time. */ + rel_targets_nelts_fixed = rel_targets->nelts; + + for (i = 0; i < rel_targets_nelts_fixed; i++) + { + int j; + const char *target; + apr_array_header_t *externals = NULL; + + svn_pool_clear(iterpool); + + target = svn_dirent_join(base_abspath, + APR_ARRAY_IDX(rel_targets, i, const char *), + iterpool); + + /* ### TODO: Possible optimization: No need to do this for file targets. + * ### But what's cheaper, stat'ing the file system or querying the db? + * ### --> future. */ + + SVN_ERR(svn_wc__committable_externals_below(&externals, ctx->wc_ctx, + target, depth, + iterpool, iterpool)); + + if (externals != NULL) + { + const char *rel_target; + + for (j = 0; j < externals->nelts; j++) + { + svn_wc__committable_external_info_t *xinfo = + APR_ARRAY_IDX(externals, j, + svn_wc__committable_external_info_t *); + + if ((xinfo->kind == svn_kind_file && ! include_file_externals) + || (xinfo->kind == svn_kind_dir && ! include_dir_externals)) + continue; + + rel_target = svn_dirent_skip_ancestor(base_abspath, + xinfo->local_abspath); + + SVN_ERR_ASSERT(rel_target != NULL && *rel_target != '\0'); + + APR_ARRAY_PUSH(rel_targets, const char *) = + apr_pstrdup(result_pool, rel_target); + } + } + } + + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; +} + svn_error_t * -svn_client_commit5(const apr_array_header_t *targets, +svn_client_commit6(const apr_array_header_t *targets, svn_depth_t depth, svn_boolean_t keep_locks, svn_boolean_t keep_changelists, svn_boolean_t commit_as_operations, + svn_boolean_t include_file_externals, + svn_boolean_t include_dir_externals, const apr_array_header_t *changelists, const apr_hash_t *revprop_table, svn_commit_callback2_t commit_callback, @@ -1233,6 +1334,12 @@ svn_client_commit5(const apr_array_heade if (rel_targets->nelts == 0) APR_ARRAY_PUSH(rel_targets, const char *) = ""; + SVN_ERR(append_externals_as_explicit_targets(rel_targets, base_abspath, + include_file_externals, + include_dir_externals, + depth, ctx, + pool, pool)); + SVN_ERR(determine_lock_targets(&lock_targets, ctx->wc_ctx, base_abspath, rel_targets, pool, iterpool)); @@ -1603,3 +1710,25 @@ svn_client_commit5(const apr_array_heade return svn_error_trace(reconcile_errors(cmt_err, unlock_err, bump_err, pool)); } + +svn_error_t * +svn_client_commit5(const apr_array_header_t *targets, + svn_depth_t depth, + svn_boolean_t keep_locks, + svn_boolean_t keep_changelists, + svn_boolean_t commit_as_operations, + const apr_array_header_t *changelists, + 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_client_commit6(targets, depth, keep_locks, keep_changelists, + commit_as_operations, + TRUE, /* include_file_externals */ + FALSE, /* include_dir_externals */ + changelists, revprop_table, commit_callback, + commit_baton, ctx, pool); +} + Index: subversion/libsvn_client/commit_util.c =================================================================== --- subversion/libsvn_client/commit_util.c (revision 1197810) +++ subversion/libsvn_client/commit_util.c (working copy) @@ -410,6 +410,12 @@ bail_on_tree_conflicted_ancestor(svn_wc_ when harvesting committables; that is, don't add a path to COMMITTABLES unless it's a member of one of those changelists. + IS_EXPLICIT_TARGET should always be passed as TRUE, except when + harvest_committables() calls itself in recursion. This provides a way to + tell whether LOCAL_ABSPATH was an original target or whether it was reached + by recursing deeper into a dir target. (This is used to skip all file + externals that aren't explicit commit targets.) + If CANCEL_FUNC is non-null, call it with CANCEL_BATON to see if the user has cancelled the operation. @@ -428,6 +434,7 @@ harvest_committables(svn_wc_context_t *w apr_hash_t *changelists, svn_boolean_t skip_files, svn_boolean_t skip_dirs, + svn_boolean_t is_explicit_target, svn_client__check_url_kind_t check_url_func, void *check_url_baton, svn_cancel_func_t cancel_func, @@ -543,10 +550,24 @@ harvest_committables(svn_wc_context_t *w svn_dirent_local_style(local_abspath, scratch_pool)); } - /* ### in need of comment */ - if (copy_mode - && is_update_root - && db_kind == svn_node_file) + /* Handle file externals. + * (IS_UPDATE_ROOT is more generally defined, but at the moment this + * condition matches only file externals.) + * + * Don't copy files that svn:externals brought into the WC. So in copy_mode, + * even explicit targets are skipped. + * + * Exclude file externals from recursion. Hande file externals only when + * passed as explicit target. Note that svn_client_commit6() passes all + * committable externals in as explicit targets iff they count. + * + * Also note that dir externals will never be reached recursively by this + * function, since svn_wc__node_get_children_of_working_node() (used below + * to recurse) does not return switched subdirs. */ + if (is_update_root + && db_kind == svn_node_file + && (copy_mode + || ! is_explicit_target)) { return SVN_NO_ERROR; } @@ -841,6 +862,7 @@ harvest_committables(svn_wc_context_t *w changelists, (depth < svn_depth_files), (depth < svn_depth_immediates), + FALSE, /* IS_EXPLICIT_TARGET */ check_url_func, check_url_baton, cancel_func, cancel_baton, notify_func, notify_baton, @@ -1125,6 +1147,7 @@ svn_client__harvest_committables(svn_cli FALSE /* COPY_MODE_ROOT */, depth, just_locked, changelist_hash, FALSE, FALSE, + TRUE /* IS_EXPLICIT_TARGET */, check_url_func, check_url_baton, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, @@ -1218,6 +1241,7 @@ harvest_copy_committables(void *baton, v FALSE, /* JUST_LOCKED */ NULL, FALSE, FALSE, /* skip files, dirs */ + TRUE, /* IS_EXPLICIT_TARGET (don't care) */ btn->check_url_func, btn->check_url_baton, btn->ctx->cancel_func, Index: subversion/libsvn_wc/externals.c =================================================================== --- subversion/libsvn_wc/externals.c (revision 1197810) +++ subversion/libsvn_wc/externals.c (working copy) @@ -1115,6 +1115,110 @@ svn_wc__read_external_info(svn_node_kind return SVN_NO_ERROR; } +/* Return TRUE in *IS_ROLLED_OUT iff a node exists at XINFO->LOCAL_ABSPATH and + * if that node's origin corresponds with XINFO->REPOS_ROOT_URL and + * XINFO->REPOS_RELPATH. All allocations are made in SCRATCH_POOL. */ +static svn_error_t * +is_external_rolled_out(svn_boolean_t *is_rolled_out, + svn_wc_context_t *wc_ctx, + svn_wc__committable_external_info_t *xinfo, + apr_pool_t *scratch_pool) +{ + const char *x_repos_relpath; + const char *x_repos_root_url; + svn_error_t *err; + + *is_rolled_out = FALSE; + + err = svn_wc__node_get_origin(NULL, NULL, + &x_repos_relpath, + &x_repos_root_url, + NULL, + wc_ctx, xinfo->local_abspath, + FALSE, /* scan_deleted */ + scratch_pool, scratch_pool); + + if (err) + { + if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) + { + svn_error_clear(err); + return SVN_NO_ERROR; + } + SVN_ERR(err); + } + + *is_rolled_out = (strcmp(xinfo->repos_root_url, x_repos_root_url) == 0 && + strcmp(xinfo->repos_relpath, x_repos_relpath) == 0); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_wc__committable_externals_below(apr_array_header_t **externals, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_depth_t depth, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_array_header_t *orig_externals; + int i; + apr_pool_t *iterpool; + + /* For svn_depth_files, this also fetches dirs. They are filtered later. */ + SVN_ERR(svn_wc__db_committable_externals_below(&orig_externals, + wc_ctx->db, + local_abspath, + depth != svn_depth_infinity, + result_pool, scratch_pool)); + + if (orig_externals == NULL) + return SVN_NO_ERROR; + + iterpool = svn_pool_create(scratch_pool); + + for (i = 0; i < orig_externals->nelts; i++) + { + svn_boolean_t is_rolled_out; + + svn_wc__committable_external_info_t *xinfo = + APR_ARRAY_IDX(orig_externals, i, + svn_wc__committable_external_info_t *); + + /* Discard dirs for svn_depth_files (s.a.). */ + if (depth == svn_depth_files + && xinfo->kind == svn_kind_dir) + continue; + + svn_pool_clear(iterpool); + + /* Discard those externals that are not currently checked out. */ + SVN_ERR(is_external_rolled_out(&is_rolled_out, wc_ctx, xinfo, + iterpool)); + if (! is_rolled_out) + continue; + + if (*externals == NULL) + *externals = apr_array_make( + result_pool, 0, + sizeof(svn_wc__committable_external_info_t *)); + + APR_ARRAY_PUSH(*externals, + svn_wc__committable_external_info_t *) = xinfo; + + if (depth != svn_depth_infinity) + continue; + + /* Are there any nested externals? */ + SVN_ERR(svn_wc__committable_externals_below(externals, wc_ctx, + xinfo->local_abspath, + svn_depth_infinity, + result_pool, iterpool)); + } + + return SVN_NO_ERROR; +} + svn_error_t * svn_wc__externals_defined_below(apr_hash_t **externals, svn_wc_context_t *wc_ctx, Index: subversion/libsvn_wc/wc_db.c =================================================================== --- subversion/libsvn_wc/wc_db.c (revision 1197810) +++ subversion/libsvn_wc/wc_db.c (working copy) @@ -3059,6 +3059,64 @@ svn_wc__db_external_read(svn_wc__db_stat } svn_error_t * +svn_wc__db_committable_externals_below(apr_array_header_t **externals, + svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t immediates_only, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc__db_wcroot_t *wcroot; + svn_sqlite__stmt_t *stmt; + const char *local_relpath; + svn_boolean_t have_row; + svn_wc__committable_external_info_t *info; + svn_kind_t db_kind; + apr_array_header_t *result = NULL; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db, + local_abspath, scratch_pool, scratch_pool)); + VERIFY_USABLE_WCROOT(wcroot); + + SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, + STMT_SELECT_COMMITTABLE_EXTERNALS_BELOW)); + + SVN_ERR(svn_sqlite__bindf(stmt, "isi", wcroot->wc_id, local_relpath, + (apr_int64_t)(immediates_only ? 1 : 0))); + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + + if (have_row) + result = apr_array_make(result_pool, 0, + sizeof(svn_wc__committable_external_info_t *)); + + while (have_row) + { + info = apr_palloc(result_pool, sizeof(*info)); + + local_relpath = svn_sqlite__column_text(stmt, 0, NULL); + info->local_abspath = svn_dirent_join(wcroot->abspath, local_relpath, + result_pool); + + db_kind = svn_sqlite__column_token(stmt, 1, kind_map); + SVN_ERR_ASSERT(db_kind == svn_kind_file || db_kind == svn_kind_dir); + info->kind = db_kind; + + info->repos_relpath = svn_sqlite__column_text(stmt, 3, result_pool); + info->repos_root_url = svn_sqlite__column_text(stmt, 4, result_pool); + + APR_ARRAY_PUSH(result, svn_wc__committable_external_info_t *) = info; + + SVN_ERR(svn_sqlite__step(&have_row, stmt)); + } + + *externals = result; + return svn_error_trace(svn_sqlite__reset(stmt)); +} + +svn_error_t * svn_wc__db_externals_defined_below(apr_hash_t **externals, svn_wc__db_t *db, const char *local_abspath, Index: subversion/libsvn_wc/wc_db.h =================================================================== --- subversion/libsvn_wc/wc_db.h (revision 1197810) +++ subversion/libsvn_wc/wc_db.h (working copy) @@ -1133,6 +1133,33 @@ svn_wc__db_external_read(svn_wc__db_stat apr_pool_t *result_pool, apr_pool_t *scratch_pool); +/* Return in *EXTERNALS a list of svn_wc__committable_external_info_t * + * containing info on externals defined to be checked out below LOCAL_ABSPATH, + * returning only those externals that are not fixed to a specific revision. + * + * If IMMEDIATES_ONLY is TRUE, only those externals defined to be checked out + * as immediate children of LOCAL_ABSPATH are returned (this is useful for + * treating user requested depth < infinity). + * + * If there are no externals to be returned, set *EXTERNALS to NULL. Otherwise + * set *EXTERNALS to an APR array newly cleated in RESULT_POOL. + * + * NOTE: This only returns the externals known by the immediate WC root for + * LOCAL_ABSPATH; i.e.: + * - If there is a further parent WC "above" the immediate WC root, and if + * that parent WC defines externals to live somewhere within this WC, these + * externals will appear to be foreign/unversioned and won't be picked up. + * - Likewise, only the topmost level of externals nestings (externals + * defined within a checked out external dir) is picked up by this function. + * (For recursion, see svn_wc__committable_externals_below().) */ +svn_error_t * +svn_wc__db_committable_externals_below(apr_array_header_t **externals, + svn_wc__db_t *db, + const char *local_abspath, + svn_boolean_t immediates_only, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + /* Gets a mapping from const char * local abspaths of externals to the const char * local abspath of where they are defined for all externals defined at or below LOCAL_ABSPATH. Index: subversion/libsvn_wc/wc-queries.sql =================================================================== --- subversion/libsvn_wc/wc-queries.sql (revision 1197810) +++ subversion/libsvn_wc/wc-queries.sql (working copy) @@ -956,6 +956,40 @@ SELECT presence, kind, def_local_relpath FROM externals WHERE wc_id = ?1 AND local_relpath = ?2 LIMIT 1 +/* Select all committable externals, i.e. only unpegged ones on the same + * repository as the target path ?2, that are defined by WC ?1 to + * live below the target path. It does not matter which ancestor has the + * svn:externals definition, only the local path at which the external is + * supposed to be checked out is queried. + * Arguments: + * ?1: wc_id. + * ?2: the target path, local relpath inside ?1. + * ?3: boolean, if 1 return immediate children of ?2 only. + * + * ### NOTE: This statement deliberately removes file externals that live + * inside an unversioned dir, because commit still breaks on those. + * Once that's been fixed, the conditions below "--->8---" become obsolete. */ +-- STMT_SELECT_COMMITTABLE_EXTERNALS_BELOW +SELECT local_relpath, kind, repos_id, def_repos_relpath, repository.root +FROM externals +LEFT OUTER JOIN repository ON repository.id = externals.repos_id +WHERE wc_id = ?1 + AND def_revision IS NULL + AND repos_id = (SELECT repos_id FROM nodes + WHERE nodes.local_relpath = ?2) + AND ( ((NOT ?3) + AND (?2 = '' + /* Want only the cildren of e.local_relpath; + * externals can't have a local_relpath = ''. */ + OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))) + OR + ((?3) + AND parent_relpath = ?2) ) + /* ------>8----- */ + AND (EXISTS (SELECT 1 FROM nodes + WHERE nodes.wc_id = externals.wc_id + AND nodes.local_relpath = externals.parent_relpath)) + -- STMT_SELECT_EXTERNALS_DEFINED SELECT local_relpath, def_local_relpath FROM externals Index: subversion/svn/cl.h =================================================================== --- subversion/svn/cl.h (revision 1197810) +++ subversion/svn/cl.h (working copy) @@ -230,6 +230,7 @@ typedef struct svn_cl__opt_state_t svn_boolean_t internal_diff; /* override diff_cmd in config file */ svn_boolean_t use_git_diff_format; /* Use git's extended diff format */ svn_boolean_t allow_mixed_rev; /* Allow operation on mixed-revision WC */ + svn_boolean_t include_externals; /* Recurses (in)to file & dir externals */ } svn_cl__opt_state_t; Index: subversion/svn/commit-cmd.c =================================================================== --- subversion/svn/commit-cmd.c (revision 1197810) +++ subversion/svn/commit-cmd.c (working copy) @@ -166,11 +166,13 @@ svn_cl__commit(apr_getopt_t *os, } /* Commit. */ - err = svn_client_commit5(targets, + err = svn_client_commit6(targets, opt_state->depth, no_unlock, opt_state->keep_changelists, TRUE /* commit_as_operations */, + opt_state->include_externals, /* file externals */ + opt_state->include_externals, /* dir externals */ opt_state->changelists, opt_state->revprop_table, ! opt_state->quiet Index: subversion/svn/main.c =================================================================== --- subversion/svn/main.c (revision 1197810) +++ subversion/svn/main.c (working copy) @@ -123,7 +123,8 @@ typedef enum svn_cl__longopt_t { opt_diff, opt_internal_diff, opt_use_git_diff_format, - opt_allow_mixed_revisions + opt_allow_mixed_revisions, + opt_include_externals, } svn_cl__longopt_t; @@ -345,6 +346,12 @@ const apr_getopt_option_t svn_cl__option "Use of this option is not recommended!\n" " " "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)")}, /* Long-opt Aliases * @@ -463,7 +470,7 @@ const svn_opt_subcommand_desc2_t svn_cl_ " If any targets are (or contain) locked items, those will be\n" " unlocked after a successful commit.\n"), {'q', 'N', opt_depth, opt_targets, opt_no_unlock, SVN_CL__LOG_MSG_OPTIONS, - opt_changelist, opt_keep_changelists} }, + opt_changelist, opt_keep_changelists, opt_include_externals} }, { "copy", svn_cl__copy, {"cp"}, N_ ("Duplicate something in working copy or repository, remembering\n" @@ -2048,6 +2055,9 @@ main(int argc, const char *argv[]) case opt_allow_mixed_revisions: opt_state.allow_mixed_rev = TRUE; break; + case opt_include_externals: + opt_state.include_externals = TRUE; + break; default: /* Hmmm. Perhaps this would be a good place to squirrel away opts that commands like svn diff might need. Hmmm indeed. */ Index: subversion/tests/cmdline/externals_tests.py =================================================================== --- subversion/tests/cmdline/externals_tests.py (revision 1197810) +++ subversion/tests/cmdline/externals_tests.py (working copy) @@ -2098,6 +2098,546 @@ def copy_file_externals(sbox): 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): + "commit --include-externals" + # svntest.factory.make(sbox, """ + # mkdir Z + # echo 'This is the file zeta.' > Z/zeta + # svn add Z + # svn mkdir --parents Xpegged X/Y + # svn ci + # svn up + # svn ps svn:externals "^/Z xZ" A/D/H + # svn ps svn:externals "^/iota@1 Xpegged/xiota" wc_dir + # # ^^^ manually set externals to: + # # ^/iota@1 Xpegged/xiota + # # -r1 ^/A/B/E Xpegged/xE + # # ^/A/mu X/xmu + # # ^/A/B/lambda X/Y/xlambda + # # ^/A/D/G X/xG + # # ^/A/D/H X/Y/xH + # """) + # exit(0) + + sbox.build() + wc_dir = sbox.wc_dir + + A_D_H = os.path.join(wc_dir, 'A', 'D', 'H') + X_Y = os.path.join(wc_dir, 'X', 'Y') + Xpegged = os.path.join(wc_dir, 'Xpegged') + Z = os.path.join(wc_dir, 'Z') + Z_zeta = os.path.join(wc_dir, 'Z', 'zeta') + + # mkdir Z + os.makedirs(Z) + + # echo 'This is the file zeta.' > Z/zeta + main.file_write(Z_zeta, 'This is the file zeta.\n') + + # svn add Z + expected_stdout = verify.UnorderedOutput([ + 'A ' + Z + '\n', + 'A ' + Z_zeta + '\n', + ]) + + actions.run_and_verify_svn2('OUTPUT', expected_stdout, [], 0, 'add', Z) + + # svn mkdir --parents Xpegged X/Y + expected_stdout = verify.UnorderedOutput([ + 'A ' + Xpegged + '\n', + 'A ' + wc_dir + '/X\n', + 'A ' + X_Y + '\n', + ]) + + actions.run_and_verify_svn2('OUTPUT', expected_stdout, [], 0, 'mkdir', + '--parents', Xpegged, X_Y) + + # svn ci + expected_output = svntest.wc.State(wc_dir, { + 'Z' : Item(verb='Adding'), + 'Z/zeta' : Item(verb='Adding'), + 'X' : Item(verb='Adding'), + 'X/Y' : Item(verb='Adding'), + 'Xpegged' : Item(verb='Adding'), + }) + + expected_status = actions.get_virginal_state(wc_dir, 1) + expected_status.add({ + 'Z' : Item(status=' ', wc_rev='2'), + 'Z/zeta' : Item(status=' ', wc_rev='2'), + 'X' : Item(status=' ', wc_rev='2'), + 'X/Y' : Item(status=' ', wc_rev='2'), + 'Xpegged' : Item(status=' ', wc_rev='2'), + }) + + actions.run_and_verify_commit(wc_dir, expected_output, expected_status, + None, wc_dir) + + # svn up + expected_output = svntest.wc.State(wc_dir, {}) + + expected_disk = svntest.main.greek_state.copy() + expected_disk.add({ + 'Z' : Item(), + 'Z/zeta' : Item(contents="This is the file zeta.\n"), + 'Xpegged' : Item(), + 'X' : Item(), + 'X/Y' : Item(), + }) + + expected_status.tweak(wc_rev='2') + + actions.run_and_verify_update(wc_dir, expected_output, expected_disk, + expected_status, None, None, None, None, None, False, wc_dir) + + # svn ps svn:externals "^/Z xZ" A/D/H + expected_stdout = ["property 'svn:externals' set on '" + A_D_H + "'\n"] + + actions.run_and_verify_svn2('OUTPUT', expected_stdout, [], 0, 'ps', + 'svn:externals', '^/Z xZ', A_D_H) + + # svn ps svn:externals "^/iota@1 Xpegged/xiota" wc_dir + expected_stdout = ["property 'svn:externals' set on '" + wc_dir + "'\n"] + + actions.run_and_verify_svn2('OUTPUT', expected_stdout, [], 0, 'ps', + 'svn:externals', + ''' + ^/iota@1 Xpegged/xiota + -r1 ^/A/B/E Xpegged/xE + ^/A/mu X/xmu + ^/A/B/lambda X/Y/xlambda + ^/A/D/G X/xG + ^/A/D/H X/Y/xH + ''', wc_dir) + + # svntest.factory.make(sbox, prev_disk=expected_disk, + # prev_status=expected_status, + # commands = """ + # svn ci + # svn up + # echo mod >> Xpegged/xE/alpha + # echo mod >> X/xmu + # echo mod >> X/Y/xlambda + # echo mod >> X/xG/pi + # echo mod >> X/Y/xH/chi + # echo mod >> X/Y/xH/xZ/zeta + # svn status + # # Expect no externals to be committed + # svn ci + # # Expect no externals to be committed, because pegged + # svn ci --include-externals Xpegged + # # Expect no externals to be committed, because of depth + # svn ci --depth=immediates --include-externals + # # Expect only unpegged externals to be committed (those in X/) + # svn ci --include-externals + # # ### Below, manually add: + # # expected_status.tweak('A/D/H/xZ', 'Xpegged/xE', 'X/Y/xH', 'X/xG', + # # wc_rev=None) + # svn up + # # new mods to check more cases + # echo mod >> X/xmu + # echo mod >> X/Y/xlambda + # echo mod >> X/xG/pi + # echo mod >> X/Y/xH/chi + # echo mod >> X/Y/xH/xZ/zeta + # svn status + # # Expect no externals to be committed, because of depth + # svn ci --include-externals --depth=empty X + # # Expect only file external xmu to be committed, because of depth + # svn ci --include-externals --depth=files X + # svn status + # # ### Below, manually add: + # # expected_status.tweak('A/D/H/xZ', 'Xpegged/xE', 'X/Y/xH', 'X/xG', + # # wc_rev=None) + # svn up + # echo mod >> X/xG/pi + # svn status + # # Expect explicit targets to be committed + # svn ci X/Y/xlambda X/xG + # svn status + # """) + + X = os.path.join(wc_dir, 'X') + X_xG = os.path.join(wc_dir, 'X', 'xG') + X_xG_pi = os.path.join(wc_dir, 'X', 'xG', 'pi') + X_xmu = os.path.join(wc_dir, 'X', 'xmu') + X_Y_xH_chi = os.path.join(wc_dir, 'X', 'Y', 'xH', 'chi') + X_Y_xH_xZ_zeta = os.path.join(wc_dir, 'X', 'Y', 'xH', 'xZ', 'zeta') + X_Y_xlambda = os.path.join(wc_dir, 'X', 'Y', 'xlambda') + Xpegged = os.path.join(wc_dir, 'Xpegged') + Xpegged_xE_alpha = os.path.join(wc_dir, 'Xpegged', 'xE', 'alpha') + + # svn ci + expected_output = svntest.wc.State(wc_dir, { + '' : Item(verb='Sending'), + 'A/D/H' : Item(verb='Sending'), + }) + + expected_status.tweak('', 'A/D/H', wc_rev='3') + + actions.run_and_verify_commit(wc_dir, expected_output, expected_status, + None, wc_dir) + + # svn up + expected_output = svntest.wc.State(wc_dir, { + 'X/xmu' : Item(status='A '), + 'X/xG/tau' : Item(status='A '), + 'X/xG/rho' : Item(status='A '), + 'X/xG/pi' : Item(status='A '), + 'X/Y/xH' : Item(status=' U'), + 'X/Y/xH/psi' : Item(status='A '), + 'X/Y/xH/xZ/zeta' : Item(status='A '), + 'X/Y/xH/chi' : Item(status='A '), + 'X/Y/xH/omega' : Item(status='A '), + 'X/Y/xlambda' : Item(status='A '), + 'A/D/H/xZ/zeta' : Item(status='A '), + 'Xpegged/xiota' : Item(status='A '), + 'Xpegged/xE/alpha' : Item(status='A '), + 'Xpegged/xE/beta' : Item(status='A '), + }) + + expected_disk.add({ + 'Xpegged/xE' : Item(), + 'Xpegged/xE/beta' : Item(contents="This is the file 'beta'.\n"), + 'Xpegged/xE/alpha' : Item(contents="This is the file 'alpha'.\n"), + 'Xpegged/xiota' : Item(contents="This is the file 'iota'.\n"), + 'A/D/H/xZ' : Item(), + 'A/D/H/xZ/zeta' : Item(contents="This is the file zeta.\n"), + 'X/Y/xlambda' : Item(contents="This is the file 'lambda'.\n"), + 'X/Y/xH' : Item(), + 'X/Y/xH/chi' : Item(contents="This is the file 'chi'.\n"), + 'X/Y/xH/xZ' : Item(), + 'X/Y/xH/xZ/zeta' : Item(contents="This is the file zeta.\n"), + 'X/Y/xH/psi' : Item(contents="This is the file 'psi'.\n"), + 'X/Y/xH/omega' : Item(contents="This is the file 'omega'.\n"), + 'X/xmu' : Item(contents="This is the file 'mu'.\n"), + 'X/xG' : Item(), + 'X/xG/tau' : Item(contents="This is the file 'tau'.\n"), + 'X/xG/rho' : Item(contents="This is the file 'rho'.\n"), + 'X/xG/pi' : Item(contents="This is the file 'pi'.\n"), + }) + + expected_status.tweak(wc_rev='3') + expected_status.add({ + 'A/D/H/xZ' : Item(status='X '), + 'Xpegged/xiota' : Item(status=' ', wc_rev='1', switched='X'), + 'Xpegged/xE' : Item(status='X '), + 'X/Y/xH' : Item(status='X '), + 'X/Y/xlambda' : Item(status=' ', wc_rev='3', switched='X'), + 'X/xmu' : Item(status=' ', wc_rev='3', switched='X'), + 'X/xG' : Item(status='X '), + }) + expected_status.tweak('Xpegged/xiota', wc_rev='1') + + actions.run_and_verify_update(wc_dir, expected_output, expected_disk, + expected_status, None, None, None, None, None, False, wc_dir) + + # echo mod >> Xpegged/xE/alpha + main.file_append(Xpegged_xE_alpha, 'mod\n') + + # echo mod >> X/xmu + main.file_append(X_xmu, 'mod\n') + + # echo mod >> X/Y/xlambda + main.file_append(X_Y_xlambda, 'mod\n') + + # echo mod >> X/xG/pi + main.file_append(X_xG_pi, 'mod\n') + + # echo mod >> X/Y/xH/chi + main.file_append(X_Y_xH_chi, 'mod\n') + + # echo mod >> X/Y/xH/xZ/zeta + main.file_append(X_Y_xH_xZ_zeta, 'mod\n') + + # svn status + expected_status.tweak('X/Y/xlambda', 'X/xmu', status='M ') + + actions.run_and_verify_unquiet_status(wc_dir, expected_status) + + # Expect no externals to be committed + # svn ci + expected_output = svntest.wc.State(wc_dir, {}) + + actions.run_and_verify_commit(wc_dir, expected_output, expected_status, + None, wc_dir) + + # Expect no externals to be committed, because pegged + # svn ci --include-externals Xpegged + expected_output = svntest.wc.State(wc_dir, {}) + + actions.run_and_verify_commit(wc_dir, expected_output, expected_status, + None, '--include-externals', Xpegged) + + # Expect no externals to be committed, because of depth + # svn ci --depth=immediates --include-externals + expected_output = svntest.wc.State(wc_dir, {}) + + actions.run_and_verify_commit(wc_dir, expected_output, expected_status, + None, '--depth=immediates', '--include-externals', wc_dir) + + # Expect only unpegged externals to be committed (those in X/) + # svn ci --include-externals + expected_output = svntest.wc.State(wc_dir, { + 'X/xmu' : Item(verb='Sending'), + 'X/Y/xlambda' : Item(verb='Sending'), + 'X/Y/xH/xZ/zeta' : Item(verb='Sending'), + 'X/Y/xH/chi' : Item(verb='Sending'), + 'X/xG/pi' : Item(verb='Sending'), + }) + + expected_status.tweak(status=' ') + expected_status.tweak('X/Y/xlambda', 'X/xmu', wc_rev='4') + expected_status.tweak('X/Y/xH', 'X/xG', 'A/D/H/xZ', 'Xpegged/xE', + status='X ') + + actions.run_and_verify_commit(wc_dir, expected_output, expected_status, + None, '--include-externals', wc_dir) + + # svn up + expected_output = svntest.wc.State(wc_dir, { + 'A/mu' : Item(status='U '), + 'A/D/H/chi' : Item(status='U '), + 'A/D/H/xZ/zeta' : Item(status='U '), + 'A/D/G/pi' : Item(status='U '), + 'A/B/lambda' : Item(status='U '), + 'Z/zeta' : Item(status='U '), + }) + + expected_disk.tweak('Xpegged/xE/alpha', + contents="This is the file 'alpha'.\nmod\n") + expected_disk.tweak('A/D/H/chi', 'X/Y/xH/chi', + contents="This is the file 'chi'.\nmod\n") + expected_disk.tweak('A/D/H/xZ/zeta', 'X/Y/xH/xZ/zeta', 'Z/zeta', + contents='This is the file zeta.\nmod\n') + expected_disk.tweak('A/D/G/pi', 'X/xG/pi', + contents="This is the file 'pi'.\nmod\n") + expected_disk.tweak('A/mu', 'X/xmu', + contents="This is the file 'mu'.\nmod\n") + expected_disk.tweak('A/B/lambda', 'X/Y/xlambda', + contents="This is the file 'lambda'.\nmod\n") + + expected_status.tweak(wc_rev='4') + expected_status.tweak('Xpegged/xiota', wc_rev='1') + expected_status.tweak('A/D/H/xZ', 'Xpegged/xE', 'X/Y/xH', 'X/xG', + wc_rev=None) + + actions.run_and_verify_update(wc_dir, expected_output, expected_disk, + expected_status, None, None, None, None, None, False, wc_dir) + + # new mods to check more cases + # echo mod >> X/xmu + main.file_append(X_xmu, 'mod\n') + + # echo mod >> X/Y/xlambda + main.file_append(X_Y_xlambda, 'mod\n') + + # echo mod >> X/xG/pi + main.file_append(X_xG_pi, 'mod\n') + + # echo mod >> X/Y/xH/chi + main.file_append(X_Y_xH_chi, 'mod\n') + + # echo mod >> X/Y/xH/xZ/zeta + main.file_append(X_Y_xH_xZ_zeta, 'mod\n') + + # svn status + expected_status.tweak('X/Y/xlambda', 'X/xmu', status='M ') + + actions.run_and_verify_unquiet_status(wc_dir, expected_status) + + # Expect no externals to be committed, because of depth + # svn ci --include-externals --depth=empty X + expected_output = svntest.wc.State(wc_dir, {}) + + actions.run_and_verify_commit(wc_dir, expected_output, expected_status, + None, '--include-externals', '--depth=empty', X) + + # Expect only file external xmu to be committed, because of depth + # svn ci --include-externals --depth=files X + expected_output = svntest.wc.State(wc_dir, { + 'X/xmu' : Item(verb='Sending'), + }) + + expected_status.tweak(status=' ') + expected_status.tweak('X/xmu', wc_rev='5') + expected_status.tweak('X/Y/xlambda', status='M ') + expected_status.tweak('X/Y/xH', 'X/xG', 'A/D/H/xZ', 'Xpegged/xE', + status='X ') + + actions.run_and_verify_commit(wc_dir, expected_output, expected_status, + None, '--include-externals', '--depth=files', X) + + # svn status + actions.run_and_verify_unquiet_status(wc_dir, expected_status) + + # svn up + expected_output = svntest.wc.State(wc_dir, { + 'A/mu' : Item(status='U '), + }) + + expected_disk.tweak('A/mu', 'X/xmu', + contents="This is the file 'mu'.\nmod\nmod\n") + expected_disk.tweak('X/Y/xlambda', + contents="This is the file 'lambda'.\nmod\nmod\n") + expected_disk.tweak('X/Y/xH/chi', + contents="This is the file 'chi'.\nmod\nmod\n") + expected_disk.tweak('X/Y/xH/xZ/zeta', + contents='This is the file zeta.\nmod\nmod\n') + expected_disk.tweak('X/xG/pi', + contents="This is the file 'pi'.\nmod\nmod\n") + + expected_status.tweak(wc_rev='5') + expected_status.tweak('Xpegged/xiota', wc_rev='1') + expected_status.tweak('A/D/H/xZ', 'Xpegged/xE', 'X/Y/xH', 'X/xG', + wc_rev=None) + + actions.run_and_verify_update(wc_dir, expected_output, expected_disk, + expected_status, None, None, None, None, None, False, wc_dir) + + # echo mod >> X/xG/pi + main.file_append(X_xG_pi, 'mod\n') + + # svn status + actions.run_and_verify_unquiet_status(wc_dir, expected_status) + + # Expect explicit targets to be committed + # svn ci X/Y/xlambda X/xG + expected_output = svntest.wc.State(wc_dir, { + 'X/Y/xlambda' : Item(verb='Sending'), + 'X/xG/pi' : Item(verb='Sending'), + }) + + expected_status.tweak(status=' ') + expected_status.tweak('X/Y/xlambda', wc_rev='6') + expected_status.tweak('X/Y/xH', 'X/xG', 'A/D/H/xZ', 'Xpegged/xE', + status='X ') + + actions.run_and_verify_commit(wc_dir, expected_output, expected_status, + None, X_Y_xlambda, X_xG) + + # svn status + actions.run_and_verify_unquiet_status(wc_dir, expected_status) + + +@XFail() +def include_immediate_dir_externals(sbox): + "commit --include-externals --depth=immediates" + # See also comment inside svn_client_commit6(). + + # svntest.factory.make(sbox,""" + # svn mkdir X + # svn ci + # svn up + # svn ps svn:externals "^/A/B/E X/XE" wc_dir + # svn ci + # svn up + # + # svn ps some change X/XE + # echo mod >> X/XE/alpha + # + # svn st X/XE + # # Expect only the propset on X/XE to be committed. + # # Should be like 'svn commit --include-externals --depth=empty X/XE'. + # svn commit --include-externals --depth=immediates X + # """) + + sbox.build() + wc_dir = sbox.wc_dir + + X = os.path.join(wc_dir, 'X') + X_XE = os.path.join(wc_dir, 'X', 'XE') + X_XE_alpha = os.path.join(wc_dir, 'X', 'XE', 'alpha') + + # svn mkdir X + expected_stdout = ['A ' + X + '\n'] + + actions.run_and_verify_svn2('OUTPUT', expected_stdout, [], 0, 'mkdir', X) + + # svn ci + expected_output = svntest.wc.State(wc_dir, { + 'X' : Item(verb='Adding'), + }) + + expected_status = actions.get_virginal_state(wc_dir, 1) + expected_status.add({ + 'X' : Item(status=' ', wc_rev='2'), + }) + + actions.run_and_verify_commit(wc_dir, expected_output, expected_status, + None, wc_dir) + + # svn up + expected_output = svntest.wc.State(wc_dir, {}) + + expected_disk = svntest.main.greek_state.copy() + expected_disk.add({ + 'X' : Item(), + }) + + expected_status.tweak(wc_rev='2') + + actions.run_and_verify_update(wc_dir, expected_output, expected_disk, + expected_status, None, None, None, None, None, False, wc_dir) + + # svn ps svn:externals "^/A/B/E X/XE" wc_dir + expected_stdout = ["property 'svn:externals' set on '" + wc_dir + "'\n"] + + actions.run_and_verify_svn2('OUTPUT', expected_stdout, [], 0, 'ps', + 'svn:externals', '^/A/B/E X/XE', wc_dir) + + # svn ci + expected_output = svntest.wc.State(wc_dir, { + '' : Item(verb='Sending'), + }) + + expected_status.tweak('', wc_rev='3') + + actions.run_and_verify_commit(wc_dir, expected_output, expected_status, + None, wc_dir) + + # svn up + expected_output = svntest.wc.State(wc_dir, { + 'X/XE/alpha' : Item(status='A '), + 'X/XE/beta' : Item(status='A '), + }) + + expected_disk.add({ + 'X/XE' : Item(), + 'X/XE/alpha' : Item(contents="This is the file 'alpha'.\n"), + 'X/XE/beta' : Item(contents="This is the file 'beta'.\n"), + }) + + expected_status.tweak(wc_rev='3') + expected_status.add({ + 'X/XE' : Item(status='X '), + }) + + actions.run_and_verify_update(wc_dir, expected_output, expected_disk, + expected_status, None, None, None, None, None, False, wc_dir) + + # svn ps some change X/XE + expected_stdout = ["property 'some' set on '" + X_XE + "'\n"] + + actions.run_and_verify_svn2('OUTPUT', expected_stdout, [], 0, 'ps', 'some', + 'change', X_XE) + + # echo mod >> X/XE/alpha + main.file_append(X_XE_alpha, 'mod\n') + + # svn st X/XE + actions.run_and_verify_unquiet_status(wc_dir, expected_status) + + # Expect only the propset on X/XE to be committed. + # Should be like 'svn commit --include-externals --depth=empty X/XE'. + # svn commit --include-externals --depth=immediates X + expected_output = svntest.wc.State(wc_dir, { + 'X/XE' : Item(verb='Sending'), + }) + + actions.run_and_verify_commit(wc_dir, expected_output, expected_status, + None, '--include-externals', '--depth=immediates', X) + ######################################################################## # Run the tests @@ -2140,6 +2680,8 @@ test_list = [ None, file_externals_different_repos, file_external_in_unversioned, copy_file_externals, + include_externals, + include_immediate_dir_externals, ] if __name__ == '__main__':
signature.asc
Description: OpenPGP digital signature