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

Proposal: Support for versioning symlinks and other special files.

From: Josh Pieper <jjp_at_pobox.com>
Date: 2004-06-28 15:03:18 CEST

I've begun work on implementing versioning of symlinks and other
special device files in Subversion. It follows the approach suggested
by Nuutti Kotivuori in Issue #677 of using the EOL/keyword translation
framework to "translate" all the special files into a textual version
that can be stored in the text-base and in the repository. The
translation is controlled by a new Subversion namespace property,
"svn:special".

This approach should allow Windows and other platforms without support
for the special devices to still view, modify, and create them,
although the files will not have the special meaning that they do on
UNIX like platforms. I am not proposing any use of symlinks as a way
to replace svn:externals, instead the destination of the symlink is
just treated like the versioned contents of a file. I think this gets
95% of the cases when you actually want to version a symlink.

I'm mainly looking for feedback on the general approach in this
message, but I have attached a patch that implements some of this
behavior. It is not complete and several items remain to be finished.
First, only symlinks are supported, however support for character
devices, block devices, sockets, and FIFOs would mostly be similar to
that for symlinks. Second, I haven't actually tried to implement
support for non-UNIX like systems yet. Finally, I believe a new type
of entry in the WC entries file is necessary, perhaps "special" or
something similar. It would allow the WC code to determine when a
normal file has been replaced with a special file or vice versa.
Changes between different types of special files can be handled using
the normal translation framework.

One other question about the patch. In it, I change the behavior of
svn_io_check_path to return svn_node_file for any special file,
instead of svn_node_unknown. This is still in line with the docstring
for the function, but is a behavior change. Is this acceptable within
the 1.x line of development, or should it get a new API routine while
deprecating the old?

-Josh

========================================================

Issue #677: Allow special files such as symlinks to be added to
working copies on systems that support them. The special files are
stored in the repository as normal files with the svn:special property
set and the information necessary to recreate the special file are
stored in the contents of the normal file. Most of the hard work is
done using the existing EOL/keyword translation support.

* subversion/include/svn_subst.h
  (SVN_SUBST__SPECIAL_LINK_STR,
   SVN_SUBST__SPECIAL_CHR_STR,
   SVN_SUBST__SPECIAL_BLK_STR,
   SVN_SUBST__SPECIAL_FIFO_STR,
   SVN_SUBST__SPECIAL_SOCK_STR): New defines that allow the correct
    special device to be created given a repository file'ified version
    of it.
  (svn_subst_copy_and_translate): Deprecated.
  (svn_subst_copy_and_translate2): New routine which duplicates the
    functionality of svn_subst_copy_and_translate and also allows
    translation of special files. All callers changed to use the new
    API.

* subversion/include/svn_props.h
  (SVN_PROP_SPECIAL, SVN_PROP_SPECIAL_VALUE): New property that sets a
    file as being a "special" file.

* subversion/include/svn_types.h
  (svn_node_kind_t): Include all the possible types that APR supports
    as possible node kinds.

* subversion/include/svn_io.h
  (svn_io_check_special_path): New.
  (svn_io_create_unique_link): New.
  (svn_io_read_link): New.

* subversion/libsvn_wc/merge.c
  (svn_wc_merge): Use the new svn_subst_copy_and_translate2.

* subversion/libsvn_wc/translate.h
  (svn_wc__get_special): New.

* subversion/libsvn_wc/adm_crawler.c
  (restore_file): Use the new svn_subst_copy_and_translate2.

* subversion/libsvn_wc/log.c
  (file_xfer_under_path, install_committed_file): Use the new
    svn_subst_copy_and_translate2.

* subversion/libsvn_wc/adm_ops.c
  (revert_admin_things): Use the new svn_subst_copy_and_translate2.

* subversion/libsvn_wc/translate.c
  (svn_wc_translated_file): Use the new svn_subst_copy_and_translate2.
  (svn_wc__get_special): New, determines if the svn:special property
    is set on a path.

* subversion/libsvn_subr/subst.c
  (create_special_file): New, given a repository normal version of a
    special file, create the actual special file using whatever
    platform-dependent methods are necessary.
  (detranslate_special_file): New, given an actual special file,
    create a repository normal version of it.
  (svn_subst_copy_and_translate): Impelemented in terms of
    svn_subst_copy_and_translate2.
  (svn_subst_copy_and_translate2): New, similar to
    svn_subst_copy_and_translate except for the addition of the
    special option, which controls whether a file should be translated
    as if it were a special file.

* subversion/libsvn_subr/io.c
  (io_check_path): Add a new option that determines whether special
    file types will be collapsed into svn_node_file or whether they
    will be returned as their actual types.
  (svn_io_check_path, svn_io_check_resolved_path): Use the new
    io_check_path. Note that this changes their behavior slightly
    since special files used to return svn_node_unknown and will now
    return svn_node_file.
  (svn_io_check_special_path): New, similar to svn_io_check_path,
    except that any special files are returned as their actual type,
    not svn_node_file.
  (svn_io_create_unique_link): New, creates a symlink that has a
    unique name.
  (svn_io_read_link): New, reads the destination path from a symlink.

