I've rinsed and improved my proposed feature dubbed
  svn commit --include-externals
(Related issues: #1167, #3563)

I hope this will cut a much clearer path through the jungle that is
externals behavior. Now I'm hoping for some reviews!

The idea is to have file and dir externals behave the same way during
commit, and to provide a way to recursively commit all externals (that are
from the same repository and are not revision-pegged).

Who'd have guessed, there are a few corners that would be good to have
others' opinions on.

(To: CMike and Bert because you two were involved in the original
discussion: http://svn.haxx.se/dev/archive-2011-08/0617.shtml )

Thanks!
~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,
* subversion/libsvn_client/commit.c
  (svn_client_commit6):
    New function, adding INCLUDE_FILE_EXTERNALS and INCLUDE_DIR_EXTERNALS
    arguments over svn_client_commit5().
  (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.
  (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
  (svn_wc__committable_externals_below): New function.

* 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/commit-cmd.c
  (svn_cl__commit): Call svn_client_commit6() instead of &5().

* subversion/svn/cl.h
  (svn_cl__opt_state_t): Add INCLUDE_EXTERNALS for --include-externals.

* 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 1197104)
+++ subversion/include/private/svn_wc_private.h (working copy)
@@ -131,6 +131,49 @@ 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_node_file or svn_node_dir. */
+  svn_node_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.
+ *
+ * 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_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/include/svn_client.h
===================================================================
--- subversion/include/svn_client.h     (revision 1197104)
+++ subversion/include/svn_client.h     (working copy)
@@ -1954,6 +1954,17 @@ 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 @a include_dir_externals are #TRUE, all
+ * file respectively dir externals as defined by an svn:externals property are
+ * included in commit recursion, with these exceptions: any externals that
+ * - have a fixed peg revision or
+ * - that come from a different repository root URL (dir externals)
+ * are never included in recursion.
+ *
+ * ### 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.
+ *
  * 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 +1978,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 1197104)
+++ subversion/libsvn_client/commit.c   (working copy)
@@ -1169,11 +1169,13 @@ check_url_kind(void *baton,
 }
 
 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 +1235,97 @@ svn_client_commit5(const apr_array_heade
   if (rel_targets->nelts == 0)
     APR_ARRAY_PUSH(rel_targets, const char *) = "";
 
+  /* Easy part of applying DEPTH to externals. */
+  if (depth == svn_depth_empty)
+     {
+       /* If user passes an external as explicit target, the external *will* be
+        * handled; just stop all recursion to externals: */
+       include_file_externals = include_dir_externals = FALSE;
+     }
+   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 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 effect though 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 including externals, query every target for committable externals
+   * and append them as first class targets. All externals paths thus obtained
+   * are below BASE_ABSPATH, so we can just append more REL_TARGETS. */
+  if (include_file_externals || include_dir_externals)
+    {
+      int rel_targets_nelts_fixed;
+
+      /* Iterate *and* grow REL_TARGETS */
+      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);
+
+        /* On svn_depth_files, all dirs are already filtered out by setting
+         * INCLUDE_DIR_EXTERNALS = FALSE above. So if depth < infinity
+         * here, simply allow only direct children of the target. */
+
+        SVN_ERR(svn_wc__committable_externals_below(
+                                              &externals, ctx->wc_ctx, target,
+                                              (depth != svn_depth_infinity),
+                                              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 *);
+
+                /* Does this external kind match the include_file/dir flags? */
+                if ((xinfo->kind == svn_node_file
+                     && ! include_file_externals)
+                    || (xinfo->kind == svn_node_dir
+                        && ! include_dir_externals))
+                  {
+                    continue;
+                  }
+
+                /* "Slip in" a rel_target to handle this external.
+                 * Needs to be relative to BASE_ABSPATH. */
+                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(pool, rel_target);
+              }
+          }
+      }
+    }
+
   SVN_ERR(determine_lock_targets(&lock_targets, ctx->wc_ctx, base_abspath,
                                  rel_targets, pool, iterpool));
 
@@ -1603,3 +1696,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 1197104)
+++ subversion/libsvn_client/commit_util.c      (working copy)
@@ -410,6 +410,10 @@ 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. File externals are
+   skipped when this is FALSE.
+
    If CANCEL_FUNC is non-null, call it with CANCEL_BATON to see
    if the user has cancelled the operation.
 
@@ -428,6 +432,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 +548,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 +860,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 +1145,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 +1239,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 1197104)
+++ subversion/libsvn_wc/externals.c    (working copy)
@@ -1115,6 +1115,103 @@ 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. */
+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_boolean_t immediates_only,
+                                    apr_pool_t *result_pool,
+                                    apr_pool_t *scratch_pool)
+{
+  apr_array_header_t *orig_externals;
+  int i;
+  apr_pool_t *iterpool;
+
+  SVN_ERR(svn_wc__db_committable_externals_below(&orig_externals,
+                                                 wc_ctx->db,
+                                                 local_abspath,
+                                                 immediates_only,
+                                                 result_pool, scratch_pool));
+  
+  if (orig_externals == NULL)
+    return SVN_NO_ERROR;
+
+  /* Discard those externals that are not currently checked out. */
+  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 *);
+
+      svn_pool_clear(iterpool);
+
+      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 (immediates_only)
+        continue;
+
+      /* Are there any nested externals? */
+      SVN_ERR(svn_wc__committable_externals_below(externals, wc_ctx,
+                                                  xinfo->local_abspath, FALSE,
+                                                  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 1197104)
+++ subversion/libsvn_wc/wc_db.c        (working copy)
@@ -3059,6 +3059,68 @@ 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);
+      if (db_kind == svn_kind_file)
+        info->kind = svn_node_file;
+      else if (db_kind == svn_kind_dir)
+        info->kind = svn_node_dir;
+      else
+        SVN_ERR_MALFUNCTION();
+
+      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 1197104)
+++ 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 1197104)
+++ 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 1197104)
+++ 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 1197104)
+++ 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 1197104)
+++ 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 1197104)
+++ 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__':

Attachment: signature.asc
Description: OpenPGP digital signature

Reply via email to