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

PATCH: Perform multi-file operations in sorted order

From: Julian Foad <julianfoad_at_btopenworld.com>
Date: 2004-06-01 00:34:55 CEST

In the interests of predictability, readability, comparability, etc., this patch makes most Subversion operations proceed in the sorted order of file names. I particularly like seeing "svn st" list files in order, and "svn diff" producing a patch file which can be meaningfully compared with a previous iteration of the same patch.

I would appreciate any high or low level review of this. Unfortunately during the next few weeks I will be very seldom at e-mail, so please don't expect a quick response from me.

I have as yet failed to get the hardest part working, which is sorting the combination of local and repository information for "svn diff -rX[:Y]" and "svn status --show-updates". The crux of this seems to be sorting the various add/change/delete operations within subversion/libsvn_repos/reporter.c (delta_dirs), but when I try to do that various things fail. It may be theoretically impossible but I do not yet see any reason why. I may try that again and/or request help on it later, but, even without it, having local operations sorted seems very nice to me.

(The log message appears in line below, and then as part of the attached patch.)

Regards,

- Julian

[[[
Perform multi-file operations in the sorted order of the file names.

This makes the output of operations like "svn diff" and "svn status"
predictable, repeatable, easy to read, etc., for the user's benefit, and
also provides a better foundation for machine processing of Subversion
output and even internal operations.

This patch addresses the following operations (+=yes, .=n/a, -=no):
+ add
. blame
. cat
+ checkout
. cleanup
+ commit
. copy
+ delete [1]
+ diff (locally) [1]
- diff (contacting repository) [1]
+ export
. help
+ import
+ info
+ list
. log
+ merge [1]
. mkdir
+ move
. propedit
+ propget,proplist
+ propset,propdel
+ resolved
+ revert
+ status (locally)
- status (contacting repository)
+ switch,update [1]

[1] Sorted except that directories can be reported after their children.

File and directory names are sorted in the numerical order of their Unicode
representations. This order is fixed (because it is implemented on the server
as well as the client) and is not affected by locale or case-insensitive file
systems.

This patch sorts sequences of path names that are generated within Subversion.
It does not, for instance, sort a list of targets specified by the user.

Tweak a regression test so that it does not depend on "svn status" reporting
unversioned files before ".".

* subversion/clients/cmdline/propget-cmd.c (svn_cl__propget)
  Sort the properties by filename before printing them.

* subversion/libsvn_client/commit.c (import_dir)
  Call svn_io_get_dirents instead of doing the hard work manually.
  Sort the dir entries before processing and recursing.

* subversion/libsvn_client/export.c (copy_versioned_files)
  Sort the dir entries before processing and recursing.

* subversion/libsvn_client/prop_commands.c (remote_propget, remote_proplist)
  Sort the dir entries before processing and recursing.

* subversion/libsvn_repos/delta.c (delta_dirs)
  Bring together all additions, changes and deletions rather than leaving
    deletions until afterwards.
  Sort the FS dir entries before processing and recursing, so as to
    emit the editing operations in sorted order.

* subversion/libsvn_wc/adm_crawler.c (report_revisions)
* subversion/libsvn_wc/adm_ops.c (svn_wc_revert, mark_tree)
* subversion/libsvn_wc/diff.c (directory_elements_diff)
* subversion/libsvn_wc/entries.c (walker_helper)
  Sort the WC entries before processing and recursing.

* subversion/libsvn_wc/status.c
  (get_dir_status): Bring together all versioned and unversioned items from
    a single directory, instead of handling all the unversioned items first.
    Sort them before recursing and reporting.
  (handle_statii): Sort the entries before recursing and reporting.
  (close_directory): Report "." before its children, not after them.

* subversion/tests/clients/cmdline/stat_tests.py
  (status_for_unignored_file): Commit the property change so that no status is
    reported for "this dir", to avoid depending on the reporting order.
]]]

Perform multi-file operations in the sorted order of the file names.

This makes the output of operations like "svn diff" and "svn status"
predictable, repeatable, easy to read, etc., for the user's benefit, and
also provides a better foundation for machine processing of Subversion
output and even internal operations.

This patch addresses the following operations (+=yes, .=n/a, -=no):
+ add
. blame
. cat
+ checkout
. cleanup
+ commit
. copy
+ delete [1]
+ diff (locally) [1]
- diff (contacting repository) [1]
+ export
. help
+ import
+ info
+ list
. log
+ merge [1]
. mkdir
+ move
. propedit
+ propget,proplist
+ propset,propdel
+ resolved
+ revert
+ status (locally)
- status (contacting repository)
+ switch,update [1]

[1] Sorted except that directories can be reported after their children.

File and directory names are sorted in the numerical order of their Unicode
representations. This order is fixed (because it is implemented on the server
as well as the client) and is not affected by locale or case-insensitive file
systems.

This patch sorts sequences of path names that are generated within Subversion.
It does not, for instance, sort a list of targets specified by the user.

Tweak a regression test so that it does not depend on "svn status" reporting
unversioned files before ".".

* subversion/clients/cmdline/propget-cmd.c (svn_cl__propget)
  Sort the properties by filename before printing them.

* subversion/libsvn_client/commit.c (import_dir)
  Call svn_io_get_dirents instead of doing the hard work manually.
  Sort the dir entries before processing and recursing.

* subversion/libsvn_client/export.c (copy_versioned_files)
  Sort the dir entries before processing and recursing.