* subversion/libsvn_client/add.c
  (add_file): Check to see if the svn:special property should be set
    when adding a new file.
  (add_dir_recursive): Add all files types that aren't unknown, rather
    than just regular files.

Index: subversion/include/svn_subst.h
===================================================================
--- subversion/include/svn_subst.h (revision 10080)
+++ subversion/include/svn_subst.h (working copy)
@@ -32,6 +32,17 @@
 extern "C" {
 #endif /* __cplusplus */
 
+/**
+ * The textual elements of a detranslated special file. One of these
+ * strings must appear as the first element of any special file as it
+ * exists in the repository or the text base.
+ */
+#define SVN_SUBST__SPECIAL_LINK_STR "link"
+#define SVN_SUBST__SPECIAL_CHR_STR "chr"
+#define SVN_SUBST__SPECIAL_BLK_STR "blk"
+#define SVN_SUBST__SPECIAL_FIFO_STR "fifo"
+#define SVN_SUBST__SPECIAL_SOCK_STR "sock"
+
 /* Eol conversion and keyword expansion. */
 
 /** Valid states for 'svn:eol-style' property.
@@ -163,9 +174,29 @@
                             svn_boolean_t expand);
 
 
-/** Convenience routine: a variant of @c svn_subst_translate_stream which
- * operates on files. (See previous docstring for details.)
+/**
+ * @deprecated Provided for backward compatibility with the 1.0.0 API.
  *
+ * Similar to svn_subst_copy_and_translate2 except that @a special is
+ * always set to @c FALSE.
+ */
+svn_error_t *
+svn_subst_copy_and_translate (const char *src,
+ const char *dst,
+ const char *eol_str,
+ svn_boolean_t repair,
+ const svn_subst_keywords_t *keywords,
+ svn_boolean_t expand,
+ apr_pool_t *pool);
+
+/**
+ * @since New in 1.1.
+ *
+ * Convenience routine: a variant of @c svn_subst_translate_stream
+ * which operates on files. (See previous docstring for details.) In
+ * addition, it will create/detranslate a special file if @a special
+ * is @c TRUE.
+ *
  * Copy the contents of file-path @a src to file-path @a dst atomically,
  * either creating @a dst (or overwriting @a dst if it exists), possibly
  * performing line ending and keyword translations.
@@ -177,13 +208,14 @@
  * copy.
  */
 svn_error_t *
-svn_subst_copy_and_translate (const char *src,
- const char *dst,
- const char *eol_str,
- svn_boolean_t repair,
- const svn_subst_keywords_t *keywords,
- svn_boolean_t expand,
- apr_pool_t *pool);
+svn_subst_copy_and_translate2 (const char *src,
+ const char *dst,
+ const char *eol_str,
+ svn_boolean_t repair,
+ const svn_subst_keywords_t *keywords,
+ svn_boolean_t expand,
+ svn_boolean_t special,
+ apr_pool_t *pool);
 
 /** Convenience routine: a variant of @c svn_subst_translate_stream which
  * operates on cstrings. (See previous docstring for details.)
Index: subversion/include/svn_props.h
===================================================================
--- subversion/include/svn_props.h (revision 10080)
+++ subversion/include/svn_props.h (working copy)
@@ -182,6 +182,12 @@
 /** The value to force the executable property to when set */
 #define SVN_PROP_EXECUTABLE_VALUE "*"
 
+/** Set if the file should be treated as a special file. */
+#define SVN_PROP_SPECIAL SVN_PROP_PREFIX "special"
+
+/** The value to force the special property to when set. */
+#define SVN_PROP_SPECIAL_VALUE "*"
+
 /** Describes external items to check out into this directory.
  *
  * The format is a series of lines, such as:
Index: subversion/include/svn_types.h
===================================================================
--- subversion/include/svn_types.h (revision 10080)
+++ subversion/include/svn_types.h (working copy)
@@ -85,6 +85,21 @@
   /* directory */
   svn_node_dir,
 
+ /* a character device */
+ svn_node_chr,
+
+ /* a block device */
+ svn_node_blk,
+
+ /* a FIFO pipe */
+ svn_node_pipe,
+
+ /* a symbolic link */
+ svn_node_link,
+
+ /* a socket */
+ svn_node_sock,
+
   /* something's here, but we don't know what */
   svn_node_unknown
 
Index: subversion/include/svn_io.h
===================================================================
--- subversion/include/svn_io.h (revision 10080)
+++ subversion/include/svn_io.h (working copy)
@@ -59,6 +59,16 @@
                                 svn_node_kind_t *kind,
                                 apr_pool_t *pool);
 
