[svn.haxx.se] · SVN Dev · SVN Users · SVN Org · TSVN Dev · TSVN Users · Subclipse Dev · Subclipse Users · this month's index

[PATCH] Store wcprops in one file per .svn area

From: Peter N. Lundblad <peter_at_famlundblad.se>
Date: 2006-04-15 01:05:07 CEST

Hi,

There is one more wc format change I'd like in 1.4. (This is the last
change, promise!). The point is to avoid lots of wcprops files which
are typically very small (100 bytes ore something) but still take up
an inode and a disk block.

To avoid O(n^2) complexity when setting wcprops during update/commit,
I cache all wcprops in memory and write once at the end of the log run
like we do with entries. Switch currently has quadratic behaviour
when invalidating all wcprops, but that's easily fixed.

The patch is against the nonxml-entries branch because that was
simplest for me, but it isn't dependent on that work.

Any objections to the idea or to the patch in itself?

[[[
Use one file per admin area for wcprops instead of one file per entry.

* subversion/libsvn_wc/props.c
  (read_wcprops, svn_wc__wcprops_write): New function.
  (svn_wc__wcprop_list): Rename from wcprop_list(). Try to read wcprops
  into cache if possible. Change path argument to entryname.
  (wcprop_get): Rename from svn_wc__wcprop_get(). Adjust for new signature
  of svn_wc__wcprop_list().
  (svn_wc__wcprop_set): Add force_write parameter. Write from the cache
  if the WC format is new enough.
  (svn_wc__remove_wcprops): MOve here. Add name parameter to support
  removing all wcprops from a single entry. Support new format.

* subversion/libsvn_wc/props.h
  (svn_wc__wcprop_get): Remove prototype.
  (svn_wc__wcprop_list): Declare.
  (svn_wc__wcprop_set): Add force_write parameter. Callrs updated.
  (svn_wc__remove_wcprops): Add name parameter. Callrs updated.
  (svn_wc__wcprops_write): Declare.

* subversion/libsvn_wc/copy.c (svn_wc__remove_wcprops): Move to above location.

* subversion/libsvn_wc/wc.h (SVN_WC__ADM_ALL_WCPROPS): New macro.

* subversion/libsvn_wc/log.c
  (struct log_runner): Add wcprops_modified member.
  (log_do_modify_wcprop): Set wcprops_modified instead of writing the new
  props to disk immediately.
  (run_log): Initialize wcprops_modified member of log_runner. Write
  wcprops to disk if modified.

* subversion/libsvn_wc/adm_ops.c
  (svn_wc_remove_from_revision_control): Use svn_wc__remove_wcprops
  to remove wcprops. When removing a directory, remove all wcprops in this
  directory (including file children) to avoid quadratic behaviour.

* subversion/libsvn_wc/adm_files.c
  (init_adm_tmp_area, init_adm): Don't create wcprops directory.

* subversion/libsvn_wc/lock.c
  (struct svn_wc_adm_access_t): Add wcprop member.
  (convert_wcprops): New function.
  (maybe_upgrade_format): Convert wcprop from old to new format.
  (adm_access_alloc): Init the wcprops member of the lock to NULL.
  (svn_wc__adm_access_set_wcprops): New function.
  (svn_wc__adm_access_wcprops): New function.

* subversion/libsvn_wc/lock.h
  (svn_wc__adm_access_set_wcprops): Declare.
  (svn_wc__adm_access_wcprops): Declare.
]]]

Index: subversion/libsvn_wc/relocate.c
===================================================================
--- subversion/libsvn_wc/relocate.c (revision 19377)
+++ subversion/libsvn_wc/relocate.c (arbetskopia)
@@ -2,7 +2,7 @@
  * relocate.c: do wc repos relocation
  *
  * ====================================================================
- * Copyright (c) 2002-2004 CollabNet. All rights reserved.
+ * Copyright (c) 2002-2006 CollabNet. All rights reserved.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -176,7 +176,7 @@
                              validator, validator_baton, FALSE, pool));
     }
 
- SVN_ERR(svn_wc__remove_wcprops(adm_access, FALSE, pool));
+ SVN_ERR(svn_wc__remove_wcprops(adm_access, NULL, FALSE, pool));
   SVN_ERR(svn_wc__entries_write(entries, adm_access, pool));
   return SVN_NO_ERROR;
 }
Index: subversion/libsvn_wc/props.c
===================================================================
--- subversion/libsvn_wc/props.c (revision 19377)
+++ subversion/libsvn_wc/props.c (arbetskopia)
@@ -2,7 +2,7 @@
  * props.c : routines dealing with properties in the working copy
  *
  * ====================================================================
- * Copyright (c) 2000-2004 CollabNet. All rights reserved.
+ * Copyright (c) 2000-2006 CollabNet. All rights reserved.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -826,36 +826,185 @@
 
 /*** Private 'wc prop' functions ***/
 
