On the 'pristines-on-demand' branch:

  - Sketch of configurable pristines-mode per WC.
  - Debug/trace prints.

The pristines-mode config option can be set in:

  - user config (e.g. '~/.subversion/config') -- for user default
  - (new) WC config file ('wc/.svn/config') -- per WC override

The pristines mode can:

  - skip the "hydrating" of modified files
  - skip the "dehydrating" of unmodified files

It CANNOT yet:

  - force "hydrating" files that currently have no local modification

Syntax and currently effective values for the option (in the user
config file and/or in the per-WC config file):

  [working-copy]
  pristines-mode = hydrate=[never|on-demand],dehydrate=[never|on-demand]

TODO: Force "hydrating" files that currently have no local modification.

  pristines-mode = hydrate=all-files

TODO: Allow abbreviations for useful combinations, such as:

  pristines-mode = all-present|on-demand|off-line|...
  ### The current code supports some such abbreviations, for experimentation.

TODO: Split out the per-WC config as a separate patch:

  - add per-WC config file generation
  - support other existing WC settings from user config in per-WC config

TODO: Add '#pristines-mode = ...' example to generated config files.


Changes:

* subversion/include/svn_config.h
  (SVN_CONFIG_OPTION_WC_PRISTINES_MODE): New.

* subversion/libsvn_client/textbase.c
  Add debug/trace prints, including "Fetching missing text bases...".

* subversion/libsvn_wc/textbase.c
  (check_file_modified): Add debug/trace prints.
  (textbase_walk_cb): Add debug/trace prints.
  (check_pristines_mode): New.
  (svn_wc__textbase_sync): Check the configured pristines mode, using it to
    skip the hydrating and/or the dehydrating.

* subversion/libsvn_wc/wc.h
  (SVN_WC__PRISTINES_ON_DEMAND_VERSION): New.
  (SVN_WC__ADM_CONFIG_FILE): New.

* subversion/libsvn_wc/wc_db.h
  (svn_wc__db_pristines_mode): New.

* subversion/libsvn_wc/wc_db_private.h
  (svn_wc__db_t,
   svn_wc__db_wcroot_t): Add a 'pristines_mode' in each of these.

* subversion/libsvn_wc/wc_db_wcroot.c
  (svn_wc__db_open): Read pristines-mode from per-user config.
  (read_per_wc_config): New.
  (svn_wc__db_pdh_create_wcroot): Read per-WC pristines-mode from per-WC config.
  (svn_wc__db_pristines_mode): New.

--This line, and those below, will be ignored--

Index: subversion/include/svn_config.h
===================================================================
--- subversion/include/svn_config.h	(revision 1897454)
+++ subversion/include/svn_config.h	(working copy)
@@ -168,12 +168,14 @@ typedef struct svn_config_t svn_config_t
 /** @since New in 1.8. */
 #define SVN_CONFIG_OPTION_SQLITE_EXCLUSIVE          "exclusive-locking"
 /** @since New in 1.8. */
 #define SVN_CONFIG_OPTION_SQLITE_EXCLUSIVE_CLIENTS  "exclusive-locking-clients"
 /** @since New in 1.9. */
 #define SVN_CONFIG_OPTION_SQLITE_BUSY_TIMEOUT       "busy-timeout"