+/**
+ * @since New in 1.1.
+ *
+ * Like svn_io_check_path(), but return the actual node type for any
+ * special files.
+ */
+svn_error_t *svn_io_check_special_path (const char *path,
+ svn_node_kind_t *kind,
+ apr_pool_t *pool);
+
 /** Like svn_io_check_path(), but resolve symlinks. This returns the
     same varieties of @a kind as svn_io_check_path(). */
 svn_error_t *svn_io_check_resolved_path (const char *path,
@@ -119,7 +129,30 @@
                                       svn_boolean_t delete_on_close,
                                       apr_pool_t *pool);
 
+/**
+ * @since New in 1.1.
+ *
+ * Like svn_io_open_unique_file, except that instead of creating a
+ * file, a symlink is generated that references the path @a dest.
+ */
+svn_error_t *svn_io_create_unique_link (const char **unique_name_p,
+ const char *path,
+ const char *dest,
+ const char *suffix,
+ apr_pool_t *pool);
 
+
+/**
+ * @since New in 1.1.
+ *
+ * Set @a dest to the path that the symlink at @a path references.
+ * Allocate the string from @a pool.
+ */
+svn_error_t *svn_io_read_link (svn_string_t **dest,
+ const char *path,
+ apr_pool_t *pool);
+
+
 /** Set @a dir to a directory path (allocated in @a pool) deemed
  * usable for the creation of temporary files and subdirectories.
  */
Index: subversion/libsvn_wc/merge.c
===================================================================
--- subversion/libsvn_wc/merge.c (revision 10080)
+++ subversion/libsvn_wc/merge.c (working copy)
@@ -52,7 +52,7 @@
   svn_subst_keywords_t *keywords;
   const char *eol;
   const svn_wc_entry_t *entry;
- svn_boolean_t contains_conflicts;
+ svn_boolean_t contains_conflicts, special;
 
   svn_path_split (merge_target, &mt_pt, &mt_bn, pool);
 
@@ -234,14 +234,18 @@
                                          NULL, pool));
           SVN_ERR (svn_wc__get_eol_style (NULL, &eol, merge_target, adm_access,
                                           pool));
- SVN_ERR (svn_subst_copy_and_translate (left,
- left_copy,
- eol, eol ? TRUE : FALSE,
- keywords, TRUE, pool));
- SVN_ERR (svn_subst_copy_and_translate (right,
- right_copy,
- eol, eol ? TRUE : FALSE,
- keywords, TRUE, pool));
+ SVN_ERR (svn_wc__get_special (&special, merge_target, adm_access,
+ pool));
+ SVN_ERR (svn_subst_copy_and_translate2 (left,
+ left_copy,
+ eol, eol ? TRUE : FALSE,
+ keywords, TRUE, special,
+ pool));
+ SVN_ERR (svn_subst_copy_and_translate2 (right,
+ right_copy,
+ eol, eol ? TRUE : FALSE,
+ keywords, TRUE, special,
+ pool));
 
           /* Back up MERGE_TARGET verbatim (it's already in expanded form.) */
           SVN_ERR (svn_io_copy_file (merge_target,
@@ -289,9 +293,12 @@
                                          NULL, pool));
           SVN_ERR (svn_wc__get_eol_style (NULL, &eol, merge_target, adm_access,
                                           pool));
- SVN_ERR (svn_subst_copy_and_translate (result_target, merge_target,
- eol, eol ? TRUE : FALSE,
- keywords, TRUE, pool));
+ SVN_ERR (svn_wc__get_special (&special, merge_target, adm_access,
+ pool));
+ SVN_ERR (svn_subst_copy_and_translate2 (result_target, merge_target,
+ eol, eol ? TRUE : FALSE,
+ keywords, TRUE, special,
+ pool));
         }
 
       /* Don't forget to clean up tmp_target, result_target, tmp_left,
Index: subversion/libsvn_wc/translate.h
===================================================================
--- subversion/libsvn_wc/translate.h (revision 10080)
+++ subversion/libsvn_wc/translate.h (working copy)
@@ -88,6 +88,15 @@
                                    apr_pool_t *pool);
 
 
+/* Determine if the svn:special flag is set on PATH. If so, set
+ SPECIAL to TRUE, if not, set it to FALSE. ADM_ACCESS must be an
+ access baton for PATH. Perform any temporary allocations in
+ POOL. */
+svn_error_t *svn_wc__get_special (svn_boolean_t *special,
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *pool);
+
 /* If the SVN_PROP_EXECUTABLE property is present at all, then set
    PATH executable. If DID_SET is non-null, then set *DID_SET to
    TRUE if did set PATH executable, or to FALSE if not. ADM_ACCESS
Index: subversion/libsvn_wc/adm_crawler.c
===================================================================
--- subversion/libsvn_wc/adm_crawler.c (revision 10080)
+++ subversion/libsvn_wc/adm_crawler.c (working copy)
@@ -69,6 +69,7 @@
   apr_time_t tstamp;
   const char *bname;
   apr_uint32_t modify_flags = 0;
+ svn_boolean_t special;
 
   text_base_path = svn_wc__text_base_path (file_path, FALSE, pool);
   tmp_text_base_path = svn_wc__text_base_path (file_path, TRUE, pool);
@@ -80,17 +81,20 @@
   SVN_ERR (svn_wc__get_eol_style (NULL, &eol, file_path, adm_access, pool));
   SVN_ERR (svn_wc__get_keywords (&keywords,
                                  file_path, adm_access, NULL, pool));
+ SVN_ERR (svn_wc__get_special (&special, file_path, adm_access, pool));
+
   
   /* When copying the tmp-text-base out to the working copy, make
      sure to do any eol translations or keyword substitutions,
      as dictated by the property values. If these properties
      are turned off, then this is just a normal copy. */
