Whoo hoo!
(Haven't tried it yet, but looks good so far...).
-K
kevin@tigris.org writes:
>   User: kevin   
>   Date: 01/02/12 10:00:28
> 
>   Modified:    subversion/include svn_path.h
>                subversion/libsvn_subr Makefile.am
>                subversion/tests/libsvn_subr Makefile.am
>   Added:       subversion/libsvn_subr target.c
>                subversion/tests/libsvn_subr target-test.c target-test.sh
>   Log:
>   The implementation of svn_path_get_absolute, svn_path_split_if_file
>   and svn_path_condense_targets.
>       * new file libsvn_subr/target.c implementations
>   
>       * new file test/libsvn_subr/target-test.c a test program for above
>   
>       * new file test/libsvn_subr/target-test.sh invokation script for test
>   
>       * libsvn_subr/Makefile.am test/libsvn_subr/Makefile.am, addition of
>           those files and test suites.
>   
>       * include/svn_path.h prototypes for the above functions.
>   
>   Revision  Changes    Path
>   1.29      +34 -0     subversion/subversion/include/svn_path.h
>   
>   Index: svn_path.h
>   ===================================================================
>   RCS file: /cvs/subversion/subversion/include/svn_path.h,v
>   retrieving revision 1.28
>   retrieving revision 1.29
>   diff -u -r1.28 -r1.29
>   --- svn_path.h	2001/02/09 03:36:22	1.28
>   +++ svn_path.h	2001/02/12 18:00:27	1.29
>   @@ -22,7 +22,9 @@
>    
>    
>    #include <apr_pools.h>
>   +#include <apr_tables.h>
>    #include "svn_string.h"
>   +#include "svn_error.h"
>    
>    
>    /*** Notes:
>   @@ -120,6 +122,38 @@
>    svn_string_t *svn_path_get_longest_ancestor (const svn_string_t *path1,
>                                                 const svn_string_t *path2,
>                                                 apr_pool_t *pool);
>   +
>   +/* Convert RELATIVE path to an absolute path and return the results in
>   +   *PABSOLUTE. */
>   +svn_error_t *
>   +svn_path_get_absolute(svn_string_t **pabsolute,
>   +                      const svn_string_t *relative,
>   +                      apr_pool_t *pool);
>   +
>   +/* Return the path part of PATH in *PDIRECTORY, and the file part in *PFILE.
>   +   If PATH is a directory, it will be returned through PDIRECTORY, and *PFILE
>   +   will be the empty string (not NULL). */
>   +svn_error_t *
>   +svn_path_split_if_file(svn_string_t *path,
>   +                       svn_string_t **pdirectory, 
>   +                       svn_string_t **pfile,
>   +                       apr_pool_t *pool);
>   +
>   +/* Find the common part of all the paths in TARGETS.  The elements in 
>   +   TARGETS must be existing files or directories, in local path style.
>   +   PBASEDIR will be set to the absolute path that is common to all of the
>   +   items.  Additionally, if PCONDENSED_TARGETS is non-null, it will be 
>   +   set to a list of targets relative to *PBASEDIR, with no overlapping
>   +   targets. If there are no items in TARGETS, *PBASENAME and (if applicable)
>   +   *PCONDENSED_TARGETS will be NULL.  If an item in TARGETS is equal to
>   +   *PBASENAME, it will be returned as an empty string.
>   +
>   +    NOTE: There is no guarantee that *PBASENAME is within a working copy. */
>   +svn_error_t *
>   +svn_path_condense_targets(svn_string_t **pbasedir,
>   +                          apr_array_header_t **pcondensed_targets,
>   +                          const apr_array_header_t *targets,
>   +                          apr_pool_t *pool);
>    
>    #endif /* SVN_PATHS_H */
>    
>   
>   
>   
>   1.16      +1 -1      subversion/subversion/libsvn_subr/Makefile.am
>   
>   Index: Makefile.am
>   ===================================================================
>   RCS file: /cvs/subversion/subversion/libsvn_subr/Makefile.am,v
>   retrieving revision 1.15
>   retrieving revision 1.16
>   diff -u -r1.15 -r1.16
>   --- Makefile.am	2001/01/30 23:06:22	1.15
>   +++ Makefile.am	2001/02/12 18:00:27	1.16
>   @@ -10,7 +10,7 @@
>    ## Sources needed to build each library (names canonicalized)
>    libsvn_subr_la_SOURCES = svn_string.c svn_error.c path.c \
>                             hashdump.c xml.c base64.c quoprint.c io.c \
>   -                         keysort.c
>   +                         keysort.c target.c
>    
>    ## Build flags ---------
>    
>   
>   
>   
>   1.1                  subversion/subversion/libsvn_subr/target.c
>   
>   Index: target.c
>   ===================================================================
>   /*
>    * target.c:  functions which operate on a list of targets supplied to 
>    *              a subversion subcommand.
>    *
>    * ====================================================================
>    * Copyright (c) 2000 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
>    * are also available at http://subversion.tigris.org/license-1.html.
>    * If newer versions of this license are posted there, you may use a
>    * newer version instead, at your option.
>    * ====================================================================
>    */
>   
>   /* ==================================================================== */
>   
>   
>   
>   /*** Includes. ***/
>   
>   #include "svn_string.h"
>   #include "svn_error.h"
>   #include "svn_path.h"
>   #include "apr_file_info.h"
>   
>   
>   /*** Code. ***/
>   
>   svn_error_t *
>   svn_path_get_absolute(svn_string_t **pabsolute,
>                         const svn_string_t *relative,
>                         apr_pool_t *pool)
>   {
>   #ifdef WIN32
>     char buffer[_MAX_PATH];
>     if (_fullpath(buffer, relative->data, _MAX_PATH) != NULL)
>       {
>         *pabsolute = svn_string_create(buffer, pool);
>       }
>     else 
>       {
>         /* TODO: (kevin) Create better error messages, once I learn about
>            the errors returned from _fullpath() */
>         return svn_error_createf(APR_SUCCESS, SVN_ERR_BAD_FILENAME,
>                                  NULL, pool, "Could not determine absolute "
>                                  "path of %s", relative->data);
>       }
>   #else
>     char buffer[PATH_MAX];
>     if (realpath(relative->data, buffer) != NULL)
>       {
>         *pabsolute = svn_string_create(buffer, pool);
>       }
>     else 
>       {
>         switch (errno)
>           {
>           case EACCES:
>               return svn_error_createf(APR_SUCCESS, SVN_ERR_NOT_AUTHORIZED,
>                                        NULL, pool, "Could not get absolute path "
>                                        "for %s, because you lack permissions",
>                                        relative->data);
>               break;
>           case EINVAL: /* FALLTHRU */
>           case EIO: /* FALLTHRU */
>           case ELOOP: /* FALLTHRU */
>           case ENAMETOOLONG: /* FALLTHRU */
>           case ENOENT: /* FALLTHRU */
>           case ENOTDIR:
>               return svn_error_createf(APR_SUCCESS, SVN_ERR_BAD_FILENAME,
>                                        NULL, pool, "Could not get absolute path "
>                                        "for %s, because it is not a valid file "
>                                        "name.", relative->data);
>           default:
>               return svn_error_createf(APR_SUCCESS, SVN_ERR_BAD_FILENAME,
>                                        NULL, pool, "Could not determine if %s "
>                                        "is a file or directory.", relative->data);
>               break;
>           }
>       }
>   #endif
>     return SVN_NO_ERROR;
>   }
>   
>   svn_error_t *
>   svn_path_split_if_file(svn_string_t *path,
>                          svn_string_t **pdirectory,
>                          svn_string_t **pfile,
>                          apr_pool_t * pool)
>   {
>     apr_finfo_t finfo;
>     apr_status_t apr_err = apr_stat(&finfo, path->data, APR_FINFO_TYPE, pool);
>     if (apr_err != APR_SUCCESS)
>       {
>         return svn_error_createf(apr_err, SVN_ERR_BAD_FILENAME, NULL,
>                                 pool, "Couldn't determine if %s was a file or "
>                                 "directory.", path->data);
>       }
>     else
>       {
>         if (finfo.filetype == APR_DIR)
>           {
>             *pdirectory = path;
>             *pfile = svn_string_create("", pool);
>           }
>         else if (finfo.filetype == APR_REG)
>           {
>             svn_path_split(path, pdirectory, pfile, svn_path_local_style, pool);
>           }
>         else 
>           {
>             return svn_error_createf(APR_SUCCESS, SVN_ERR_BAD_FILENAME, NULL, pool,
>                                     "%s is neither a file nor a directory name.",
>                                     path->data);
>           }
>       }
>     return SVN_NO_ERROR;
>   }
>   
>   svn_error_t *
>   svn_path_condense_targets(svn_string_t **pbasedir,
>                             apr_array_header_t ** pcondensed_targets,
>                             const apr_array_header_t *targets,
>                             apr_pool_t *pool)
>   {
>     if (targets->nelts <=0)
>       {
>         *pbasedir = NULL;
>         if (pcondensed_targets)
>           *pcondensed_targets = NULL;
>       }
>     else
>       {
>         int i, j, num_condensed = targets->nelts;
>         svn_string_t *file;
>         svn_boolean_t *removed = apr_pcalloc(pool,
>                                              targets->nelts*sizeof(svn_boolean_t));
>   
>         /* Copy the targets array, but with absolute paths instead of relative.
>            Also, find the pbasedir argument by finding what is common in all
>            of the absolute paths. NOTE: This is not as efficient as it could be
>            The calculation of the the basedir could be done in the loop below, which would
>            save some calls to svn_path_get_longest_ancestor.  I decided to do it this way
>            because I thought it would simpler, since this way, we don't even do the loop
>            if we don't need to condense the targets. */
>         apr_array_header_t *abs_targets = apr_array_make(pool,
>                                                          targets->nelts,
>                                                          sizeof(svn_string_t*));
>         SVN_ERR(svn_path_get_absolute(pbasedir, ((svn_string_t **)targets->elts)[0], pool));
>         (*((svn_string_t**)apr_array_push(abs_targets))) = *pbasedir;
>         for(i = 1; i < targets->nelts; ++i)
>           {
>             svn_string_t *rel = ((svn_string_t **)targets->elts)[i];
>             svn_string_t *absolute;
>             SVN_ERR(svn_path_get_absolute(&absolute, rel, pool));
>             (*((svn_string_t **)apr_array_push(abs_targets))) = absolute;
>             *pbasedir = svn_path_get_longest_ancestor(*pbasedir, absolute, pool);
>           }
>   
>         /* If we need to find the targets, find the common part of each pair
>            of targets.  If common part is equal to one of the paths, the other
>            is a child of it, and can be removed. */
>         if (pcondensed_targets != NULL)
>           {
>             for (i = 0; i < abs_targets->nelts - 1; ++i)
>               {
>                 if (!removed[i])
>                   {
>                     for (j = i + 1; j < abs_targets->nelts; ++j)
>                       {
>                         if (!removed[i] && !removed[j])
>                           {
>                             svn_string_t *abs_targets_i = ((svn_string_t **)
>                                                            abs_targets->elts)[i];
>                             svn_string_t *abs_targets_j = ((svn_string_t **)
>                                                            abs_targets->elts)[j];
>                             svn_string_t *ancestor
>                               = svn_path_get_longest_ancestor(abs_targets_i,
>                                                               abs_targets_j,
>                                                               pool);
>                             if (ancestor != NULL)
>                               {
>                                 if (svn_string_compare(ancestor, abs_targets_i))
>                                   {
>                                     removed[j] = TRUE;
>                                     num_condensed--;
>                                   }
>                                 else if (svn_string_compare(ancestor,abs_targets_j))
>                                   {
>                                     removed[i] = TRUE;
>                                     num_condensed--;
>                                   }
>                               }
>                           }
>                       }
>                   }
>               }
>   
>             /* Now create the return array, and copy the non-removed items */
>             *pcondensed_targets = apr_array_make(pool, num_condensed,
>                                                  sizeof(svn_string_t*));
>             for (i = 0; i < abs_targets->nelts; ++i)
>               {
>                 if (!removed[i])
>                   {
>                     char * rel_item = ((svn_string_t**)abs_targets->elts)[i]->data;
>                     rel_item += (*pbasedir)->len + 1;
>                     (*((svn_string_t**)apr_array_push(*pcondensed_targets)))
>                       = svn_string_create(rel_item, pool);
>                   }
>               }
>           }
>   
>         /* Finally check if pbasedir is a dir or a file. */
>         SVN_ERR(svn_path_split_if_file(*pbasedir, pbasedir, &file, pool));
>         if (pcondensed_targets != NULL)
>           {
>             /* If we have only one element, then it is currently the empty string.
>                Set it to the file if we found one, or the empty string, if not. */
>             if (num_condensed == 1)
>               ((svn_string_t **)(*pcondensed_targets)->elts)[0] = file;
>           }
>       }
>     return SVN_NO_ERROR;
>   }
>   
>   
>   /* 
>    * local variables:
>    * eval: (load-file "../svn-dev.el")
>    * end: */
>   
>   
>   
>   1.17      +8 -2      subversion/subversion/tests/libsvn_subr/Makefile.am
>   
>   Index: Makefile.am
>   ===================================================================
>   RCS file: /cvs/subversion/subversion/tests/libsvn_subr/Makefile.am,v
>   retrieving revision 1.16
>   retrieving revision 1.17
>   diff -u -r1.16 -r1.17
>   --- Makefile.am	2001/02/05 00:25:16	1.16
>   +++ Makefile.am	2001/02/12 18:00:28	1.17
>   @@ -1,8 +1,9 @@
>    ## Makefile.in is generated from this by automake.
>    
>   -noinst_PROGRAMS = hashdump-test stringtest
>   +noinst_PROGRAMS = hashdump-test stringtest target-test
>    hashdump_test_SOURCES = hashdump-test.c
>    stringtest_SOURCES = stringtest.c
>   +target_test_SOURCES = target-test.c
>    
>    ## Flags needed when compiling:
>    INCLUDES = @SVN_INCLUDES@ @SVN_APR_INCLUDES@
>   @@ -20,13 +21,18 @@
>                       @SVN_LIBSVN_SUBR_LIBS@ \
>                       @SVN_APR_LIBS@ @SVN_EXPAT_LIBS@
>    
>   +target_test_LDADD = @SVN_LIBSVN_SUBR_LIBS@ @SVN_APR_LIBS@ @SVN_EXPAT_LIBS@
>   +
>    ## Make libtool be quiet
>    LIBTOOL = @LIBTOOL@ --silent
>    
>    ## Automatic tests run by `make check` -----------------------------
>    
>    ## A list of test-programs to run.  (Each program contains sub-tests.)
>   -SVN_TESTS = stringtest hashdump-test 
>   +SVN_TESTS = stringtest hashdump-test $(srcdir)/target-test.sh
>   +
>   +## Give the shell script along with the distribution
>   +EXTRA_DIST = target-test.sh
>    
>    ## We're overriding automake's own `check' rule, because it's extremely
>    ## inflexible;  we want better control over automated-test output. 
>   
>   
>   
>   1.1                  subversion/subversion/tests/libsvn_subr/target-test.c
>   
>   Index: target-test.c
>   ===================================================================
>   #include <svn_path.h>
>   #include <stdio.h>
>   #include <apr_general.h>
>   
>   int main(int argc, char **argv)
>   {
>     apr_pool_t *pool;
>     svn_error_t *err;
>     apr_array_header_t *targets;
>     apr_array_header_t *condensed_targets;
>     svn_string_t *common_path = 0;
>     int i;
>   
>     if (argc < 2) {
>       fprintf(stderr, "USAGE: %s <list of entries to be compared>\n", argv[0]);
>       return EXIT_FAILURE;
>     }
>   
>     apr_initialize();
>     pool = svn_pool_create(NULL);
>   
>     /* Create the target array */
>     targets = apr_array_make(pool, argc - 1, sizeof(svn_string_t*));
>     for (i = 1; i < argc; i++)
>       {
>         svn_string_t * target = svn_string_create(argv[i], pool);
>         (*((svn_string_t **)apr_array_push(targets))) = target;
>       }
>   
>     /* Call the function */
>     err = svn_path_condense_targets(&common_path, &condensed_targets, targets, pool);
>     if (err != SVN_NO_ERROR)
>       svn_handle_error(err, stderr, 1);
>   
>     /* Display the results */
>     printf("%s: ", common_path->data);
>     for (i = 0; i < condensed_targets->nelts; i++)
>       {
>         svn_string_t * target = ((svn_string_t**)condensed_targets->elts)[i];
>         if (target)
>           printf("%s, ", ((svn_string_t **)condensed_targets->elts)[i]->data);
>         else
>           printf("NULL, "); 
>       }
>     printf("\n");
>   
>     /* Now ensure it works without the pbasename */
>     err = svn_path_condense_targets(&common_path, NULL, targets, pool);
>     if (err != SVN_NO_ERROR)
>       svn_handle_error(err, stderr, 1);
>   
>     printf("%s\n", common_path->data);
>   
>     return EXIT_SUCCESS;
>   }
>   
>   
>   
>   1.1                  subversion/subversion/tests/libsvn_subr/target-test.sh
>   
>   Index: target-test.sh
>   ===================================================================
>   #!/bin/sh
>   
>   if ! test -d "z"; then
>       mkdir z
>       mkdir z/A
>       mkdir z/A/B
>       mkdir z/A/C
>       mkdir z/D
>       mkdir z/D/E
>       mkdir z/D/F
>       mkdir z/G
>       mkdir z/G/H
>       mkdir z/G/I
>       touch z/A/file
>   fi
>   
>   echo "Testing normal usage"
>   ./target-test z/A/B z/A z/A/C z/D/E z/D/F z/D z/G z/G/H z/G/I
>   echo
>   
>   echo "Testing with identical arguments (that are dirs)"
>   ./target-test z/A z/A z/A z/A
>   echo
>   
>   echo "Testing with identical arguments (that are files)"
>   ./target-test z/A/file z/A/file z/A/file z/A/file
>   echo
>   
>   echo "Testing with a single dir"
>   ./target-test z/A
>   echo
>   
>   echo "Testing with a single file"
>   ./target-test z/A/file
>   echo
>   
>   
>
Received on Sat Oct 21 14:36:22 2006