-/* A clone of svn_wc_prop_list, for the most part, except that it
- returns 'wc' props instead of normal props. */
+/* If wcprops are stored in a single file in this working copy, read that file
+ and store it in the cache of ADM_ACCESS. Use POOL for temporary
+ allocations. */
 static svn_error_t *
-wcprop_list(apr_hash_t **props,
- const char *path,
- svn_wc_adm_access_t *adm_access,
- apr_pool_t *pool)
+read_wcprops(svn_wc_adm_access_t *adm_access, apr_pool_t *pool)
 {
+ apr_file_t *file;
+ apr_pool_t *cache_pool = svn_wc_adm_access_pool(adm_access);
+ apr_hash_t *wcprops;
+ /* Props for one entry. */
+ apr_hash_t *proplist;
+ svn_stream_t *stream;
+ svn_error_t *err;
+
+ /* If the WC format is too old, there is nothing to cache. */
+ /* ### XXX. Own constant? */
+ if (svn_wc__adm_wc_format(adm_access) <= SVN_WC__XML_ENTRIES_VERSION)
+ return SVN_NO_ERROR;
+
+ wcprops = apr_hash_make(cache_pool);
+
+ err = svn_wc__open_adm_file(&file, svn_wc_adm_access_path(adm_access),
+ SVN_WC__ADM_ALL_WCPROPS,
+ APR_READ | APR_BUFFERED, pool);
+
+ /* A non-existent file means there are no props. */
+ if (err && err->apr_err == APR_ENOENT)
+ {
+ svn_error_clear(err);
+ svn_wc__adm_access_set_wcprops(adm_access, wcprops);
+ return SVN_NO_ERROR;
+ }
+ SVN_ERR(err);
+
+ stream = svn_stream_from_aprfile2(file, TRUE, pool);
+
+ /* Read the proplist for THIS_DIR. */
+ proplist = apr_hash_make(cache_pool);
+ SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, cache_pool));
+ apr_hash_set(wcprops, SVN_WC_ENTRY_THIS_DIR, APR_HASH_KEY_STRING, proplist);
+
+ /* And now, the children. */
+ while (1729)
+ {
+ svn_stringbuf_t *line;
+ svn_boolean_t eof;
+ SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, cache_pool));
+ if (eof)
+ {
+ if (line->len > 0)
+ return svn_error_createf
+ (SVN_ERR_WC_CORRUPT, NULL,
+ _("Missing end of line in wcprops file '%s'"),
+ svn_path_local_style(svn_wc_adm_access_path(adm_access), pool));
+ break;
+ }
+ proplist = apr_hash_make(cache_pool);
+ SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR,
+ cache_pool));
+ apr_hash_set(wcprops, line->data, APR_HASH_KEY_STRING, proplist);
+ }
+
+ svn_wc__adm_access_set_wcprops(adm_access, wcprops);
+
+ SVN_ERR(svn_wc__close_adm_file(file, svn_wc_adm_access_path(adm_access),
+ SVN_WC__ADM_ALL_WCPROPS, FALSE, pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__wcprops_write(svn_wc_adm_access_t *adm_access, apr_pool_t *pool)
+{
+ apr_hash_t *wcprops = svn_wc__adm_access_wcprops(adm_access);
+ apr_file_t *file;
+ svn_stream_t *stream;
+ apr_hash_t *proplist;
+ apr_hash_index_t *hi;
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ /* If there are no cached wcprops, there is nothing to do. */
+ if (! wcprops)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_wc__open_adm_file(&file, svn_wc_adm_access_path(adm_access),
+ SVN_WC__ADM_ALL_WCPROPS,
+ APR_WRITE | APR_BUFFERED, pool));
+ stream = svn_stream_from_aprfile2(file, TRUE, pool);
+
+ /* First, the props for this_dir. */
+ proplist = apr_hash_get(wcprops, SVN_WC_ENTRY_THIS_DIR, APR_HASH_KEY_STRING);
+ if (! proplist)
+ proplist = apr_hash_make(subpool);
+ SVN_ERR(svn_hash_write2(proplist, stream, SVN_HASH_TERMINATOR, subpool));
+
+ /* Write children. */
+ for (hi = apr_hash_first(pool, wcprops); hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ void *val;
+ const char *name;
+
+ apr_hash_this(hi, &key, NULL, &val);
+ name = key;
+ proplist = val;
+
+ /* We already wrote this_dir. */
+ if (strcmp(SVN_WC_ENTRY_THIS_DIR, name) == 0)
+ continue;
+
+ svn_pool_clear(subpool);
+
+ svn_stream_printf(stream, subpool, "%s\n", name);
+ SVN_ERR(svn_hash_write2(proplist, stream, SVN_HASH_TERMINATOR, subpool));
+ }
+
+ SVN_ERR(svn_wc__close_adm_file(file, svn_wc_adm_access_path(adm_access),
+ SVN_WC__ADM_ALL_WCPROPS, TRUE, pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__wcprop_list(apr_hash_t **wcprops,
+ const char *entryname,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *pool)
+{
   const char *prop_path;
   const svn_wc_entry_t *entry;
+ apr_hash_t *all_wcprops;
+ apr_pool_t *cache_pool = svn_wc_adm_access_pool(adm_access);
+ const char *path = svn_path_join(svn_wc_adm_access_path(adm_access),
+ entryname, pool);
 
- *props = apr_hash_make(pool);
-
- /* Check validity of PATH */
- /* Construct a path to the relevant property file */
   SVN_ERR(svn_wc_entry(&entry, path, adm_access, FALSE, pool));
   if (! entry)
- /* No entry exists, therefore no wcprop-file can exist */
- return SVN_NO_ERROR;
+ {
+ /* No entry exists, therefore no wcprop-file can exist */
+ *wcprops = apr_hash_make(pool);
+ return SVN_NO_ERROR;
+ }
 
+ /* Try the cache first. */
+ all_wcprops = svn_wc__adm_access_wcprops(adm_access);
+ if (! all_wcprops)
+ {
+ SVN_ERR(read_wcprops(adm_access, pool));
+ all_wcprops = svn_wc__adm_access_wcprops(adm_access);
+ }
+ if (all_wcprops)
+ {
+ *wcprops = apr_hash_get(all_wcprops, entryname, APR_HASH_KEY_STRING);
+ /* The cache contains no hash tables for empty proplist, so we just
+ create one here if that's the case. */
+ if (! *wcprops)
+ {
+ *wcprops = apr_hash_make(cache_pool);
+ entryname = apr_pstrdup(cache_pool, entryname);
+ apr_hash_set(all_wcprops, entryname, APR_HASH_KEY_STRING, *wcprops);
+ }
+ return SVN_NO_ERROR;
+ }
+
+ /* Fall back on individual files for backwards compatibility. */
+
+ /* Construct a path to the relevant property file */
   SVN_ERR(svn_wc__wcprop_path(&prop_path, path, entry->kind, FALSE, pool));
+ *wcprops = apr_hash_make(pool);
+ SVN_ERR(svn_wc__load_prop_file(prop_path, *wcprops, pool));
 
- SVN_ERR(svn_wc__load_prop_file(prop_path, *props, pool));
-
   return SVN_NO_ERROR;
 }
 
 
-svn_error_t *
-svn_wc__wcprop_get(const svn_string_t **value,
+/* Get a single 'wcprop' NAME for versioned object PATH, return in
+ *VALUE. ADM_ACCESS is an access baton set that contains PATH. */
+static svn_error_t *
+wcprop_get(const svn_string_t **value,
                    const char *name,
                    const char *path,
                    svn_wc_adm_access_t *adm_access,
@@ -863,8 +1012,21 @@
 {
   svn_error_t *err;
   apr_hash_t *prophash;
+ const svn_wc_entry_t *entry;
 
- err = wcprop_list(&prophash, path, adm_access, pool);
+ SVN_ERR(svn_wc_entry(&entry, path, adm_access, FALSE, pool));
+ if (! entry)
+ {
+ *value = NULL;
+ return SVN_NO_ERROR;
+ }
+ if (entry->kind == svn_node_dir)
+ SVN_ERR(svn_wc_adm_retrieve(&adm_access, adm_access, path, pool));
+ else
+ SVN_ERR(svn_wc_adm_retrieve(&adm_access, adm_access,
+ svn_path_dirname(path, pool), pool));
+
+ err = svn_wc__wcprop_list(&prophash, entry->name, adm_access, pool);
   if (err)
     return
       svn_error_quick_wrap
@@ -880,13 +1042,27 @@
                    const svn_string_t *value,
                    const char *path,
                    svn_wc_adm_access_t *adm_access,
+ svn_boolean_t force_write,
                    apr_pool_t *pool)
 {
   svn_error_t *err;
   apr_hash_t *prophash;
   apr_file_t *fp = NULL;
+ apr_pool_t *cache_pool = svn_wc_adm_access_pool(adm_access);
+ const svn_wc_entry_t *entry;
 
- err = wcprop_list(&prophash, path, adm_access, pool);
+ SVN_ERR(svn_wc_entry(&entry, path, adm_access, FALSE, pool));
+ if (! entry)
+ return svn_error_createf(SVN_ERR_UNVERSIONED_RESOURCE, NULL,
+ _("'%s' is not under version control"),
+ svn_path_local_style(path, pool));
+
+ if (entry->kind == svn_node_dir)
+ SVN_ERR(svn_wc_adm_retrieve(&adm_access, adm_access, path, pool));
+ else
+ SVN_ERR(svn_wc_adm_retrieve(&adm_access, adm_access,
+ svn_path_dirname(path, pool), pool));
+ err = svn_wc__wcprop_list(&prophash, entry->name, adm_access, pool);
   if (err)
     return
       svn_error_quick_wrap
@@ -894,32 +1070,130 @@
 
   /* Now we have all the properties in our hash. Simply merge the new
      property into it. */
+ name = apr_pstrdup(cache_pool, name);
+ if (value)
+ value = svn_string_dup(value, cache_pool);
   apr_hash_set(prophash, name, APR_HASH_KEY_STRING, value);
 
- /* Open the propfile for writing. */
- SVN_ERR(svn_wc__open_props(&fp,
- path, /* open in PATH */
- (APR_WRITE | APR_CREATE | APR_BUFFERED),
- 0, /* not base props */
- 1, /* we DO want wcprops */
- pool));
- /* Write. */
- SVN_ERR_W(svn_hash_write(prophash, fp, pool),
- apr_psprintf(pool,
- _("Cannot write property hash for '%s'"),
- svn_path_local_style(path, pool)));
+ if (svn_wc__adm_wc_format(adm_access) > SVN_WC__XML_ENTRIES_VERSION)
+ {
+ if (force_write)
+ SVN_ERR(svn_wc__wcprops_write(adm_access, pool));
+ }
+ else
+ {
+ /* For backwards compatibility. We don't use the cache in this case,
+ so write to disk regardless of force_write. */
+ /* Open the propfile for writing. */
+ SVN_ERR(svn_wc__open_props(&fp,
+ path, /* open in PATH */
+ (APR_WRITE | APR_CREATE | APR_BUFFERED),
+ 0, /* not base props */
+ 1, /* we DO want wcprops */
+ pool));
+ /* Write. */
+ SVN_ERR_W(svn_hash_write(prophash, fp, pool),
+ apr_psprintf(pool,
+ _("Cannot write property hash for '%s'"),
+ svn_path_local_style(path, pool)));
   
- /* Close file, and doing an atomic "move". */
- SVN_ERR(svn_wc__close_props(fp, path, 0, 1,
- 1, /* sync! */
- pool));
+ /* Close file, doing an atomic "move". */
+ SVN_ERR(svn_wc__close_props(fp, path, 0, 1,
+ 1, /* sync! */
+ pool));
+ }
 
   return SVN_NO_ERROR;
 }
 
+svn_error_t *
+svn_wc__remove_wcprops(svn_wc_adm_access_t *adm_access,
+ const char *name,
+ svn_boolean_t recurse,
+ apr_pool_t *pool)
+{
+ apr_hash_t *all_wcprops = svn_wc__adm_access_wcprops(adm_access);
+ svn_boolean_t write_needed = FALSE;
 
+ if (! name)
+ {
+ /* There is no point in reading the props just to determine if we
+ need to rewrite them:-), so assume a write is needed if the props
+ aren't already cached. */
+ if (! all_wcprops || apr_hash_count(all_wcprops) > 0)
+ {
+ svn_wc__adm_access_set_wcprops
+ (adm_access, apr_hash_make(svn_wc_adm_access_pool(adm_access)));
+ write_needed = TRUE;
+ }
+ }
+ else
+ {
+ apr_hash_t *wcprops;
+ if (! all_wcprops)
+ {
+ SVN_ERR(read_wcprops(adm_access, pool));
+ all_wcprops = svn_wc__adm_access_wcprops(adm_access);
+ }
+ if (all_wcprops)
+ wcprops = apr_hash_get(all_wcprops, name, APR_HASH_KEY_STRING);
+ else
+ wcprops = NULL;
+ if (wcprops && apr_hash_count(wcprops) > 0)
+ {
+ apr_hash_set(all_wcprops, name, APR_HASH_KEY_STRING, NULL);
+ write_needed = TRUE;
+ }
+ }
+ if (write_needed)
+ SVN_ERR(svn_wc__wcprops_write(adm_access, pool));
 
+ if (recurse)
+ {
+ apr_hash_t *entries;
+ apr_hash_index_t *hi;
+ apr_pool_t *subpool = svn_pool_create(pool);
 
+ /* Read PATH's entries. */
+ SVN_ERR(svn_wc_entries_read(&entries, adm_access, FALSE, subpool));
+
+ /* Recursively loop over all children. */
+ for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ void *val;
+ const char *entryname;
+ const svn_wc_entry_t *current_entry;
+ const char *child_path;
+ svn_wc_adm_access_t *child_access;
+
+ apr_hash_this(hi, &key, NULL, &val);
+ entryname = key;
+ current_entry = val;
+
+ /* Ignore files the "this dir" entry. */
+ if (current_entry->kind != svn_node_dir
+ || ! strcmp(entryname, SVN_WC_ENTRY_THIS_DIR))
+ continue;
+
+ svn_pool_clear(subpool);
+
+ child_path = svn_path_join(svn_wc_adm_access_path(adm_access),
+ entryname, subpool);
+
+ SVN_ERR(svn_wc_adm_retrieve(&child_access, adm_access, child_path,
+ subpool));
+ SVN_ERR(svn_wc__remove_wcprops(child_access, NULL, TRUE, subpool));
+ }
+
+ /* Cleanup */
+ svn_pool_destroy(subpool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
 /*------------------------------------------------------------------*/
 
 /*** Public Functions ***/
@@ -1048,7 +1322,7 @@
 
   if (kind == svn_prop_wc_kind)
     {
- return svn_wc__wcprop_get(value, name, path, adm_access, pool);
+ return wcprop_get(value, name, path, adm_access, pool);
     }
   if (kind == svn_prop_entry_kind)
     {
@@ -1186,7 +1460,7 @@
   const svn_wc_entry_t *entry;
 
   if (prop_kind == svn_prop_wc_kind)
- return svn_wc__wcprop_set(name, value, path, adm_access, pool);
+ return svn_wc__wcprop_set(name, value, path, adm_access, TRUE, pool);
   else if (prop_kind == svn_prop_entry_kind)
     return svn_error_createf /* we don't do entry properties here */
       (SVN_ERR_BAD_PROP_KIND, NULL,
Index: subversion/libsvn_wc/props.h
===================================================================
--- subversion/libsvn_wc/props.h (revision 19377)
+++ subversion/libsvn_wc/props.h (arbetskopia)
@@ -2,7 +2,7 @@
  * props.h : properties
  *
  * ====================================================================
- * Copyright (c) 2000-2004 CollabNet. All rights reserved.
+ * Copyright (c) 2000-2006 CollabNet. All rights reserved.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -87,29 +87,47 @@
                                  svn_stringbuf_t **entry_accum);
 
 
-/* Get a single 'wcprop' NAME for versioned object PATH, return in
- *VALUE. ADM_ACCESS is an access baton set that contains PATH. */
-svn_error_t *svn_wc__wcprop_get(const svn_string_t **value,
- const char *name,
- const char *path,
- svn_wc_adm_access_t *adm_access,
- apr_pool_t *pool);
+/* Return a list of wc props for ENTRYNAME in ADM_ACCESS.
+ ENTRYNAME must be the name of a file or SVN_WC_ENTRY_THIS_DIR.
+
+ The returned WCPROPS may be allocated in POOL, or may be the props
+ cached in ADM_ACCESS. */
+svn_error_t *
+svn_wc__wcprop_list(apr_hash_t **wcprops,
+ const char *entryname,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *pool);
 
 /* Set a single 'wcprop' NAME to VALUE for versioned object PATH.
    If VALUE is null, remove property NAME. ADM_ACCESS is an access
- baton set that contains PATH. */
+ baton set that contains PATH.
+
+ If FORCE_WRITE is true, then the change will be written to disk
+ immediately. Else, the in memory cache (if that is used) will only
+ be updated and the caller is expected to use
+ svn_wc__wcprops_write() later on the correct access baton to store
+ the change persistently. */
 svn_error_t *svn_wc__wcprop_set(const char *name,
                                 const svn_string_t *value,
                                 const char *path,
                                 svn_wc_adm_access_t *adm_access,
+ svn_boolean_t force_write,
                                 apr_pool_t *pool);
 
-/* Remove all wc properties under ADM_ACCESS, recursively. Do any
- temporary allocation in POOL. */
+/* Remove wcprops for entry NAME under ADM_ACCESS, or for all files
+ and this_dir if NAME is null. Recurse into subdirectories if
+ RECURSE is true. Use POOL for temporary allocations. */
 svn_error_t *svn_wc__remove_wcprops(svn_wc_adm_access_t *adm_access,
+ const char *name,
                                     svn_boolean_t recurse,
                                     apr_pool_t *pool);
 
+/* Write the wcprops cached in ADM_ACCESS, if any, to disk using POOL for
+ temporary allocations. */
+svn_error_t *
+svn_wc__wcprops_write(svn_wc_adm_access_t *adm_access, apr_pool_t *pool);
+
+
 /* Returns TRUE if PROPS contains the svn:special property */
 svn_boolean_t svn_wc__has_special_property(apr_hash_t *props);
 
Index: subversion/libsvn_wc/copy.c
===================================================================
--- subversion/libsvn_wc/copy.c (revision 19377)
+++ subversion/libsvn_wc/copy.c (arbetskopia)
@@ -2,7 +2,7 @@
  * copy.c: wc 'copy' functionality.
  *
  * ====================================================================
- * Copyright (c) 2000-2004 CollabNet. All rights reserved.
+ * Copyright (c) 2000-2006 CollabNet. All rights reserved.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -39,75 +39,6 @@
 
 /*** Code. ***/
 
-svn_error_t *
-svn_wc__remove_wcprops(svn_wc_adm_access_t *adm_access,
- svn_boolean_t recurse,
- apr_pool_t *pool)
-{
- apr_hash_t *entries;
- apr_hash_index_t *hi;
- const char *wcprop_path;
- apr_pool_t *subpool = svn_pool_create(pool);
-
- /* Read PATH's entries. */
- SVN_ERR(svn_wc_entries_read(&entries, adm_access, FALSE, subpool));
-
- /* Remove this_dir's wcprops */
- SVN_ERR(svn_wc__wcprop_path(&wcprop_path,
- svn_wc_adm_access_path(adm_access),
- svn_node_dir, FALSE, subpool));
- svn_error_clear(svn_io_remove_file(wcprop_path, subpool));
-
- /* Recursively loop over all children. */
- for (hi = apr_hash_first(subpool, entries); hi; hi = apr_hash_next(hi))
- {
- const void *key;
- void *val;
- const char *name;
- const svn_wc_entry_t *current_entry;
- const char *child_path;
-
- apr_hash_this(hi, &key, NULL, &val);
- name = key;
- current_entry = val;
-
- /* Ignore the "this dir" entry. */
- if (! strcmp(name, SVN_WC_ENTRY_THIS_DIR))
- continue;
-
- child_path = svn_path_join(svn_wc_adm_access_path(adm_access), name,
- subpool);
-
- /* If a file, remove it from wcprops. */
- if (current_entry->kind == svn_node_file)
- {
- SVN_ERR(svn_wc__wcprop_path(&wcprop_path, child_path,
- svn_node_file, FALSE, subpool));
- svn_error_clear(svn_io_remove_file(wcprop_path, subpool));
- /* ignoring any error value from the removal; most likely,
- apr_file_remove will complain about trying to a remove a
- file that's not there. But this more efficient than
- doing an independant stat for each file's existence
- before trying to remove it, no? */
- }
-
- /* If a dir, recurse. */
- else if (recurse && current_entry->kind == svn_node_dir)
- {
- svn_wc_adm_access_t *child_access;
- SVN_ERR(svn_wc_adm_retrieve(&child_access, adm_access, child_path,
- subpool));
- SVN_ERR(svn_wc__remove_wcprops(child_access, recurse, subpool));
- }
- }
-
- /* Cleanup */
- svn_pool_destroy(subpool);
-
- return SVN_NO_ERROR;
-}
-
-
 /* This function effectively creates and schedules a file for
    addition, but does extra administrative things to allow it to
    function as a 'copy'.
@@ -251,7 +182,7 @@
   const char *path = svn_wc_adm_access_path(adm_access);
   
   /* Remove wcprops. */
- SVN_ERR(svn_wc__remove_wcprops(adm_access, FALSE, pool));
+ SVN_ERR(svn_wc__remove_wcprops(adm_access, NULL, FALSE, pool));
 
   /* Read this directory's entries file. */
   SVN_ERR(svn_wc_entries_read(&entries, adm_access, FALSE, pool));
Index: subversion/libsvn_wc/wc.h
===================================================================
--- subversion/libsvn_wc/wc.h (revision 19377)
+++ subversion/libsvn_wc/wc.h (arbetskopia)
@@ -122,6 +122,7 @@
 #define SVN_WC__ADM_DIR_PROP_REVERT "dir-prop-revert"
 #define SVN_WC__ADM_WCPROPS "wcprops"
 #define SVN_WC__ADM_DIR_WCPROPS "dir-wcprops"
+#define SVN_WC__ADM_ALL_WCPROPS "all-wcprops"
 #define SVN_WC__ADM_LOG "log"
 #define SVN_WC__ADM_KILLME "KILLME"
 
Index: subversion/libsvn_wc/log.c
===================================================================
--- subversion/libsvn_wc/log.c (revision 19377)
+++ subversion/libsvn_wc/log.c (arbetskopia)
@@ -172,6 +172,7 @@
   apr_pool_t *pool;
   svn_xml_parser_t *parser;
   svn_boolean_t entries_modified;
+ svn_boolean_t wcprops_modified;
   svn_boolean_t rerun;
   svn_wc_adm_access_t *adm_access; /* the dir in which all this happens */
   const char *diff3_cmd; /* external diff3 cmd, or null if none */
@@ -1399,8 +1400,12 @@
       value.len = strlen(propval);
     }
 
- return svn_wc__wcprop_set(propname, propval ? &value : NULL,
- path, loggy->adm_access, loggy->pool);
+ SVN_ERR(svn_wc__wcprop_set(propname, propval ? &value : NULL,
+ path, loggy->adm_access, FALSE, loggy->pool));
+
+ loggy->wcprops_modified = TRUE;
+
+ return SVN_NO_ERROR;
 }
 
 static svn_error_t *
@@ -1632,6 +1637,7 @@
   loggy->pool = svn_pool_create(pool);
   loggy->parser = parser;
   loggy->entries_modified = FALSE;
+ loggy->wcprops_modified = FALSE;
   loggy->rerun = rerun;
   loggy->diff3_cmd = diff3_cmd;
   loggy->count = 0;
@@ -1702,6 +1708,8 @@
       SVN_ERR(svn_wc_entries_read(&entries, loggy->adm_access, TRUE, pool));
       SVN_ERR(svn_wc__entries_write(entries, loggy->adm_access, pool));
     }
+ if (loggy->wcprops_modified)
+ SVN_ERR(svn_wc__wcprops_write(loggy->adm_access, pool));
 
   /* Check for a 'killme' file in the administrative area. */
   if (svn_wc__adm_path_exists(svn_wc_adm_access_path(adm_access), 0, pool,
Index: subversion/libsvn_wc/adm_ops.c
===================================================================
--- subversion/libsvn_wc/adm_ops.c (revision 19377)
+++ subversion/libsvn_wc/adm_ops.c (arbetskopia)
@@ -1262,7 +1262,7 @@
                             pool));
 
           /* Clean out the now-obsolete wcprops. */
- SVN_ERR(svn_wc__remove_wcprops(adm_access, TRUE, pool));
+ SVN_ERR(svn_wc__remove_wcprops(adm_access, NULL, TRUE, pool));
         }
     }
 
@@ -1871,13 +1871,15 @@
                                  _("File '%s' has local modifications"),
                                  svn_path_local_style(full_path, pool));
 
+ /* Remove the wcprops. */
+ SVN_ERR(svn_wc__remove_wcprops(adm_access, name, FALSE, pool));
+
       /* Remove NAME from PATH's entries file: */
       SVN_ERR(svn_wc_entries_read(&entries, adm_access, TRUE, pool));
       svn_wc__entry_remove(entries, name);
       SVN_ERR(svn_wc__entries_write(entries, adm_access, pool));
 
- /* Remove text-base/NAME.svn-base, prop/NAME, prop-base/NAME.svn-base,
- wcprops/NAME */
+ /* Remove text-base/NAME.svn-base, prop/NAME, prop-base/NAME.svn-base. */
       {
         const char *svn_thang;
 
@@ -1898,11 +1900,6 @@
                                        FALSE, pool));
         SVN_ERR(remove_file_if_present(svn_thang, pool));
 
- /* wc-prop file. */
- SVN_ERR(svn_wc__wcprop_path(&svn_thang, full_path,
- is_file ? svn_node_file : svn_node_dir,
- FALSE, pool));
- SVN_ERR(remove_file_if_present(svn_thang, pool));
       }
 
       /* If we were asked to destroy the working file, do so unless
@@ -1937,6 +1934,11 @@
                                    TRUE, /* sync to disk immediately */
                                    pool));
       