+/** @since New in 1.15. */
+#define SVN_CONFIG_OPTION_WC_PRISTINES_MODE         "pristines-mode"
 /** @} */
 
 /** @name Repository conf directory configuration files strings
  * Strings for the names of sections and options in the
  * repository conf directory configuration files.
  * @{
Index: subversion/libsvn_client/textbase.c
===================================================================
--- subversion/libsvn_client/textbase.c	(revision 1897454)
+++ subversion/libsvn_client/textbase.c	(working copy)
@@ -30,12 +30,13 @@
 /* A baton for use with textbase_hydrate_cb(). */
 typedef struct textbase_hydrate_baton_t
 {
   apr_pool_t *pool;
   svn_client_ctx_t *ctx;
   svn_ra_session_t *ra_session;
+  svn_boolean_t have_notified_start;
 } textbase_hydrate_baton_t;
 
 /* Implements svn_wc__textbase_hydrate_cb_t. */
 static svn_error_t *
 textbase_hydrate_cb(void *baton,
                     const char *repos_root_url,
@@ -48,12 +49,21 @@ textbase_hydrate_cb(void *baton,
 {
   struct textbase_hydrate_baton_t *b = baton;
   const char *url;
   const char *old_url;
   svn_error_t *err;
 
+#ifdef SVN_DEBUG
+  if (!b->have_notified_start)
+    {
+      SVN_DBG(("Fetching missing text bases..."));
+      b->have_notified_start = TRUE;
+    }
+  SVN_DBG(("hydrating r%ld ^%s", revision, repos_relpath));
+  sleep(2);
+#endif
   url = svn_path_url_add_component2(repos_root_url, repos_relpath,
                                     scratch_pool);
 
   if (!b->ra_session)
     {
       svn_ra_session_t *session;
@@ -90,12 +100,13 @@ svn_client__textbase_sync(const char *lo
                           svn_boolean_t allow_dehydrate,
                           svn_client_ctx_t *ctx,
                           apr_pool_t *scratch_pool)
 {
   textbase_hydrate_baton_t baton = {0};
 
+  SVN_DBG(("textbase_sync(%d,%d) '%s'", allow_hydrate, allow_dehydrate, local_abspath));
   SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
 
   baton.pool = scratch_pool;
   baton.ctx = ctx;
   baton.ra_session = NULL;
 
Index: subversion/libsvn_wc/textbase.c
===================================================================
--- subversion/libsvn_wc/textbase.c	(revision 1897454)
+++ subversion/libsvn_wc/textbase.c	(working copy)
@@ -154,12 +154,13 @@ check_file_modified(svn_boolean_t *modif
                     svn_boolean_t props_mod,
                     apr_pool_t *scratch_pool)
 {
   const svn_io_dirent2_t *dirent;
   svn_boolean_t modified;
 
+  SVN_DBG(("stat '/...%s'", local_abspath+30));
   SVN_ERR(svn_io_stat_dirent2(&dirent, local_abspath, FALSE, TRUE,
                               scratch_pool, scratch_pool));
 
   if (dirent->kind == svn_node_file &&
       dirent->filesize == recorded_size &&
       dirent->mtime == recorded_time)
@@ -489,12 +490,14 @@ textbase_walk_cb(svn_boolean_t *referenc
                                   recorded_size, recorded_time, checksum,
                                   have_props, props_mod, scratch_pool));
       /* Pin the text-base for modified files. */
       referenced = modified;
     }
 
+  SVN_DBG(("textbase_walk: ref?=%d <- d%d/%d '/...%s'",
+           referenced, op_depth, max_op_depth, local_abspath+30));
   *referenced_p = referenced;
   return SVN_NO_ERROR;
 }
 
 /* Implements svn_wc__db_textbase_hydrate_cb_t. */
 static svn_error_t *
@@ -513,12 +516,52 @@ textbase_hydrate_cb(void *baton,
                               repos_relpath, revision, contents,
                               cancel_func, cancel_baton, scratch_pool));
 
   return SVN_NO_ERROR;
 }
 
+/* Check the pristines mode configured for the WC identified by WC_CTX,
+ * LOCAL_ABSPATH.  Modify the values of *ALLOW_HYDRATE, *ALLOW_DEHYDRATE
+ * according to the mode.
+ */
+static svn_error_t *
+check_pristines_mode(svn_boolean_t *allow_hydrate,
+                     svn_boolean_t *allow_dehydrate,
+                     svn_wc_context_t *wc_ctx,
+                     const char *local_abspath,
+                     apr_pool_t *scratch_pool)
+{
+  const char *mode;
+
+  SVN_ERR(svn_wc__db_pristines_mode(wc_ctx->db, local_abspath,
+                                    &mode, scratch_pool));
+
+  /* ### for now, these are just a bunch of experimental modes and
+   * experimental matching syntax */
+  if (strcmp(mode, "off") == 0)
+    {
+      *allow_hydrate = FALSE;
+      *allow_dehydrate = FALSE;
+    }
+  if (strcmp(mode, "off-line") == 0)
+    *allow_hydrate = FALSE;
+  if (strcmp(mode, "fetch-only") == 0)
+    *allow_dehydrate = FALSE;
+  if (strcmp(mode, "fetch-all") == 0)
+    *allow_dehydrate = FALSE;
+
+  if (strstr(mode, "hydrate=never"))
+    *allow_hydrate = FALSE;
+  if (strstr(mode, "dehydrate=never"))
+    *allow_dehydrate = FALSE;
+
+  SVN_DBG(("pristines mode: %s -> hydrate=%d dehydrate=%d",
+           mode, *allow_hydrate, *allow_dehydrate));
+  return SVN_NO_ERROR;
+}
+
 svn_error_t *
 svn_wc__textbase_sync(svn_wc_context_t *wc_ctx,
                       const char *local_abspath,
                       svn_boolean_t allow_hydrate,
                       svn_boolean_t allow_dehydrate,
                       svn_wc__textbase_hydrate_cb_t hydrate_callback,
@@ -528,12 +571,18 @@ svn_wc__textbase_sync(svn_wc_context_t *
                       apr_pool_t *scratch_pool)
 {
   textbase_sync_baton_t baton = {0};
 
   SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
 
+  SVN_ERR(check_pristines_mode(&allow_hydrate, &allow_dehydrate,
+                               wc_ctx, local_abspath, scratch_pool));
+
+  if (!allow_hydrate && !allow_dehydrate)
+    return SVN_NO_ERROR;
+
   baton.db = wc_ctx->db;
   baton.hydrate_callback = hydrate_callback;
   baton.hydrate_baton = hydrate_baton;
 
   SVN_ERR(svn_wc__db_textbase_walk(wc_ctx->db, local_abspath,
                                    textbase_walk_cb, &baton,
Index: subversion/libsvn_wc/wc_db.h
===================================================================
--- subversion/libsvn_wc/wc_db.h	(revision 1897454)
+++ subversion/libsvn_wc/wc_db.h	(working copy)
@@ -1069,12 +1069,21 @@ svn_wc__db_pristine_check(svn_boolean_t
 svn_error_t *
 svn_wc__db_pristine_dehydrate(svn_wc__db_t *db,
                               const char *wri_abspath,
                               const svn_checksum_t *sha1_checksum,
                               apr_pool_t *scratch_pool);
 
+/* Read the pristines mode into *MODE from the per-WC config (if set there)
+ * or else from the per-user config (if set there), with a default value of
+ * "all". */
+svn_error_t *
+svn_wc__db_pristines_mode(svn_wc__db_t *db,
+                          const char *local_abspath,
+                          const char **mode,
+                          apr_pool_t *scratch_pool);
+
 /* @defgroup svn_wc__db_external  External management
    @{ */
 
 /* Adds (or overwrites) a file external LOCAL_ABSPATH to the working copy
    identified by WRI_ABSPATH.
 
Index: subversion/libsvn_wc/wc_db_private.h
===================================================================
--- subversion/libsvn_wc/wc_db_private.h	(revision 1897454)
+++ subversion/libsvn_wc/wc_db_private.h	(working copy)
@@ -61,12 +61,16 @@ struct svn_wc__db_t {
   struct
   {
     svn_stringbuf_t *abspath;
     svn_node_kind_t kind;
   } parse_cache;
 
+  /* The default config value for the per-WC 'pristines-mode' field.
+     See #svn_wc__db_wcroot_t.pristines_mode. */
+  const char *pristines_mode;
+
   /* As we grow the state of this DB, allocate that state here. */
   apr_pool_t *state_pool;
 };
 
 
 /* Hold information about an owned lock */
@@ -104,12 +108,15 @@ typedef struct svn_wc__db_wcroot_t {
   apr_array_header_t *owned_locks;
 
   /* Map a working copy directory to a cached adm_access baton.
      const char *local_abspath -> svn_wc_adm_access_t *adm_access */
   apr_hash_t *access_cache;
 
+  /* How to manage the pristines. For values, see config file. */
+  const char *pristines_mode;
+
 } svn_wc__db_wcroot_t;
 
 
 /* */
 svn_error_t *
 svn_wc__db_close_many_wcroots(apr_hash_t *roots,
Index: subversion/libsvn_wc/wc_db_wcroot.c
===================================================================
--- subversion/libsvn_wc/wc_db_wcroot.c	(revision 1897454)
+++ subversion/libsvn_wc/wc_db_wcroot.c	(working copy)
@@ -229,21 +229,23 @@ svn_wc__db_open(svn_wc__db_t **db,
 {
   *db = apr_pcalloc(result_pool, sizeof(**db));
   (*db)->config = config;
   (*db)->verify_format = !open_without_upgrade;
   (*db)->enforce_empty_wq = enforce_empty_wq;
   (*db)->dir_data = apr_hash_make(result_pool);
+  (*db)->pristines_mode = "all";
 
   (*db)->state_pool = result_pool;
 
   /* Don't need to initialize (*db)->parse_cache, due to the calloc above */
   if (config)
     {
       svn_error_t *err;
       svn_boolean_t sqlite_exclusive = FALSE;
       apr_int64_t timeout;
+      const char *pristines_mode;
 
       err = svn_config_get_bool(config, &sqlite_exclusive,
                                 SVN_CONFIG_SECTION_WORKING_COPY,
                                 SVN_CONFIG_OPTION_SQLITE_EXCLUSIVE,
                                 FALSE);
       if (err)
@@ -258,12 +260,22 @@ svn_wc__db_open(svn_wc__db_t **db,
                                  SVN_CONFIG_OPTION_SQLITE_BUSY_TIMEOUT,
                                  0);
       if (err || timeout < 0 || timeout > APR_INT32_MAX)
         svn_error_clear(err);
       else
         (*db)->timeout = (apr_int32_t)timeout;
+
+      svn_config_get(config, &pristines_mode,
+                     SVN_CONFIG_SECTION_WORKING_COPY,
+                     SVN_CONFIG_OPTION_WC_PRISTINES_MODE,
+                     NULL);
+      if (pristines_mode)
+        {
+          (*db)->pristines_mode = apr_pstrdup(result_pool, pristines_mode);
+          SVN_DBG(("pristines-mode from per-user config: %s", pristines_mode));
+        }
     }
 
   return SVN_NO_ERROR;
 }
 
 
@@ -291,12 +303,48 @@ svn_wc__db_close(svn_wc__db_t *db)
   /* Run the cleanup for each WCROOT.  */
   return svn_error_trace(svn_wc__db_close_many_wcroots(roots, db->state_pool,
                                                        scratch_pool));
 }
 
 
+/* Read the per-WC config file into *WC_CONFIG (if the file exists),
+ * else reads an empty config structure.
+ */
+static svn_error_t *
+read_per_wc_config(svn_config_t **wc_config,
+                   const char *wcroot_abspath,
+                   apr_pool_t *result_pool,
+                   apr_pool_t *scratch_pool)
+{
+  const char *config_abspath
+    = svn_wc__adm_child(wcroot_abspath, SVN_WC__ADM_CONFIG_FILE,
+                        scratch_pool);
+
+  SVN_ERR(svn_config_read3(wc_config, config_abspath, FALSE, FALSE, FALSE,
+                           scratch_pool));
+  return SVN_NO_ERROR;
+}
+
+/* Read the pristines mode from the per-WC config (if set there) else NULL.
+ */
+static svn_error_t *
+read_pristines_mode_from_wc_config(const char **pristines_mode,
+                                   svn_config_t *wc_config,
+                                   apr_pool_t *result_pool,
+                                   apr_pool_t *scratch_pool)
+{
+  svn_config_get(wc_config, pristines_mode,
+                 "working-copy", "pristines-mode", NULL);
+  if (*pristines_mode)
+    {
+      *pristines_mode = apr_pstrdup(result_pool, *pristines_mode);
+      SVN_DBG(("pristines-mode from per-WC config: %s", *pristines_mode));
+    }
+  return SVN_NO_ERROR;
+}
+
 svn_error_t *
 svn_wc__db_pdh_create_wcroot(svn_wc__db_wcroot_t **wcroot,
                              const char *wcroot_abspath,
                              svn_sqlite__db_t *sdb,
                              apr_int64_t wc_id,
                              int format,
@@ -374,12 +422,23 @@ svn_wc__db_pdh_create_wcroot(svn_wc__db_
   (*wcroot)->format = format;
   /* 8 concurrent locks is probably more than a typical wc_ng based svn client
      uses. */
   (*wcroot)->owned_locks = apr_array_make(result_pool, 8,
                                           sizeof(svn_wc__db_wclock_t));
   (*wcroot)->access_cache = apr_hash_make(result_pool);
+  /* Read the pristines-mode setting in this WC.  */
+  if (format >= SVN_WC__PRISTINES_ON_DEMAND_VERSION)
+    {
+      svn_config_t *wc_config;
+
+      SVN_ERR(read_per_wc_config(&wc_config, wcroot_abspath,
+                                 scratch_pool, scratch_pool));
+      SVN_ERR(read_pristines_mode_from_wc_config(&(*wcroot)->pristines_mode,
+                                                 wc_config,
+                                                 result_pool, scratch_pool));
+    }
 
   /* SDB will be NULL for pre-NG working copies. We only need to run a
      cleanup when the SDB is present.  */
   if (sdb != NULL)
     apr_pool_cleanup_register(result_pool, *wcroot, close_wcroot,
                               apr_pool_cleanup_null);
@@ -1014,6 +1073,26 @@ svn_wc__db_drop_root(svn_wc__db_t *db,
   result = apr_pool_cleanup_run(db->state_pool, root_wcroot, close_wcroot);
   if (result != APR_SUCCESS)
     return svn_error_wrap_apr(result, NULL);
 
   return SVN_NO_ERROR;
 }
+
+svn_error_t *
+svn_wc__db_pristines_mode(svn_wc__db_t *db,
+                          const char *local_abspath,
+                          const char **mode,
+                          apr_pool_t *scratch_pool)
+{
+  svn_wc__db_wcroot_t *wcroot;
+  const char *local_relpath;
+
+  SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath, db,
+                                                local_abspath, scratch_pool,
+                                                scratch_pool));
+  VERIFY_USABLE_WCROOT(wcroot);
+
+  *mode = wcroot->pristines_mode;
+  if (! *mode)
+    *mode = db->pristines_mode;
+  return SVN_NO_ERROR;
+}
Index: subversion/libsvn_wc/wc.h
===================================================================
--- subversion/libsvn_wc/wc.h	(revision 1897454)
+++ subversion/libsvn_wc/wc.h	(working copy)
@@ -193,12 +193,16 @@ extern "C" {
 #define SVN_WC__HAS_WORK_QUEUE 13
 
 /* While we still have this DB version we should verify if there is
    sqlite_stat1 table on opening */
 #define SVN_WC__ENSURE_STAT1_TABLE 31
 
+/* Starting from this version, pristines can be missing and fetched on
+ * demand.  */
+#define SVN_WC__PRISTINES_ON_DEMAND_VERSION 32
+
 /* Return a string indicating the released version (or versions) of
  * Subversion that used WC format number WC_FORMAT, or some other
  * suitable string if no released version used WC_FORMAT.
  *
  * ### It's not ideal to encode this sort of knowledge in this low-level
  * library.  On the other hand, it doesn't need to be updated often and
@@ -287,12 +291,13 @@ struct svn_wc_traversal_info_t
 #define SVN_WC__ADM_FORMAT              "format"
 #define SVN_WC__ADM_ENTRIES             "entries"
 #define SVN_WC__ADM_TMP                 "tmp"
 #define SVN_WC__ADM_PRISTINE            "pristine"
 #define SVN_WC__ADM_NONEXISTENT_PATH    "nonexistent-path"
 #define SVN_WC__ADM_EXPERIMENTAL        "experimental"
+#define SVN_WC__ADM_CONFIG_FILE         "config"
 
 /* The basename of the ".prej" file, if a directory ever has property
    conflicts.  This .prej file will appear *within* the conflicted
    directory.  */
 #define SVN_WC__THIS_DIR_PREJ           "dir_conflicts"
 