* subversion/libsvn_client/prop_commands.c (remote_propget, remote_proplist)
  Sort the dir entries before processing and recursing.

* subversion/libsvn_repos/delta.c (delta_dirs)
  Bring together all additions, changes and deletions rather than leaving
    deletions until afterwards.
  Sort the FS dir entries before processing and recursing, so as to
    emit the editing operations in sorted order.

* subversion/libsvn_wc/adm_crawler.c (report_revisions)
* subversion/libsvn_wc/adm_ops.c (svn_wc_revert, mark_tree)
* subversion/libsvn_wc/diff.c (directory_elements_diff)
* subversion/libsvn_wc/entries.c (walker_helper)
  Sort the WC entries before processing and recursing.

* subversion/libsvn_wc/status.c
  (get_dir_status): Bring together all versioned and unversioned items from
    a single directory, instead of handling all the unversioned items first.
    Sort them before recursing and reporting.
  (handle_statii): Sort the entries before recursing and reporting.
  (close_directory): Report "." before its children, not after them.

* subversion/tests/clients/cmdline/stat_tests.py
  (status_for_unignored_file): Commit the property change so that no status is
    reported for "this dir", to avoid depending on the reporting order.

Index: subversion/clients/cmdline/propget-cmd.c
===================================================================
--- subversion/clients/cmdline/propget-cmd.c (revision 9915)
+++ subversion/clients/cmdline/propget-cmd.c (working copy)
@@ -32,6 +32,7 @@
 #include "svn_utf.h"
 #include "svn_subst.h"
 #include "svn_path.h"
+#include "svn_sorts.h"
 #include "cl.h"
 
 
@@ -140,7 +141,8 @@
         {
           const char *target = ((const char **) (targets->elts))[i];
           apr_hash_t *props;
- apr_hash_index_t *hi;
+ apr_array_header_t *sorted_props;
+ int j;
           svn_boolean_t print_filenames = FALSE;
           svn_boolean_t is_url = svn_path_is_url (target);
 
@@ -158,17 +160,19 @@
                               || apr_hash_count (props) > 1)
                              && (! opt_state->strict));
             