- SVN_ERR (svn_subst_copy_and_translate (tmp_text_base_path,
- file_path,
- eol, FALSE, /* don't repair */
- keywords,
- TRUE, /* expand keywords */
- pool));
+ SVN_ERR (svn_subst_copy_and_translate2 (tmp_text_base_path,
+ file_path,
+ eol, FALSE, /* don't repair */
+ keywords,
+ TRUE, /* expand keywords */
+ special,
+ pool));
   
   SVN_ERR (svn_io_remove_file (tmp_text_base_path, pool));
 
Index: subversion/libsvn_wc/log.c
===================================================================
--- subversion/libsvn_wc/log.c (revision 10080)
+++ subversion/libsvn_wc/log.c (working copy)
@@ -115,20 +115,24 @@
       {
         svn_subst_keywords_t *keywords;
         const char *eol_str;
+ svn_boolean_t special;
 
         /* Note that this action takes properties from dest, not source. */
         SVN_ERR (svn_wc__get_keywords (&keywords, full_dest_path, adm_access,
                                        NULL, pool));
         SVN_ERR (svn_wc__get_eol_style (NULL, &eol_str, full_dest_path,
                                         adm_access, pool));
+ SVN_ERR (svn_wc__get_special (&special, full_dest_path, adm_access,
+ pool));
 
- SVN_ERR (svn_subst_copy_and_translate (full_from_path,
- full_dest_path,
- eol_str,
- TRUE,
- keywords,
- TRUE,
- pool));
+ SVN_ERR (svn_subst_copy_and_translate2 (full_from_path,
+ full_dest_path,
+ eol_str,
+ TRUE,
+ keywords,
+ TRUE,
+ special,
+ pool));
 
         /* After copying, set the file executable if props dictate. */
         return svn_wc__maybe_set_executable (NULL, full_dest_path, adm_access,
@@ -139,23 +143,27 @@
       {
         svn_subst_keywords_t *keywords;
         const char *eol_str;
+ svn_boolean_t special;
 
         /* Note that this action takes properties from source, not dest. */
         SVN_ERR (svn_wc__get_keywords (&keywords, full_from_path, adm_access,
                                        NULL, pool));
         SVN_ERR (svn_wc__get_eol_style (NULL, &eol_str, full_from_path,
                                         adm_access, pool));
+ SVN_ERR (svn_wc__get_special (&special, full_from_path, adm_access,
+ pool));
 
         /* If any specific eol style was indicated, then detranslate
            back to repository normal form ("\n"), repairingly. But if
            no style indicated, don't touch line endings at all. */
- return svn_subst_copy_and_translate (full_from_path,
- full_dest_path,
- (eol_str ? "\n" : NULL),
- (eol_str ? TRUE : FALSE),
- keywords,
- FALSE, /* contract keywords */
- pool);
+ return svn_subst_copy_and_translate2 (full_from_path,
+ full_dest_path,
+ (eol_str ? "\n" : NULL),
+ (eol_str ? TRUE : FALSE),
+ keywords,
+ FALSE, /* contract keywords */
+ special,
+ pool);
       }
 
     case svn_wc__xfer_mv:
@@ -212,6 +220,7 @@
   svn_boolean_t same, did_set;
   const char *tmp_wfile, *pdir, *bname;
   const char *eol_str;
+ svn_boolean_t special;
 
   /* start off assuming that the working file isn't touched. */
   *overwrote_working = FALSE;
@@ -238,6 +247,7 @@
   /* start off getting the latest translation prop values. */
   SVN_ERR (svn_wc__get_eol_style (NULL, &eol_str, filepath, adm_access, pool));
   SVN_ERR (svn_wc__get_keywords (&keywords, filepath, adm_access, NULL, pool));
+ SVN_ERR (svn_wc__get_special (&special, filepath, adm_access, pool));
 
   svn_path_split (filepath, &pdir, &bname, pool);
   tmp_wfile = svn_wc__adm_path (pdir, TRUE, pool, bname, NULL);
@@ -252,23 +262,32 @@
   SVN_ERR (svn_io_check_path (tmp_text_base, &kind, pool));
 
   if (kind == svn_node_file)
- SVN_ERR (svn_subst_copy_and_translate (tmp_text_base,
- tmp_wfile,
- eol_str,
- FALSE, /* don't repair eol */
- keywords,
- TRUE, /* expand keywords */
- pool));
+ SVN_ERR (svn_subst_copy_and_translate2 (tmp_text_base,
+ tmp_wfile,
+ eol_str,
+ FALSE, /* don't repair eol */
+ keywords,
+ TRUE, /* expand keywords */
+ special,
+ pool));
   else
- SVN_ERR (svn_subst_copy_and_translate (filepath,
- tmp_wfile,
- eol_str,
- FALSE, /* don't repair eol */
- keywords,
- TRUE, /* expand keywords */
- pool));
+ SVN_ERR (svn_subst_copy_and_translate2 (filepath,
+ tmp_wfile,
+ eol_str,
+ FALSE, /* don't repair eol */
+ keywords,
+ TRUE, /* expand keywords */
+ special,
+ pool));
 
- SVN_ERR (svn_io_files_contents_same_p (&same, tmp_wfile, filepath, pool));
+ if (! special)
+ {
+ SVN_ERR (svn_io_files_contents_same_p (&same, tmp_wfile, filepath, pool));
+ }
+ else
+ {
+ same = TRUE;
+ }
   
   if (! same)
     {
Index: subversion/libsvn_wc/adm_ops.c
===================================================================
--- subversion/libsvn_wc/adm_ops.c (revision 10080)
+++ subversion/libsvn_wc/adm_ops.c (working copy)
@@ -1245,23 +1245,26 @@
              file. */
           svn_subst_keywords_t *keywords;
           const char *eol;
+ svn_boolean_t special;
           
           SVN_ERR (svn_wc__get_eol_style (NULL, &eol, fullpath, adm_access,
                                           pool));
           SVN_ERR (svn_wc__get_keywords (&keywords, fullpath, adm_access, NULL,
                                          pool));
+ SVN_ERR (svn_wc__get_special (&special, fullpath, adm_access, pool));
 
           /* When copying the text-base out to the working copy, make
              sure to do any eol translations or keyword substitutions,
              as dictated by the property values. If these properties
              are turned off, then this is just a normal copy. */
- if ((err = svn_subst_copy_and_translate (base_thing,
- fullpath,
- eol,
- FALSE, /* don't repair */
- keywords,
- TRUE, /* expand keywords */
- pool)))
+ if ((err = svn_subst_copy_and_translate2 (base_thing,
+ fullpath,
+ eol,
+ FALSE, /* don't repair */
+ keywords,
+ TRUE, /* expand keywords */
+ special,
+ pool)))
             return svn_error_quick_wrap
               (err, apr_psprintf (pool, _("Error restoring text for '%s'"),
                                   fullpath));
Index: subversion/libsvn_wc/translate.c
===================================================================
--- subversion/libsvn_wc/translate.c (revision 10080)
+++ subversion/libsvn_wc/translate.c (working copy)
@@ -57,11 +57,13 @@
   svn_subst_eol_style_t style;
   const char *eol;
   svn_subst_keywords_t *keywords;
+ svn_boolean_t special;
   
   SVN_ERR (svn_wc__get_eol_style (&style, &eol, vfile, adm_access, pool));
   SVN_ERR (svn_wc__get_keywords (&keywords, vfile, adm_access, NULL, pool));
+ SVN_ERR (svn_wc__get_special (&special, vfile, adm_access, pool));
 
- if ((style == svn_subst_eol_style_none) && (! keywords))
+ if ((style == svn_subst_eol_style_none) && (! keywords) && (! special))
     {
       /* Translation would be a no-op, so return the original file. */
       *xlated_p = vfile;
@@ -91,33 +93,36 @@
       
       if (style == svn_subst_eol_style_fixed)
         {
- SVN_ERR (svn_subst_copy_and_translate (vfile,
- tmp_vfile,
- eol,
- TRUE,
- keywords,
- FALSE,
- pool));
+ SVN_ERR (svn_subst_copy_and_translate2 (vfile,
+ tmp_vfile,
+ eol,
+ TRUE,
+ keywords,
+ FALSE,
+ special,
+ pool));
         }
       else if (style == svn_subst_eol_style_native)
         {
- SVN_ERR (svn_subst_copy_and_translate (vfile,
- tmp_vfile,
- SVN_WC__DEFAULT_EOL_MARKER,
- force_repair,
- keywords,
- FALSE,
- pool));
+ SVN_ERR (svn_subst_copy_and_translate2 (vfile,
+ tmp_vfile,
+ SVN_WC__DEFAULT_EOL_MARKER,
+ force_repair,
+ keywords,
+ FALSE,
+ special,
+ pool));
         }
       else if (style == svn_subst_eol_style_none)
         {
- SVN_ERR (svn_subst_copy_and_translate (vfile,
- tmp_vfile,
- NULL,
- force_repair,
- keywords,
- FALSE,
- pool));
+ SVN_ERR (svn_subst_copy_and_translate2 (vfile,
+ tmp_vfile,
+ NULL,
+ force_repair,
+ keywords,
+ FALSE,
+ special,
+ pool));
         }
       else
         {
@@ -220,6 +225,24 @@
 
 
 svn_error_t *
+svn_wc__get_special (svn_boolean_t *special,
+ const char *path,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *pool)
+{
+ const svn_string_t *propval;
+
+ /* Get the property value. */
+ SVN_ERR (svn_wc_prop_get (&propval, SVN_PROP_SPECIAL, path, adm_access,
+ pool));
+
+ *special = propval ? TRUE : FALSE;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
 svn_wc__maybe_set_executable (svn_boolean_t *did_set,
                               const char *path,
                               svn_wc_adm_access_t *adm_access,
Index: subversion/libsvn_subr/subst.c
===================================================================
--- subversion/libsvn_subr/subst.c (revision 10080)
+++ subversion/libsvn_subr/subst.c (working copy)
@@ -779,9 +779,171 @@
   return err;
 }
 
+/* Given a file containing a repository representation of a special
+ file in SRC, create the appropriate special file at location DST.
+ Perform all allocations in POOL. */
+static svn_error_t *
+create_special_file (const char *src,
+ const char *dst,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *contents;
+ char *identifier, *remainder;
+ const char *dst_tmp;
+ int i;
 
+ /* Read in the detranslated file. */
+ SVN_ERR (svn_stringbuf_from_file (&contents, src, pool));
 
+ /* Separate off the identifier. The first space character delimits
+ the identifier, after which any remaining characters are specific
+ to the actual special device being created. */
+ identifier = contents->data;
+ for (remainder = identifier; *remainder; remainder++)
+ {
+ if (*remainder == ' ')
+ {
+ *remainder = '\0';
+ remainder++;
+ break;
+ }
+ }
+
+ if (! strcmp (identifier, SVN_SUBST__SPECIAL_LINK_STR))
+ {
+ /* For symlinks, the type specific data is just a filesystem
+ path that the symlink should reference. */
+ SVN_ERR (svn_io_create_unique_link (&dst_tmp, dst, remainder,
+ ".tmp", pool));
+ }
+ /*
+ else if (! strcmp (identifier, SVN_SUBST__SPECIAL_CHR_STR) ||
+ ! strcmp (identifier, SVN_SUBST__SPECIAL_BLK_STR))
+ {
+ char *major_str, *minor_str;
+ int major, minor;
+ apr_filetype_t type;
+
+ major_str = apr_strtok (remainder, " ", &minor_str);
+
+ if ((! major_str) || (! minor_str))
+ abort();
+
+ major = atoi (major_str);
+ minor = atoi (minor_str);
+
+ type = (! strcmp (identifier, SVN_SUBST__SPECIAL_CHR_STR)) ? APR_CHR
+ : APR_BLK;
+
+ SVN_ERR (svn_io_create_unique_device (&dst_tmp, dst, type,
+ major, minor,
+ ".tmp", pool));
+ }
+ else if (! strcmp (identifier, SVN_SUBST__SPECIAL_FIFO_STR))
+ {
+ SVN_ERR (svn_io_create_unique_fifo (&dst_tmp, dst, ".tmp", pool));
+ }
+ else if (! strcmp (identifier, SVN_SUBST__SPECIAL_SOCK_STR))
+ {
+ SVN_ERR (svn_io_create_unique_socket (&dst_tmp, dst, ".tmp", pool));
+ }
+ */
+ else
+ {
+ /* We should return a valid error here. */
+ abort ();
+ }
+
+ /* Do the atomic rename from our temporary location. */
+ SVN_ERR (svn_io_file_rename (dst_tmp, dst, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Given a special file at SRC, generate a textual representation of
+ it in a normal file at DST. Perform all allocations in POOL. */
+static svn_error_t *
+detranslate_special_file (const char *src,
+ const char *dst,
+ apr_pool_t *pool)
+{
+ svn_node_kind_t kind;
+ const char *dst_tmp;
+ svn_string_t *buf;
+ apr_file_t *s, *d;
+ svn_stream_t *src_stream, *dst_stream;
+
+ /* First determine what type of special file we are
+ detranslating. */
+ SVN_ERR (svn_io_check_special_path (src, &kind, pool));
+
+ /* Open a temporary destination that we will eventually atomically
+ rename into place. */
+ SVN_ERR (svn_io_open_unique_file (&d, &dst_tmp, dst,
+ ".tmp", FALSE, pool));
+
+ dst_stream = svn_stream_from_aprfile (d, pool);
+
+ switch (kind) {
+ case svn_node_file:
+ /* Nothing special to do here, just copy the original file's
+ contents. */
+ SVN_ERR (svn_io_file_open (&s, src, APR_READ | APR_BUFFERED,
+ APR_OS_DEFAULT, pool));
+ src_stream = svn_stream_from_aprfile (d, pool);
+
+ SVN_ERR (svn_stream_copy (src_stream, dst_stream, pool));
+ break;
+ case svn_node_link:
+ /* Determine the destination of the link. */
+ SVN_ERR (svn_io_read_link (&buf, src, pool));
+
+ SVN_ERR (svn_stream_printf (dst_stream, pool, "link %s",
+ buf->data));
+ break;
+ default:
+ abort ();
+ }
+
+ SVN_ERR (svn_io_file_close (d, pool));
+
+ /* Do the atomic rename from our temporary location. */
+ SVN_ERR (svn_io_file_rename (dst_tmp, dst, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
 svn_error_t *
+svn_subst_copy_and_translate2 (const char *src,
+ const char *dst,
+ const char *eol_str,
+ svn_boolean_t repair,
+ const svn_subst_keywords_t *keywords,
+ svn_boolean_t expand,
+ svn_boolean_t special,
+ apr_pool_t *pool)
+{
+ /* Handle the case when we are handling normal files. */
+ if (! special)
+ {
+ return svn_subst_copy_and_translate (src, dst, eol_str, repair, keywords,
+ expand, pool);
+ }
+
+ /* If this is a 'special' file, we may need to create it or
+ detranslate it. */
+ if (expand)
+ SVN_ERR (create_special_file (src, dst, pool));
+ else
+ SVN_ERR (detranslate_special_file (src, dst, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
 svn_subst_translate_string (svn_string_t **new_value,
                             const svn_string_t *value,
                             const char *encoding,
Index: subversion/libsvn_subr/io.c
===================================================================
--- subversion/libsvn_subr/io.c (revision 10080)
+++ subversion/libsvn_subr/io.c (working copy)
@@ -90,6 +90,7 @@
 static svn_error_t *
 io_check_path (const char *path,
                svn_boolean_t resolve_symlinks,
+ svn_boolean_t expand_special,
                svn_node_kind_t *kind,
                apr_pool_t *pool)
 {
@@ -120,10 +121,8 @@
     *kind = svn_node_file;
   else if (finfo.filetype == APR_DIR)
     *kind = svn_node_dir;
-#if 0
- else if (finfo.filetype == APR_LINK)
- *kind = svn_node_symlink; /* we support symlinks someday, but not yet */
-#endif /* 0 */
+ else if (finfo.filetype == APR_LNK)
+ *kind = expand_special ? svn_node_link : svn_node_file;
   else
     *kind = svn_node_unknown;
 
@@ -136,7 +135,7 @@
                             svn_node_kind_t *kind,
                             apr_pool_t *pool)
 {
- return io_check_path (path, TRUE, kind, pool);
+ return io_check_path (path, TRUE, FALSE, kind, pool);
 }
 
 svn_error_t *
@@ -144,9 +143,16 @@
                    svn_node_kind_t *kind,
                    apr_pool_t *pool)
 {
- return io_check_path (path, FALSE, kind, pool);
+ return io_check_path (path, FALSE, FALSE, kind, pool);
 }
 
+svn_error_t *
+svn_io_check_special_path (const char *path,
+ svn_node_kind_t *kind,
+ apr_pool_t *pool)
+{
+ return io_check_path (path, FALSE, TRUE, kind, pool);
+}
 
 svn_error_t *
 svn_io_open_unique_file (apr_file_t **f,
@@ -234,6 +240,114 @@
                             "Unable to make name for '%s'", path);
 }
 
+svn_error_t *
+svn_io_create_unique_link (const char **unique_name_p,
+ const char *path,
+ const char *dest,
+ const char *suffix,
+ apr_pool_t *pool)
+{
+ unsigned int i;
+ const char *unique_name;
+ const char *unique_name_apr;
+ int rv;
+
+ for (i = 1; i <= 99999; i++)
+ {
+ apr_status_t apr_err;
+
+ /* Special case the first attempt -- if we can avoid having a
+ generated numeric portion at all, that's best. So first we
+ try with just the suffix; then future tries add a number
+ before the suffix. (A do-while loop could avoid the repeated
+ conditional, but it's not worth the clarity loss.)
+
+ If the first attempt fails, the first number will be "2".
+ This is good, since "1" would misleadingly imply that
+ the second attempt was actually the first... and if someone's
+ got conflicts on their conflicts, we probably don't want to
+ add to their confusion :-). */
+ if (i == 1)
+ unique_name = apr_psprintf (pool, "%s%s", path, suffix);
+ else
+ unique_name = apr_psprintf (pool, "%s.%u%s", path, i, suffix);
+
+ /* Hmmm. Ideally, we would append to a native-encoding buf
+ before starting iteration, then convert back to UTF-8 for
+ return. But I suppose that would make the appending code
+ sensitive to i18n in a way it shouldn't be... Oh well. */
+ SVN_ERR (svn_path_cstring_from_utf8 (&unique_name_apr, unique_name,
+ pool));
+
+#ifdef WIN32
+
+ rv = 0;
+#else
+ do {
+ rv = symlink (dest, unique_name_apr);
+ } while (rv == -1 && APR_STATUS_IS_EINTR (apr_get_os_error ()));
+#endif
+
+ apr_err = apr_get_os_error();
+
+ if (APR_STATUS_IS_EEXIST (apr_err))
+ continue;
+ else if (rv == -1 && apr_err)
+ {
+ /* On Win32, CreateFile failswith an "Access Denied" error
+ code, rather than "File Already Exists", if the colliding
+ name belongs to a directory. */
+ if (APR_STATUS_IS_EACCES (apr_err))
+ {
+ apr_finfo_t finfo;
+ apr_status_t apr_err_2 = apr_stat (&finfo, unique_name_apr,
+ APR_FINFO_TYPE, pool);
+
+ if (APR_STATUS_IS_SUCCESS (apr_err_2)
+ && (finfo.filetype == APR_DIR))
+ continue;
+
+ /* Else ignore apr_err_2; better to fall through and
+ return the original error. */
+ }
+
+ *unique_name_p = NULL;
+ return svn_error_wrap_apr (apr_err, "Can't open '%s'", unique_name);
+ }
+ else
+ {
+ *unique_name_p = unique_name;
+ return SVN_NO_ERROR;
+ }
+ }
+
+ *unique_name_p = NULL;
+ return svn_error_createf (SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED,
+ NULL,
+ "Unable to make name for '%s'", path);
+}
+
+svn_error_t *
+svn_io_read_link (svn_string_t **dest,
+ const char *path,
+ apr_pool_t *pool)
+{
+ char buf[1024];
+ int rv;
+
+ do {
+ rv = readlink (path, buf, sizeof(buf));
+ } while (rv == -1 && APR_STATUS_IS_EINTR (apr_get_os_error ()));
+
+ if (rv == -1)
+ return svn_error_wrap_apr
+ (apr_get_os_error (), "Can't read contents of link.");
+
+ *dest = svn_string_ncreate (buf, rv, pool);
+
+ return SVN_NO_ERROR;
+}
+
 #if 1 /* TODO: Remove this code when APR 0.9.6 is released. */
 #include "apr_env.h"
 
Index: subversion/libsvn_client/add.c
===================================================================
--- subversion/libsvn_client/add.c (revision 10080)
+++ subversion/libsvn_client/add.c (working copy)
@@ -205,27 +205,46 @@
   apr_hash_t* properties;
   apr_hash_index_t *hi;
   const char *mimetype;
+ svn_node_kind_t kind;
 
   /* add the file */
   SVN_ERR (svn_wc_add (path, adm_access, NULL, SVN_INVALID_REVNUM,
                        ctx->cancel_func, ctx->cancel_baton,
                        NULL, NULL, pool));
- /* get automatic properties */
- SVN_ERR (svn_client__get_auto_props (&properties, &mimetype, path, ctx,
- pool));
- if (properties)
+
+ /* Check to see if this is a special file. */
+ SVN_ERR (svn_io_check_special_path (path, &kind, pool));
+
+ if ((kind != svn_node_dir)
+ && (kind != svn_node_file)
+ && (kind != svn_node_unknown))
     {
- /* loop through the hashtable and add the properties */
- for (hi = apr_hash_first (pool, properties);
- hi != NULL; hi = apr_hash_next (hi))
+ /* This must be a special file. */
+ SVN_ERR (svn_wc_prop_set (SVN_PROP_SPECIAL,
+ svn_string_create (SVN_PROP_SPECIAL_VALUE, pool),
+ path, adm_access, pool));
+ mimetype = NULL;
+ }
+ else
+ {
+ /* get automatic properties */
+ SVN_ERR (svn_client__get_auto_props (&properties, &mimetype, path, ctx,
+ pool));
+ if (properties)
         {
- const void *pname;
- void *pval;
-
- apr_hash_this (hi, &pname, NULL, &pval);
- SVN_ERR (svn_wc_prop_set (pname, pval, path, adm_access, pool));
+ /* loop through the hashtable and add the properties */
+ for (hi = apr_hash_first (pool, properties);
+ hi != NULL; hi = apr_hash_next (hi))
+ {
+ const void *pname;
+ void *pval;
+
+ apr_hash_this (hi, &pname, NULL, &pval);
+ SVN_ERR (svn_wc_prop_set (pname, pval, path, adm_access, pool));
+ }
         }
     }
+
   /* Report the addition to the caller. */
   if (ctx->notify_func != NULL)
     (*ctx->notify_func) (ctx->notify_baton, path, svn_wc_notify_add,
@@ -304,7 +323,7 @@
           SVN_ERR (add_dir_recursive (fullpath, dir_access, force,
                                       ctx, subpool));
         }
- else if (this_entry.filetype == APR_REG)
+ else if (this_entry.filetype != APR_UNKFILE)
         {
           err = add_file (fullpath, ctx, dir_access, subpool);
           if (err && err->apr_err == SVN_ERR_ENTRY_EXISTS && force)

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org
Received on Mon Jun 28 15:05:13 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.