+ /* Get rid of all the wcprops in this directory. This avoids rewriting
+ the wcprops file over and over (meaning O(n^2) complexity)
+ below. */
+ SVN_ERR(svn_wc__remove_wcprops(adm_access, NULL, FALSE, pool));
+
       /* Walk over every entry. */
       SVN_ERR(svn_wc_entries_read(&entries, adm_access, FALSE, pool));
       
Index: subversion/libsvn_wc/adm_files.c
===================================================================
--- subversion/libsvn_wc/adm_files.c (revision 19377)
+++ subversion/libsvn_wc/adm_files.c (arbetskopia)
@@ -1016,10 +1016,6 @@
   SVN_ERR(svn_wc__make_adm_thing(adm_access, SVN_WC__ADM_PROPS,
                                  svn_node_dir, perms, 1, pool));
 
- /* SVN_WC__ADM_TMP/SVN_WC__ADM_WCPROPS */
- SVN_ERR(svn_wc__make_adm_thing(adm_access, SVN_WC__ADM_WCPROPS,
- svn_node_dir, perms, 1, pool));
-
   return SVN_NO_ERROR;
 }
 
@@ -1063,10 +1059,6 @@
   SVN_ERR(svn_wc__make_adm_thing(adm_access, SVN_WC__ADM_PROPS,
                                  svn_node_dir, perms, 0, pool));
 