- for (hi = apr_hash_first (pool, props); hi; hi = apr_hash_next (hi))
+ /* Sort the entries so that the output is stable. */
+ sorted_props = svn_sort__hash (props,
+ svn_sort_compare_items_as_paths,
+ pool);
+
+ for (j = 0; j < sorted_props->nelts; j++)
             {
- const void *key;
- void *val;
- const char *filename;
- svn_string_t *propval;
-
- apr_hash_this (hi, &key, NULL, &val);
- filename = key;
- propval = val;
-
+ /* Get the next entry */
+ const svn_sort__item_t *item = &APR_ARRAY_IDX (sorted_props, j,
+ svn_sort__item_t);
+ const char *filename = item->key;
+ svn_string_t *propval = item->value;
+
               /* If this is a special Subversion property, it is stored as
                  UTF8, so convert to the native format. */
               if (svn_prop_needs_translation (pname_utf8))
Index: subversion/libsvn_client/commit.c
===================================================================
--- subversion/libsvn_client/commit.c (revision 9915)
+++ subversion/libsvn_client/commit.c (working copy)
@@ -40,6 +40,7 @@
 #include "svn_io.h"
 #include "svn_md5.h"
 #include "svn_time.h"
+#include "svn_sorts.h"
 
 #include "client.h"
 
@@ -247,27 +248,29 @@
 {
   apr_pool_t *subpool = svn_pool_create (pool); /* iteration pool */
   apr_hash_t *dirents;
- apr_hash_index_t *hi;
+ apr_array_header_t *sorted_dirents;
+ int i;
   apr_array_header_t *ignores;
 
   SVN_ERR (svn_wc_get_default_ignores (&ignores, ctx->config, pool));
 
   SVN_ERR (svn_io_get_dirents (&dirents, path, pool));
 
- for (hi = apr_hash_first (pool, dirents); hi; hi = apr_hash_next (hi))
+ /* Sort the entries so that the output is stable. */
+ sorted_dirents = svn_sort__hash (dirents, svn_sort_compare_items_as_paths,
+ pool);
+
+ for (i = 0; i < sorted_dirents->nelts; i++)
     {
       const char *this_path, *this_edit_path, *abs_path;
- const svn_node_kind_t *filetype;
- const char *filename;
- const void *key;
- void *val;
-
- svn_pool_clear (subpool);
 
- apr_hash_this (hi, &key, NULL, &val);
+ /* Get the next entry */
+ const svn_sort__item_t *item = &APR_ARRAY_IDX (sorted_dirents, i,
+ svn_sort__item_t);
+ const char *filename = item->key;
+ const svn_node_kind_t *filetype = item->value;
 
- filename = key;
- filetype = val;
+ svn_pool_clear (subpool);
 
       if (ctx->cancel_func)
         SVN_ERR (ctx->cancel_func (ctx->cancel_baton));
Index: subversion/libsvn_client/export.c
===================================================================
--- subversion/libsvn_client/export.c (revision 9915)
+++ subversion/libsvn_client/export.c (working copy)
@@ -34,6 +34,7 @@
 #include "svn_subst.h"
 #include "svn_time.h"
 #include "svn_md5.h"
+#include "svn_sorts.h"
 #include "client.h"
 
 #include "svn_private_config.h"
@@ -103,7 +104,8 @@
   svn_error_t *err;
   apr_pool_t *iterpool;
   apr_hash_t *dirents;
- apr_hash_index_t *hi;
+ apr_array_header_t *sorted_dirents;
+ int i;
   apr_finfo_t finfo;
 
   SVN_ERR (svn_wc_adm_probe_open2 (&adm_access, NULL, from, FALSE,
@@ -142,21 +144,22 @@
 
   SVN_ERR (svn_io_get_dirents (&dirents, from, pool));
 
+ /* Sort the entries so that the output is stable. */
+ sorted_dirents = svn_sort__hash (dirents,
+ svn_sort_compare_items_as_paths, pool);
+
   iterpool = svn_pool_create (pool);
- for (hi = apr_hash_first (pool, dirents); hi; hi = apr_hash_next (hi))
+
+ for (i = 0; i < sorted_dirents->nelts; i++)
     {
- const svn_node_kind_t *type;
- const char *item;
- const void *key;
- void *val;
+ /* Get the next entry */
+ const svn_sort__item_t *item = &APR_ARRAY_IDX (sorted_dirents, i,
+ svn_sort__item_t);
+ const char *filename = item->key;
+ const svn_node_kind_t *type = item->value;
       
       svn_pool_clear (iterpool);
 
- apr_hash_this (hi, &key, NULL, &val);
-
- item = key;
- type = val;
-
       if (ctx->cancel_func)
         SVN_ERR (ctx->cancel_func (ctx->cancel_baton));
       
@@ -165,14 +168,14 @@
       
       if (*type == svn_node_dir)
         {
- if (strcmp (item, SVN_WC_ADM_DIR_NAME) == 0)
+ if (strcmp (filename, SVN_WC_ADM_DIR_NAME) == 0)
             {
               ; /* skip this, it's an administrative directory. */
             }
           else
             {
- const char *new_from = svn_path_join (from, key, iterpool);
- const char *new_to = svn_path_join (to, key, iterpool);
+ const char *new_from = svn_path_join (from, filename, iterpool);
+ const char *new_to = svn_path_join (to, filename, iterpool);
               
               SVN_ERR (copy_versioned_files (new_from, new_to, revision,
                                              force, native_eol, ctx,
@@ -181,8 +184,8 @@
         }
       else if (*type == svn_node_file)
         {
- const char *copy_from = svn_path_join (from, item, iterpool);
- const char *copy_to = svn_path_join (to, item, iterpool);
+ const char *copy_from = svn_path_join (from, filename, iterpool);
+ const char *copy_to = svn_path_join (to, filename, iterpool);
           svn_subst_keywords_t kw = { 0 };
           svn_subst_eol_style_t style;
           apr_hash_t *props;
Index: subversion/libsvn_client/prop_commands.c
===================================================================
--- subversion/libsvn_client/prop_commands.c (revision 9915)
+++ subversion/libsvn_client/prop_commands.c (working copy)
@@ -30,6 +30,7 @@
 #include "svn_client.h"
 #include "client.h"
 #include "svn_path.h"
+#include "svn_sorts.h"
 
 #include "svn_private_config.h"
 
@@ -449,21 +450,22 @@
   
   if (recurse && (kind == svn_node_dir) && (apr_hash_count (dirents) > 0))
     {
- apr_hash_index_t *hi;
+ apr_array_header_t *sorted_dirents;
+ int i;
 
- for (hi = apr_hash_first (pool, dirents);
- hi;
- hi = apr_hash_next (hi))
+ /* Sort the entries so that the output is stable. */
+ sorted_dirents = svn_sort__hash (dirents,
+ svn_sort_compare_items_as_paths, pool);
+
+ for (i = 0; i < sorted_dirents->nelts; i++)
         {
- const void *key;
- void *val;
- const char *this_name;
- svn_dirent_t *this_ent;
- const char *new_target_relative;
+ /* Get the next entry */
+ const svn_sort__item_t *item = &APR_ARRAY_IDX (sorted_dirents, i,
+ svn_sort__item_t);
+ const char *this_name = item->key;
+ const svn_dirent_t *this_ent = item->value;
 
- apr_hash_this (hi, &key, NULL, &val);
- this_name = key;
- this_ent = val;
+ const char *new_target_relative;
 
           new_target_relative = svn_path_join (target_relative,
                                                this_name, pool);
@@ -708,19 +710,22 @@
   
   if (recurse && (kind == svn_node_dir) && (apr_hash_count (dirents) > 0))
     {
- for (hi = apr_hash_first (pool, dirents);
- hi;
- hi = apr_hash_next (hi))
+ apr_array_header_t *sorted_dirents;
+ int i;
+
+ /* Sort the entries so that the output is stable. */
+ sorted_dirents = svn_sort__hash (dirents,
+ svn_sort_compare_items_as_paths, pool);
+
+ for (i = 0; i < sorted_dirents->nelts; i++)
         {
- const void *key;
- void *val;
- const char *this_name;
- svn_dirent_t *this_ent;
- const char *new_target_relative;
+ /* Get the next entry */
+ const svn_sort__item_t *item = &APR_ARRAY_IDX (sorted_dirents, i,
+ svn_sort__item_t);
+ const char *this_name = item->key;
+ const svn_dirent_t *this_ent = item->value;
 
- apr_hash_this (hi, &key, NULL, &val);
- this_name = key;
- this_ent = val;
+ const char *new_target_relative;
 
           new_target_relative = svn_path_join (target_relative,
                                                this_name, pool);
Index: subversion/libsvn_repos/delta.c
===================================================================
--- subversion/libsvn_repos/delta.c (revision 9915)
+++ subversion/libsvn_repos/delta.c (working copy)
@@ -28,6 +28,7 @@
 #include "svn_path.h"
 #include "svn_repos.h"
 #include "svn_pools.h"
+#include "svn_sorts.h"
 
 
 
@@ -919,6 +920,9 @@
   apr_hash_t *s_entries = 0, *t_entries = 0;
   apr_hash_index_t *hi;
   apr_pool_t *subpool;
+ svn_fs_dirent_t deleted_thing; /* dummy: address used, content ignored */
+ apr_array_header_t *sorted_t_entries;
+ int i;
 
   assert (target_path);
 
@@ -936,83 +940,112 @@
   /* Make a subpool for local allocations. */
   subpool = svn_pool_create (pool);
 
- /* Loop over the hash of entries in the target, searching for its
- partner in the source. If we find the matching partner entry,
- use editor calls to replace the one in target with a new version
- if necessary, then remove that entry from the source entries
- hash. If we can't find a related node in the source, we use
- editor calls to add the entry as a new item in the target.
- Having handled all the entries that exist in target, any entries
- still remaining the source entries hash represent entries that no
- longer exist in target. Use editor calls to delete those entries
- from the target tree. */
- for (hi = apr_hash_first (pool, t_entries); hi; hi = apr_hash_next (hi))
- {
- const svn_fs_dirent_t *s_entry, *t_entry;
- const void *key;
- void *val;
- apr_ssize_t klen;
- const char *t_fullpath;
- const char *e_fullpath;
- const char *s_fullpath;
- svn_node_kind_t tgt_kind;
-
- /* KEY is the entry name in target, VAL the dirent */
- apr_hash_this (hi, &key, &klen, &val);
- t_entry = val;
- tgt_kind = t_entry->kind;
- t_fullpath = svn_path_join (target_path, t_entry->name, subpool);
- e_fullpath = svn_path_join (edit_path, t_entry->name, subpool);
-
- /* Can we find something with the same name in the source
- entries hash? */
- if (s_entries && ((s_entry = apr_hash_get (s_entries, key, klen)) != 0))
+ /* Add a dummy target entry for each source entry that is to be deleted
+ because it has no corresponding real target. */
+ if (s_entries)
+ {
+ for (hi = apr_hash_first (subpool, s_entries);
+ hi;
+ hi = apr_hash_next (hi))
+ {
+ const void *key;
+ apr_ssize_t klen;
+ apr_hash_this (hi, &key, &klen, NULL);
+
+ /* If this source item is not in the target, add a marker */
+ if (apr_hash_get (t_entries, key, klen) == NULL)
+ apr_hash_set (t_entries, key, klen, &deleted_thing);
+ }
+ }
+
+ /* Sort the entries so that the output is stable. */
+ sorted_t_entries = svn_sort__hash (t_entries,
+ svn_sort_compare_items_as_paths, pool);
+
+ /* Process each entry in the combined list of real and dummy target entries.
+ If the entry is a "deleted thing" marker, use editor calls to delete it
+ from the target tree.
+ If the entry has a matching source entry, use editor calls to replace
+ the one in target with a new version if necessary.
+ If the entry has no matching source entry, use editor calls to add the
+ entry as a new item in the target. */
+ for (i = 0; i < sorted_t_entries->nelts; i++)
+ {
+ /* Get the next entry in the target. */
+ const svn_sort__item_t *item = &APR_ARRAY_IDX (sorted_t_entries, i,
+ svn_sort__item_t);
+ const void *key = item->key;
+ apr_ssize_t klen = item->klen;
+ const svn_fs_dirent_t *t_entry = item->value;
+
+ const svn_fs_dirent_t *s_entry
+ = (s_entries) ? apr_hash_get (s_entries, key, klen) : NULL;
+
+ /* Delete or change or add the thing. */
+ if (t_entry == &deleted_thing) /* This entry is to be deleted. */
+ {
+ const char *e_fullpath
+ = svn_path_join (edit_path, s_entry->name, subpool);
+
+ /* Do we actually want to delete the dir if we're non-recursive? */
+ if (c->recurse || (s_entry->kind != svn_node_dir))
+ SVN_ERR (delete (c, dir_baton, e_fullpath, subpool));
+ }
+ else
         {
- int distance;
- svn_node_kind_t src_kind;
+ const char *t_fullpath;
+ const char *e_fullpath;
+ svn_node_kind_t tgt_kind = t_entry->kind;
 
- s_fullpath = svn_path_join (source_path, t_entry->name, subpool);
- src_kind = s_entry->kind;
+ t_fullpath = svn_path_join (target_path, t_entry->name, subpool);
+ e_fullpath = svn_path_join (edit_path, t_entry->name, subpool);
 
- if (c->recurse || (src_kind != svn_node_dir))
+ /* Can we find something with the same name in the source
+ entries hash? */
+ if (s_entry) /* This entry is to be changed. */
             {
- /* Use svn_fs_compare_ids() to compare our current
- source and target ids.
+ svn_node_kind_t src_kind = s_entry->kind;
+ const char *s_fullpath;
+ s_fullpath = svn_path_join (source_path, t_entry->name, subpool);
 
- 0: means they are the same id, and this is a noop.
- -1: means they are unrelated, so we have to delete the
- old one and add the new one.
- 1: means the nodes are related through ancestry, so go
- ahead and do the replace directly. */
- distance = svn_fs_compare_ids (s_entry->id, t_entry->id);
- if (distance == 0)
+ if (c->recurse || (src_kind != svn_node_dir))
                 {
- /* no-op */
+ /* Use svn_fs_compare_ids() to compare our current
+ source and target ids.
+
+ 0: means they are the same id, and this is a noop.
+ -1: means they are unrelated, so we have to delete the
+ old one and add the new one.
+ 1: means the nodes are related through ancestry, so go
+ ahead and do the replace directly. */
+ int distance = svn_fs_compare_ids (s_entry->id, t_entry->id);
+ if (distance == 0)
+ {
+ /* no-op */
+ }
+ else if ((src_kind != tgt_kind)
+ || ((distance == -1) && (! c->ignore_ancestry)))
+ {
+ SVN_ERR (delete (c, dir_baton, e_fullpath, subpool));
+ SVN_ERR (add_file_or_dir (c, dir_baton, t_fullpath,
+ e_fullpath, tgt_kind,
+ subpool));
+ }
+ else
+ {
+ SVN_ERR (replace_file_or_dir (c, dir_baton, s_fullpath,
+ t_fullpath, e_fullpath,
+ tgt_kind, subpool));
+ }
                 }
- else if ((src_kind != tgt_kind)
- || ((distance == -1) && (! c->ignore_ancestry)))
+ }
+ else
+ {
+ if (c->recurse || (tgt_kind != svn_node_dir))
                 {
- SVN_ERR (delete (c, dir_baton, e_fullpath, subpool));
                   SVN_ERR (add_file_or_dir (c, dir_baton, t_fullpath,
                                             e_fullpath, tgt_kind, subpool));
                 }
- else
- {
- SVN_ERR (replace_file_or_dir (c, dir_baton, s_fullpath,
- t_fullpath, e_fullpath,
- tgt_kind, subpool));
- }
- }
-
- /* Remove the entry from the source_hash. */
- apr_hash_set (s_entries, key, APR_HASH_KEY_STRING, NULL);
- }
- else
- {
- if (c->recurse || (tgt_kind != svn_node_dir))
- {
- SVN_ERR (add_file_or_dir (c, dir_baton, t_fullpath,
- e_fullpath, tgt_kind, subpool));
             }
         }
 
@@ -1020,34 +1053,6 @@
       svn_pool_clear (subpool);
     }
 
- /* All that is left in the source entries hash are things that need
- to be deleted. Delete them. */
- if (s_entries)
- {
- for (hi = apr_hash_first (pool, s_entries); hi; hi = apr_hash_next (hi))
- {
- const svn_fs_dirent_t *s_entry;
- const void *key;
- void *val;
- apr_ssize_t klen;
- const char *e_fullpath;
- svn_node_kind_t src_kind;
-
- /* KEY is the entry name in source, VAL the dirent */
- apr_hash_this (hi, &key, &klen, &val);
- s_entry = val;
- src_kind = s_entry->kind;
- e_fullpath = svn_path_join (edit_path, s_entry->name, subpool);
-
- /* Do we actually want to delete the dir if we're non-recursive? */
- if (c->recurse || (src_kind != svn_node_dir))
- SVN_ERR (delete (c, dir_baton, e_fullpath, subpool));
-
- /* Clear out our subpool for the next iteration... */
- svn_pool_clear (subpool);
- }
- }
-
   /* Destroy local allocation subpool. */
   svn_pool_destroy (subpool);
 
Index: subversion/libsvn_wc/adm_crawler.c
===================================================================
--- subversion/libsvn_wc/adm_crawler.c (revision 9915)
+++ subversion/libsvn_wc/adm_crawler.c (working copy)
@@ -166,7 +166,8 @@
                   apr_pool_t *pool)
 {
   apr_hash_t *entries, *dirents;
- apr_hash_index_t *hi;
+ apr_array_header_t *sorted_entries;
+ int i;
   apr_pool_t *subpool = svn_pool_create (pool), *iterpool;
   const svn_wc_entry_t *dot_entry;
   const char *this_url, *this_path, *full_path, *this_full_path;
@@ -205,15 +206,22 @@
         }
     }
 
+ /* Sort the entries so that the output is stable. */
+ sorted_entries = svn_sort__hash (entries, svn_sort_compare_items_as_paths,
+ subpool);
+
   /* Looping over current directory's SVN entries: */
   iterpool = svn_pool_create (subpool);
 
- for (hi = apr_hash_first (subpool, entries); hi; hi = apr_hash_next (hi))
+ for (i = 0; i < sorted_entries->nelts; i++)
     {
- const void *key;
- apr_ssize_t klen;
- void *val;
- const svn_wc_entry_t *current_entry;
+ /* Get the next entry */
+ const svn_sort__item_t *item = &APR_ARRAY_IDX (sorted_entries, i,
+ svn_sort__item_t);
+ const void *key = item->key;
+ apr_ssize_t klen = item->klen;
+ const svn_wc_entry_t *current_entry = item->value;
+
       svn_node_kind_t *dirent_kind;
       svn_boolean_t missing = FALSE;
 
@@ -221,10 +229,6 @@
          of 'continue' jump statements. */
       svn_pool_clear (iterpool);
 
- /* Get the next entry */
- apr_hash_this (hi, &key, &klen, &val);
- current_entry = val;
-
       /* Compute the name of the entry. Skip THIS_DIR altogether. */
       if (! strcmp (key, SVN_WC_ENTRY_THIS_DIR))
         continue;
Index: subversion/libsvn_wc/adm_ops.c
===================================================================
--- subversion/libsvn_wc/adm_ops.c (revision 9915)
+++ subversion/libsvn_wc/adm_ops.c (working copy)
@@ -41,6 +41,7 @@
 #include "svn_io.h"
 #include "svn_md5.h"
 #include "svn_xml.h"
+#include "svn_sorts.h"
 
 #include "wc.h"
 #include "log.h"
@@ -477,30 +478,33 @@
 {
   apr_pool_t *subpool = svn_pool_create (pool);
   apr_hash_t *entries;
- apr_hash_index_t *hi;
+ apr_array_header_t *sorted_entries;
+ int i;
   const svn_wc_entry_t *entry;
 
   /* Read the entries file for this directory. */
   SVN_ERR (svn_wc_entries_read (&entries, adm_access, FALSE, pool));
 
+ /* Sort the entries so that the output is stable. */
+ sorted_entries = svn_sort__hash (entries, svn_sort_compare_items_as_paths,
+ pool);
+
   /* Mark each entry in the entries file. */
- for (hi = apr_hash_first (pool, entries); hi; hi = apr_hash_next (hi))
+ for (i = 0; i < sorted_entries->nelts; i++)
     {
       const char *fullpath;
- const void *key;
- void *val;
- const char *base_name;
       svn_wc_entry_t *dup_entry;
 
       /* Get the next entry */
- apr_hash_this (hi, &key, NULL, &val);
- entry = val;
+ const svn_sort__item_t *item = &APR_ARRAY_IDX (sorted_entries, i,
+ svn_sort__item_t);
+ const char *base_name = item->key;
+ entry = item->value;
 
       /* Skip "this dir". */
- if (! strcmp ((const char *)key, SVN_WC_ENTRY_THIS_DIR))
+ if (! strcmp (base_name, SVN_WC_ENTRY_THIS_DIR))
         continue;
           
- base_name = key;
       fullpath = svn_path_join (svn_wc_adm_access_path (adm_access), base_name,
                                 subpool);
 
@@ -1579,19 +1583,24 @@
   if (recursive && (entry->kind == svn_node_dir))
     {
       apr_hash_t *entries;
- apr_hash_index_t *hi;
+ apr_array_header_t *sorted_entries;
+ int i;
       apr_pool_t *subpool = svn_pool_create (pool);
 
       SVN_ERR (svn_wc_entries_read (&entries, dir_access, FALSE, pool));
- for (hi = apr_hash_first (pool, entries); hi; hi = apr_hash_next (hi))
- {
- const void *key;
- const char *keystring;
- const char *full_entry_path;
 
+ /* Sort the entries so that the output is stable. */
+ sorted_entries = svn_sort__hash (entries,
+ svn_sort_compare_items_as_paths, pool);
+
+ for (i = 0; i < sorted_entries->nelts; i++)
+ {
           /* Get the next entry */
- apr_hash_this (hi, &key, NULL, NULL);
- keystring = key;
+ const svn_sort__item_t *item = &APR_ARRAY_IDX (sorted_entries, i,
+ svn_sort__item_t);
+ const void *keystring = item->key;
+
+ const char *full_entry_path;
 
           /* Skip "this dir" */
           if (! strcmp (keystring, SVN_WC_ENTRY_THIS_DIR))
Index: subversion/libsvn_wc/diff.c
===================================================================
--- subversion/libsvn_wc/diff.c (revision 9915)
+++ subversion/libsvn_wc/diff.c (working copy)
@@ -50,6 +50,7 @@
 
 #include "svn_pools.h"
 #include "svn_path.h"
+#include "svn_sorts.h"
 
 #include "wc.h"
 #include "props.h"
@@ -635,7 +636,8 @@
                          svn_boolean_t added)
 {
   apr_hash_t *entries;
- apr_hash_index_t *hi;
+ apr_array_header_t *sorted_entries;
+ int i;
   svn_boolean_t in_anchor_not_target;
   apr_pool_t *subpool;
   svn_wc_adm_access_t *adm_access;
@@ -686,23 +688,25 @@
 
   SVN_ERR (svn_wc_entries_read (&entries, adm_access, FALSE, dir_baton->pool));
 
+ /* Sort the entries so that the output is stable. */
+ sorted_entries = svn_sort__hash (entries, svn_sort_compare_items_as_paths,
+ dir_baton->pool);
+
   subpool = svn_pool_create (dir_baton->pool);
 
- for (hi = apr_hash_first (dir_baton->pool, entries); hi;
- hi = apr_hash_next (hi))
+ for (i = 0; i < sorted_entries->nelts; i++)
     {
- const void *key;
- void *val;
- const svn_wc_entry_t *entry;
+ /* Get the next entry */
+ const svn_sort__item_t *item = &APR_ARRAY_IDX (sorted_entries, i,
+ svn_sort__item_t);
+ const char *name = item->key;
+ const svn_wc_entry_t *entry = item->value;
+
       struct dir_baton *subdir_baton;
- const char *name, *path;
+ const char *path;
 
- apr_hash_this (hi, &key, NULL, &val);
- name = key;
- entry = val;
-
       /* Skip entry for the directory itself. */
- if (strcmp (key, SVN_WC_ENTRY_THIS_DIR) == 0)
+ if (strcmp (name, SVN_WC_ENTRY_THIS_DIR) == 0)
         continue;
 
       /* In the anchor directory, if the anchor is not the target then all
Index: subversion/libsvn_wc/entries.c
===================================================================
--- subversion/libsvn_wc/entries.c (revision 9915)
+++ subversion/libsvn_wc/entries.c (working copy)
@@ -28,6 +28,7 @@
 #include "svn_time.h"
 #include "svn_pools.h"
 #include "svn_path.h"
+#include "svn_sorts.h"
 
 #include "wc.h"
 #include "adm_files.h"
@@ -1722,7 +1738,8 @@
 {
   apr_pool_t *subpool = svn_pool_create (pool);
   apr_hash_t *entries;
- apr_hash_index_t *hi;
+ apr_array_header_t *sorted_entries;
+ int i;
   svn_wc_entry_t *dot_entry;
 
   SVN_ERR (svn_wc_entries_read (&entries, adm_access, show_hidden, pool));
@@ -1737,17 +1754,20 @@
 
   SVN_ERR (walk_callbacks->found_entry (dirpath, dot_entry, walk_baton, pool));
 
+ /* Sort the entries so that the output is stable. */
+ sorted_entries = svn_sort__hash (entries, svn_sort_compare_items_as_paths,
+ pool);
+
   /* Loop over each of the other entries. */
- for (hi = apr_hash_first (pool, entries); hi; hi = apr_hash_next (hi))
+ for (i = 0; i < sorted_entries->nelts; i++)
     {
- const void *key;
- apr_ssize_t klen;
- void *val;
- const svn_wc_entry_t *current_entry;
- const char *entrypath;
+ /* Get the next entry */
+ const svn_sort__item_t *item = &APR_ARRAY_IDX (sorted_entries, i,
+ svn_sort__item_t);
+ const char *key = item->key;
+ const svn_wc_entry_t *current_entry = item->value;
 
- apr_hash_this (hi, &key, &klen, &val);
- current_entry = val;
+ const char *entrypath;
 
       if (strcmp (current_entry->name, SVN_WC_ENTRY_THIS_DIR) == 0)
         continue;
Index: subversion/libsvn_wc/status.c
===================================================================
--- subversion/libsvn_wc/status.c (revision 9915)
+++ subversion/libsvn_wc/status.c (working copy)
@@ -25,6 +25,7 @@
 #include <apr_hash.h>
 #include <apr_time.h>
 #include <apr_fnmatch.h>
+#include "svn_sorts.h"
 #include "svn_pools.h"
 #include "svn_types.h"
 #include "svn_delta.h"
@@ -688,6 +689,9 @@
   apr_hash_t *dirents;
   apr_array_header_t *patterns = NULL;
   apr_pool_t *iterpool, *subpool = svn_pool_create (pool);
+ svn_wc_entry_t unversioned_thing; /* dummy: address used, content ignored */
+ apr_array_header_t *sorted_entries;
+ int i;
 
   /* See if someone wants to cancel this operation. */
   if (cancel_func)
@@ -784,34 +788,20 @@
   /** If we get here, ENTRY is NULL and we are handling all the
       directory entries. */
 
- /* Make our iteration pool. */
- iterpool = svn_pool_create (subpool);
-
   /* Add empty status structures for each of the unversioned things. */
   for (hi = apr_hash_first (subpool, dirents); hi; hi = apr_hash_next (hi))
     {
       const void *key;
       apr_ssize_t klen;
- void *val;
- svn_node_kind_t *path_kind;
-
- apr_hash_this (hi, &key, &klen, &val);
+ apr_hash_this (hi, &key, &klen, NULL);
         
- /* Skip versioned things, and skip the administrative
- directory. */
- if ((apr_hash_get (entries, key, klen))
+ /* Skip versioned things, and skip the administrative directory. */
+ if ((apr_hash_get (entries, key, klen))
           || (strcmp (key, SVN_WC_ADM_DIR_NAME) == 0))
         continue;
 
- /* Clear the iteration subpool. */
- svn_pool_clear (iterpool);
-
- /* Make an unversioned status item for KEY, and put it into our
- return hash. */
- path_kind = val;
- SVN_ERR (send_unversioned_item (key, *path_kind, adm_access,
- patterns, eb->externals, no_ignore,
- status_func, status_baton, iterpool));
+ /* Add an "unversioned item" marker to the entries hash. */
+ apr_hash_set (entries, key, klen, &unversioned_thing);
     }
 
   /* Handle "this-dir" first. */
@@ -821,29 +811,50 @@
                                     get_all, FALSE, status_func,
                                     status_baton, subpool));
 
- /* Loop over entries hash */
- for (hi = apr_hash_first (pool, entries); hi; hi = apr_hash_next (hi))
- {
- const void *key;
- void *val;
+ /* Sort the entries so that the output is stable. */
+ sorted_entries = svn_sort__hash (entries, svn_sort_compare_items_as_paths,
+ subpool);
+
+ /* Make our iteration pool. */
+ iterpool = svn_pool_create (subpool);
 
- /* Get the next dirent */
- apr_hash_this (hi, &key, NULL, &val);
+ /* Report the status of each entry, recursively. */
+ for (i = 0; i < sorted_entries->nelts; i++)
+ {
+ /* Get the next entry */
+ const svn_sort__item_t *item = &APR_ARRAY_IDX (sorted_entries, i,
+ svn_sort__item_t);
+ const char *this_name = item->key;
+ const svn_wc_entry_t *this_entry = item->value;
 
       /* ### todo: What if the subdir is from another repository? */
           
       /* Skip "this-dir". */
- if (strcmp (key, SVN_WC_ENTRY_THIS_DIR) == 0)
+ if (strcmp (this_name, SVN_WC_ENTRY_THIS_DIR) == 0)
         continue;
 
       /* Clear the iteration subpool. */
       svn_pool_clear (iterpool);
 
- /* Handle this directory entry (possibly recursing). */
- SVN_ERR (handle_dir_entry (eb, adm_access, key, dir_entry, val, ignores,
- descend, get_all, no_ignore,
- status_func, status_baton, cancel_func,
- cancel_baton, iterpool));
+ if (this_entry == &unversioned_thing)
+ {
+ /* Report the status of this unversioned thing. */
+ svn_node_kind_t *path_kind = apr_hash_get (dirents, this_name,
+ item->klen);
+ SVN_ERR (send_unversioned_item (this_name, *path_kind, adm_access,
+ patterns, eb->externals, no_ignore,
+ status_func, status_baton,
+ iterpool));
+ }
+ else
+ {
+ /* Handle this directory entry (possibly recursing). */
+ SVN_ERR (handle_dir_entry (eb, adm_access, this_name, dir_entry,
+ this_entry, ignores,
+ descend, get_all, no_ignore,
+ status_func, status_baton, cancel_func,
+ cancel_baton, iterpool));
+ }
     }
   
   /* Destroy our subpools. */
@@ -1092,11 +1103,12 @@
                apr_pool_t *pool)
 {
   apr_array_header_t *ignores = eb->ignores;
- apr_hash_index_t *hi;
   apr_pool_t *subpool = svn_pool_create (pool);
   svn_wc_status_func_t status_func = eb->status_func;
   void *status_baton = eb->status_baton;
   struct status_baton sb;
+ apr_array_header_t *sorted_statii;
+ int i;
 
   if (dir_was_deleted)
     {
@@ -1106,15 +1118,18 @@
       status_baton = &sb;
     }
 
- /* Loop over all the statuses still in our hash, handling each one. */
- for (hi = apr_hash_first (pool, statii); hi; hi = apr_hash_next (hi))
- {
- const void *key;
- void *val;
- svn_wc_status_t *status;
-
- apr_hash_this (hi, &key, NULL, &val);
- status = val;
+ /* Sort the entries so that the output is stable. */
+ sorted_statii = svn_sort__hash (statii, svn_sort_compare_items_as_paths,
+ pool);
+
+ /* Handle each status that was still in our hash. */
+ for (i = 0; i < sorted_statii->nelts; i++)
+ {
+ /* Get the next entry */
+ const svn_sort__item_t *item = &APR_ARRAY_IDX (sorted_statii, i,
+ svn_sort__item_t);
+ const char *key = item->key;
+ svn_wc_status_t *status = item->value;
 
       /* Clear the subpool. */
       svn_pool_clear (subpool);
@@ -1325,11 +1340,11 @@
           || (dir_status->repos_text_status == svn_wc_status_replaced))
         was_deleted = TRUE;
 
- /* Now do the status reporting. */
- SVN_ERR (handle_statii (eb, dir_status ? dir_status->entry : NULL,
- db->path, db->statii, was_deleted, TRUE, pool));
+ /* Now do the status reporting on ourself and all our children. */
       if (is_sendable_status (dir_status, eb))
         (eb->status_func) (eb->status_baton, db->path, dir_status);
+ SVN_ERR (handle_statii (eb, dir_status ? dir_status->entry : NULL,
+ db->path, db->statii, was_deleted, TRUE, pool));
       apr_hash_set (pb->statii, db->path, APR_HASH_KEY_STRING, NULL);
     }
   else if (! pb)
@@ -1363,13 +1378,13 @@
         }
       else
         {
- /* Otherwise, we report on all our children and ourself.
+ /* Otherwise, we report on ourself and all our children.
              Note that our directory couldn't have been deleted,
              because it is the root of the edit drive. */
- SVN_ERR (handle_statii (eb, eb->anchor_status->entry, db->path,
- db->statii, FALSE, eb->descend, pool));
           if (is_sendable_status (eb->anchor_status, eb))
             (eb->status_func) (eb->status_baton, db->path, eb->anchor_status);
+ SVN_ERR (handle_statii (eb, eb->anchor_status->entry, db->path,
+ db->statii, FALSE, eb->descend, pool));
           eb->anchor_status = NULL;
         }
     }
Index: subversion/tests/clients/cmdline/stat_tests.py
===================================================================
--- subversion/tests/clients/cmdline/stat_tests.py (revision 9915)
+++ subversion/tests/clients/cmdline/stat_tests.py (working copy)
@@ -305,15 +305,16 @@
 
   os.chdir(wc_dir)
   try:
+ svntest.main.run_svn(None, 'propset', 'svn:ignore', 'new*', '.')
+ svntest.main.run_svn(None, 'commit', '--message', '')
+
     svntest.main.file_append('newfile', 'this is a new file')
     os.makedirs('newdir')
- svntest.main.run_svn(None, 'propset', 'svn:ignore', 'new*', '.')
 
     # status on the directory with --no-ignore
     svntest.actions.run_and_verify_svn(None,
                                        ['I newdir\n',
- 'I newfile\n',
- ' M .\n'],
+ 'I newfile\n'],
                                        [],
                                        'status', '--no-ignore', '.')
 

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org
Received on Tue Jun 1 00:35:08 2004

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.