- /* SVN_WC__ADM_WCPROPS */
- SVN_ERR(svn_wc__make_adm_thing(adm_access, SVN_WC__ADM_WCPROPS,
- svn_node_dir, perms, 0, pool));
-
   /** Init the tmp area. ***/
   SVN_ERR(init_adm_tmp_area(adm_access, pool));
   
Index: subversion/libsvn_wc/lock.c
===================================================================
--- subversion/libsvn_wc/lock.c (revision 19377)
+++ subversion/libsvn_wc/lock.c (arbetskopia)
@@ -77,6 +77,13 @@
   apr_hash_t *entries;
   apr_hash_t *entries_hidden;
 
+ /* A hash mapping const char * entry names to hashes of wcprops.
+ These hashes map const char * names to svn_string_t * values.
+ NULL of the wcprops hasn't been read into memory.
+ ### Since there are typically just one or two wcprops per entry,
+ ### we could use a more compact way of storing them. */
+ apr_hash_t *wcprops;
+
   /* POOL is used to allocate cached items, they need to persist for the
      lifetime of this access baton */
   apr_pool_t *pool;
@@ -143,6 +150,66 @@
   return SVN_NO_ERROR;
 }
 
+/* Write, to LOG_ACCUM, commands to convert a WC that has wcprops in individual
+ files to use one wcprops file per directory.
+ Do this for ADM_ACCESS and its file children, using POOL for temporary
+ allocations. */
+static svn_error_t *
+convert_wcprops(svn_stringbuf_t *log_accum,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *pool)
+{
+ apr_hash_t *entries;
+ apr_hash_index_t *hi;
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ SVN_ERR(svn_wc_entries_read(&entries, adm_access, FALSE, pool));
+
+ /* Walk over the entries, adding a modify-wcprop command for each wcprop.
+ Note that the modifications happen in memory and are just written once
+ at the end of the log execution, so this isn't as inefficient as it
+ might sound. */
+ for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
+ {
+ void *val;
+ const svn_wc_entry_t *entry;
+ apr_hash_t *wcprops;
+ apr_hash_index_t *hj;
+
+ apr_hash_this(hi, NULL, NULL, &val);
+ entry = val;
+
+ if (entry->kind != svn_node_file
+ && strcmp(entry->name, SVN_WC_ENTRY_THIS_DIR) != 0)
+ continue;
+
+ svn_pool_clear(subpool);
+
+ SVN_ERR(svn_wc__wcprop_list(&wcprops, entry->name, adm_access, subpool));
+
+ /* Create a subsubpool for the inner loop...
+ No, just kidding. There are typically just one or two wcprops
+ per entry... */
+ for (hj = apr_hash_first(subpool, wcprops); hj; hj = apr_hash_next(hj))
+ {
+ const void *key2;
+ void *val2;
+ const char *propname;
+ svn_string_t *propval;
+
+ apr_hash_this(hj, &key2, NULL, &val2);
+ propname = key2;
+ propval = val2;
+ SVN_ERR(svn_wc__loggy_modify_wcprop(&log_accum, adm_access,
+ entry->name, propname,
+ propval->data,
+ subpool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
 /* Maybe upgrade the working copy directory represented by ADM_ACCESS
    to the latest 'SVN_WC__VERSION'. ADM_ACCESS must contain a write
    lock. Use POOL for all temporary allocation.
@@ -181,6 +248,11 @@
       if (adm_access->wc_format <= SVN_WC__NO_PROPCACHING_VERSION)
         SVN_ERR(introduce_propcaching(log_accum, adm_access, pool));
 
+ /* If the WC uses one file per entry for wcprops, give back some inodes
+ to the poor user. */
+ if (adm_access->wc_format <= SVN_WC__XML_ENTRIES_VERSION)
+ SVN_ERR(convert_wcprops(log_accum, adm_access, pool));
+
       SVN_ERR(svn_wc__write_log(adm_access, 0, log_accum, pool));
       SVN_ERR(svn_wc__run_log(adm_access, NULL, pool));
     }
@@ -297,6 +369,7 @@
   lock->type = type;
   lock->entries = NULL;
   lock->entries_hidden = NULL;
+ lock->wcprops = NULL;
   lock->wc_format = 0;
   lock->set = NULL;
   lock->lock_exists = FALSE;
@@ -1396,7 +1469,20 @@
     return adm_access->entries_hidden;
 }
 
+void
+svn_wc__adm_access_set_wcprops(svn_wc_adm_access_t *adm_access,
+ apr_hash_t *wcprops)
+{
+ adm_access->wcprops = wcprops;
+}
 
+apr_hash_t *
+svn_wc__adm_access_wcprops(svn_wc_adm_access_t *adm_access)
+{
+ return adm_access->wcprops;
+}
+
+
 int
 svn_wc__adm_wc_format(svn_wc_adm_access_t *adm_access)
 {
Index: subversion/libsvn_wc/lock.h
===================================================================
--- subversion/libsvn_wc/lock.h (revision 19377)
+++ subversion/libsvn_wc/lock.h (arbetskopia)
@@ -69,6 +69,15 @@
                                        svn_boolean_t show_deleted,
                                        apr_pool_t *pool);
 
+/* Store the WCPROPS in the cache in ADM_ACCESS. */
+void svn_wc__adm_access_set_wcprops(svn_wc_adm_access_t *adm_access,
+ apr_hash_t *wcprops);
+
+/* Retrieve the wcprops cached in ADM_ACCESS, if any. */
+apr_hash_t *svn_wc__adm_access_wcprops(svn_wc_adm_access_t *adm_access);
+
+
+
 /* Return an access baton for PATH in *ADM_ACCESS. This function is used
    to lock the working copy during construction of the admin area, it
    necessarily does less checking than svn_wc_adm_open3. */

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org
Received on Sat Apr 15 01:06:37 2006

This is an archived mail posted to the Subversion Dev mailing list.

This site is subject to the Apache Privacy Policy and the Apache Public Forum Archive Policy.