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

PATCH: issue 1361: Enable "svn cat -rBASE" to work without contacting the repository

From: Julian Foad <julianfoad_at_btopenworld.com>
Date: 2003-09-22 15:34:15 CEST

This is the deepest I have dug into RA and WC adm stuff so far, so please tell me what I have missed and what I could do better. For one thing, I won't check it in with the "###...?" comment in it.

I have a strong feeling that much of this logic is, or should be, common to subcommands like "diff", "export" and "copy" as well, and I would be grateful to be steered in that direction.

My goal is to recognise not only "-rBASE" as being local, but any revision specifier that resolves to the cached text-base revision, such as dates and revision numbers that lie anywhere in the range from COMMITTED to BASE inclusive. And to do so in all situations that could benefit from it. But to start with, I decided to just implement "svn cat -rBASE". I don't think it is very important by itself, and am not especially bothered about checking it in if I can instead progress toward a more general implementation.

Below the log message is a human-readable version of the patch, i.e. with intentation changes ignored and consequent word-wrapping edited out. Attached is the real patch, with which you can use your favourite space-insensitive diff tool.

- Julian

[[[
Enable "svn cat -rBASE" to work without contacting the repository
(issue 1361).

* subversion/libsvn_client/cat.c
  (open_text_base_RO) New function.
  (copy_stream) New function.
  (svn_client_cat) If the BASE revision is requested, and a path is given
    rather than a URL, then get the file and all necessary properties etc.
    from the WC administrative area instead of from the repository.
]]]

Index: subversion/libsvn_client/cat.c
===================================================================
--- subversion/libsvn_client/cat.c (revision 7111)
+++ subversion/libsvn_client/cat.c (working copy)
@@ -29,11 +29,49 @@
 #include "svn_io.h"
 #include "svn_time.h"
 #include "svn_path.h"
+#include "svn_wc.h"
 #include "client.h"

 ^L
 /*** Code. ***/

+/* Open the text base of local file PATH as a read-only STREAM. */
+static svn_error_t *
+open_text_base_RO (svn_stream_t **stream, const char *path, apr_pool_t *pool)
+{
+ const char *text_base_path;
+ apr_file_t *text_base_file;
+ SVN_ERR (svn_wc_get_pristine_copy_path (path,
+ &text_base_path, pool));
+ if (! text_base_path)
+ return svn_error_createf (SVN_ERR_ILLEGAL_TARGET, NULL,
+ "file '%s' has no text-base.",
+ path);
+ /* ### Do we need a lock before reading the text base? */
+ SVN_ERR (svn_io_file_open (&text_base_file, text_base_path,
+ APR_READ, APR_OS_DEFAULT, pool));
+ *stream = svn_stream_from_aprfile (text_base_file, pool);
+ return SVN_NO_ERROR;
+}
+
+/* Copy stream IN to stream OUT. */
+#define STREAM_COPY_BUF_SIZE 100
+static svn_error_t *
+copy_stream (svn_stream_t *out, svn_stream_t *in)
+{
+ char buf[STREAM_COPY_BUF_SIZE];
+ apr_size_t len = STREAM_COPY_BUF_SIZE;
+
+ while (len == STREAM_COPY_BUF_SIZE)
+ {
+ /* At EOF there will be a short read (0 or more bytes) which will lead
+ to a write of the same length and then a clean exit. */
+ SVN_ERR (svn_stream_read (in, buf, &len));
+ SVN_ERR (svn_stream_write (out, buf, &len));
+ }
+ return SVN_NO_ERROR;
+}
+
 svn_error_t *
 svn_client_cat (svn_stream_t *out,
                 const char *path_or_url,
@@ -51,13 +89,23 @@
   apr_hash_t *props;
   const char *auth_dir;
   const char *url;
+ svn_boolean_t from_repos;
+ svn_wc_adm_access_t *adm_access;

+ /* Decide whether we will access the repository or just get the BASE
+ revision from the WC. */
+ from_repos = ((revision->kind != svn_opt_revision_base)
+ || svn_path_is_url (path_or_url));
+
+ /* URL is wanted for keyword expansion as well as for repository access. */
   SVN_ERR (svn_client_url_from_path (&url, path_or_url, pool));
+
+ if (from_repos)
+ {
   if (! url)
     return svn_error_createf (SVN_ERR_ENTRY_MISSING_URL, NULL,
                               "'%s' has no URL", path_or_url);

-
   /* Get the RA library that handles URL. */
   SVN_ERR (svn_ra_init_ra_libs (&ra_baton, pool));
   SVN_ERR (svn_ra_get_ra_library (&ra_lib, ra_baton, url, pool));
@@ -85,6 +133,27 @@
   /* Grab some properties we need to know in order to figure out if anything
      special needs to be done with this file. */
   SVN_ERR (ra_lib->get_file (session, "", rev, NULL, NULL, &props, pool));
+ }
+ else
+ {
+ /* Access the local text base */
+ const char *anchor, *target;
+ svn_node_kind_t kind;
+
+ SVN_ERR (svn_wc_get_actual_target (path_or_url, &anchor, &target, pool));
+ SVN_ERR (svn_io_check_path (path_or_url, &kind, pool));
+ SVN_ERR (svn_wc_adm_open (&adm_access, NULL, anchor, FALSE,
+ FALSE, pool));
+
+ if (kind == svn_node_dir)
+ return svn_error_createf(SVN_ERR_CLIENT_IS_DIRECTORY, NULL,
+ "Path \"%s\" refers to a directory",
+ path_or_url);
+
+ /* Grab some properties we need to know in order to figure out if
+ anything special needs to be done with this file. */
+ SVN_ERR (svn_wc_prop_list (&props, path_or_url, adm_access, pool));
+ }

   mime_type = apr_hash_get (props, SVN_PROP_MIME_TYPE, APR_HASH_KEY_STRING);
   eol_style = apr_hash_get (props, SVN_PROP_EOL_STYLE, APR_HASH_KEY_STRING);
@@ -94,27 +163,41 @@
       || (! eol_style && ! keywords))
     {
       /* Either it's a binary file, or it's a text file with no special eol
- style. */
+ style. Just display it. */
+ if (from_repos)
       SVN_ERR (ra_lib->get_file (session, "", rev, out, NULL, NULL, pool));
+ else
+ {
+ svn_stream_t *pristine_stream;
+ SVN_ERR (open_text_base_RO (&pristine_stream, path_or_url, pool));
+ SVN_ERR (copy_stream (out, pristine_stream));
+ SVN_ERR (svn_stream_close (pristine_stream));
+ }
     }
   else
     {
+ /* Translate and display the file. */
       svn_subst_keywords_t kw = { 0 };
       svn_subst_eol_style_t style;
- const char *tmp_filename;
- svn_stream_t *tmp_stream;
+ svn_stream_t *pristine_stream;
+ const char *eol = NULL;
+
+ if (from_repos)
+ {
+ /* Make a temporary copy of the specified revision and open it
+ as "pristine_stream". */
       apr_file_t *tmp_file;
+ const char *tmp_filename;
       apr_status_t apr_err;
       apr_off_t off = 0;
- const char *eol = NULL;

       /* grab a temporary file to write the target to. */
- SVN_ERR (svn_io_open_unique_file (&tmp_file, &tmp_filename, "", ".tmp",
- TRUE, pool));
+ SVN_ERR (svn_io_open_unique_file (&tmp_file, &tmp_filename,
+ "", ".tmp", TRUE, pool));

- tmp_stream = svn_stream_from_aprfile (tmp_file, pool);
+ pristine_stream = svn_stream_from_aprfile (tmp_file, pool);

- SVN_ERR (ra_lib->get_file (session, "", rev, tmp_stream,
+ SVN_ERR (ra_lib->get_file (session, "", rev, pristine_stream,
                                  NULL, NULL, pool));

       /* rewind our stream. */
@@ -122,38 +205,62 @@
       if (apr_err)
         return svn_error_createf (apr_err, NULL, "seek failed on '%s'.",
                                   tmp_filename);
+ }
+ else
+ {
+ /* Open the text base as "pristine_stream". */
+ SVN_ERR (open_text_base_RO (&pristine_stream, path_or_url, pool));
+ }

       if (eol_style)
         svn_subst_eol_style_from_value (&style, &eol, eol_style->data);

       if (keywords)
         {
- svn_string_t *cmt_rev, *cmt_date, *cmt_author;
+ const char *where, *who;
           apr_time_t when = 0;

+ /* get entry props */
+ if (from_repos)
+ {
+ svn_string_t *cmt_rev, *cmt_date, *cmt_author;
               cmt_rev = apr_hash_get (props, SVN_PROP_ENTRY_COMMITTED_REV,
                                       APR_HASH_KEY_STRING);
               cmt_date = apr_hash_get (props, SVN_PROP_ENTRY_COMMITTED_DATE,
                                        APR_HASH_KEY_STRING);
               cmt_author = apr_hash_get (props, SVN_PROP_ENTRY_LAST_AUTHOR,
                                          APR_HASH_KEY_STRING);
+ where = cmt_rev->data;
               if (cmt_date)
                 SVN_ERR (svn_time_from_cstring (&when, cmt_date->data, pool));
+ who = cmt_author ? cmt_author->data : NULL;
+ }
+ else
+ {
+ const svn_wc_entry_t *entry;
+ svn_wc_entry (&entry, path_or_url, adm_access, TRUE, pool);
+ where = apr_psprintf (pool, "%"SVN_REVNUM_T_FMT, entry->cmt_rev);
+ when = entry->cmt_date;
+ who = entry->cmt_author;
+ }

           SVN_ERR (svn_subst_build_keywords
                    (&kw, keywords->data,
- cmt_rev->data,
+ where,
                     url,
                     when,
- cmt_author ? cmt_author->data : NULL,
+ who,
                     pool));
         }

- SVN_ERR (svn_subst_translate_stream (tmp_stream, out, eol, FALSE, &kw,
- TRUE));
+ SVN_ERR (svn_subst_translate_stream (pristine_stream, out,
+ eol, FALSE, &kw, TRUE));

- SVN_ERR (svn_stream_close (tmp_stream));
+ SVN_ERR (svn_stream_close (pristine_stream));
     }

+ if (! from_repos)
+ SVN_ERR (svn_wc_adm_close (adm_access));
+
   return SVN_NO_ERROR;
 }

Enable "svn cat -rBASE" to work without contacting the repository
(issue 1361).

* subversion/libsvn_client/cat.c
  (open_text_base_RO) New function.
  (copy_stream) New function.
  (svn_client_cat) If the BASE revision is requested, and a path is given
    rather than a URL, then get the file and all necessary properties etc.
    from the WC administrative area instead of from the repository.

Index: subversion/libsvn_client/cat.c
===================================================================
--- subversion/libsvn_client/cat.c (revision 7111)
+++ subversion/libsvn_client/cat.c (working copy)
@@ -29,11 +29,49 @@
 #include "svn_io.h"
 #include "svn_time.h"
 #include "svn_path.h"
+#include "svn_wc.h"
 #include "client.h"
 
 
 /*** Code. ***/
 
+/* Open the text base of local file PATH as a read-only STREAM. */
+static svn_error_t *
+open_text_base_RO (svn_stream_t **stream, const char *path, apr_pool_t *pool)
+{
+ const char *text_base_path;
+ apr_file_t *text_base_file;
+ SVN_ERR (svn_wc_get_pristine_copy_path (path,
+ &text_base_path, pool));
+ if (! text_base_path)
+ return svn_error_createf (SVN_ERR_ILLEGAL_TARGET, NULL,
+ "file '%s' has no text-base.",
+ path);
+ /* ### Do we need a lock before reading the text base? */
+ SVN_ERR (svn_io_file_open (&text_base_file, text_base_path,
+ APR_READ, APR_OS_DEFAULT, pool));
+ *stream = svn_stream_from_aprfile (text_base_file, pool);
+ return SVN_NO_ERROR;
+}
+
+/* Copy stream IN to stream OUT. */
+#define STREAM_COPY_BUF_SIZE 100
+static svn_error_t *
+copy_stream (svn_stream_t *out, svn_stream_t *in)
+{
+ char buf[STREAM_COPY_BUF_SIZE];
+ apr_size_t len = STREAM_COPY_BUF_SIZE;
+
+ while (len == STREAM_COPY_BUF_SIZE)
+ {
+ /* At EOF there will be a short read (0 or more bytes) which will lead
+ to a write of the same length and then a clean exit. */
+ SVN_ERR (svn_stream_read (in, buf, &len));
+ SVN_ERR (svn_stream_write (out, buf, &len));
+ }
+ return SVN_NO_ERROR;
+}
+
 svn_error_t *
 svn_client_cat (svn_stream_t *out,
                 const char *path_or_url,
@@ -51,40 +89,71 @@
   apr_hash_t *props;
   const char *auth_dir;
   const char *url;
+ svn_boolean_t from_repos;
+ svn_wc_adm_access_t *adm_access;
+
+ /* Decide whether we will access the repository or just get the BASE
+ revision from the WC. */
+ from_repos = ((revision->kind != svn_opt_revision_base)
+ || svn_path_is_url (path_or_url));
 
+ /* URL is wanted for keyword expansion as well as for repository access. */
   SVN_ERR (svn_client_url_from_path (&url, path_or_url, pool));
- if (! url)
- return svn_error_createf (SVN_ERR_ENTRY_MISSING_URL, NULL,
- "'%s' has no URL", path_or_url);
-
-
- /* Get the RA library that handles URL. */
- SVN_ERR (svn_ra_init_ra_libs (&ra_baton, pool));
- SVN_ERR (svn_ra_get_ra_library (&ra_lib, ra_baton, url, pool));
-
- SVN_ERR (svn_client__dir_if_wc (&auth_dir, "", pool));
-
- /* Open a repository session to the URL. */
- SVN_ERR (svn_client__open_ra_session (&session, ra_lib, url, auth_dir, NULL,
- NULL, FALSE, FALSE,
- ctx, pool));
-
- /* Resolve REVISION into a real revnum. */
- SVN_ERR (svn_client__get_revision_number (&rev, ra_lib, session,
- revision, path_or_url, pool));
- if (! SVN_IS_VALID_REVNUM (rev))
- SVN_ERR (ra_lib->get_latest_revnum (session, &rev, pool));
-
- /* Decide if the URL is a file or directory. */
- SVN_ERR (ra_lib->check_path (&url_kind, session, "", rev, pool));
-
- if (url_kind == svn_node_dir)
- return svn_error_createf(SVN_ERR_CLIENT_IS_DIRECTORY, NULL,
- "URL \"%s\" refers to directory", url);
-
- /* Grab some properties we need to know in order to figure out if anything
- special needs to be done with this file. */
- SVN_ERR (ra_lib->get_file (session, "", rev, NULL, NULL, &props, pool));
+
+ if (from_repos)
+ {
+ if (! url)
+ return svn_error_createf (SVN_ERR_ENTRY_MISSING_URL, NULL,
+ "'%s' has no URL", path_or_url);
+
+ /* Get the RA library that handles URL. */
+ SVN_ERR (svn_ra_init_ra_libs (&ra_baton, pool));
+ SVN_ERR (svn_ra_get_ra_library (&ra_lib, ra_baton, url, pool));
+
+ SVN_ERR (svn_client__dir_if_wc (&auth_dir, "", pool));
+
+ /* Open a repository session to the URL. */
+ SVN_ERR (svn_client__open_ra_session (&session, ra_lib, url, auth_dir,
+ NULL, NULL, FALSE, FALSE,
+ ctx, pool));
+
+ /* Resolve REVISION into a real revnum. */
+ SVN_ERR (svn_client__get_revision_number (&rev, ra_lib, session,
+ revision, path_or_url, pool));
+ if (! SVN_IS_VALID_REVNUM (rev))
+ SVN_ERR (ra_lib->get_latest_revnum (session, &rev, pool));
+
+ /* Decide if the URL is a file or directory. */
+ SVN_ERR (ra_lib->check_path (&url_kind, session, "", rev, pool));
+
+ if (url_kind == svn_node_dir)
+ return svn_error_createf(SVN_ERR_CLIENT_IS_DIRECTORY, NULL,
+ "URL \"%s\" refers to directory", url);
+
+ /* Grab some properties we need to know in order to figure out if
+ anything special needs to be done with this file. */
+ SVN_ERR (ra_lib->get_file (session, "", rev, NULL, NULL, &props, pool));
+ }
+ else
+ {
+ /* Access the local text base */
+ const char *anchor, *target;
+ svn_node_kind_t kind;
+
+ SVN_ERR (svn_wc_get_actual_target (path_or_url, &anchor, &target, pool));
+ SVN_ERR (svn_io_check_path (path_or_url, &kind, pool));
+ SVN_ERR (svn_wc_adm_open (&adm_access, NULL, anchor, FALSE,
+ FALSE, pool));
+
+ if (kind == svn_node_dir)
+ return svn_error_createf(SVN_ERR_CLIENT_IS_DIRECTORY, NULL,
+ "Path \"%s\" refers to a directory",
+ path_or_url);
+
+ /* Grab some properties we need to know in order to figure out if
+ anything special needs to be done with this file. */
+ SVN_ERR (svn_wc_prop_list (&props, path_or_url, adm_access, pool));
+ }
 
   mime_type = apr_hash_get (props, SVN_PROP_MIME_TYPE, APR_HASH_KEY_STRING);
   eol_style = apr_hash_get (props, SVN_PROP_EOL_STYLE, APR_HASH_KEY_STRING);
@@ -94,66 +163,104 @@
       || (! eol_style && ! keywords))
     {
       /* Either it's a binary file, or it's a text file with no special eol
- style. */
- SVN_ERR (ra_lib->get_file (session, "", rev, out, NULL, NULL, pool));
+ style. Just display it. */
+ if (from_repos)
+ SVN_ERR (ra_lib->get_file (session, "", rev, out, NULL, NULL, pool));
+ else
+ {
+ svn_stream_t *pristine_stream;
+ SVN_ERR (open_text_base_RO (&pristine_stream, path_or_url, pool));
+ SVN_ERR (copy_stream (out, pristine_stream));
+ SVN_ERR (svn_stream_close (pristine_stream));
+ }
     }
   else
     {
+ /* Translate and display the file. */
       svn_subst_keywords_t kw = { 0 };
       svn_subst_eol_style_t style;
- const char *tmp_filename;
- svn_stream_t *tmp_stream;
- apr_file_t *tmp_file;
- apr_status_t apr_err;
- apr_off_t off = 0;
+ svn_stream_t *pristine_stream;
       const char *eol = NULL;
 
- /* grab a temporary file to write the target to. */
- SVN_ERR (svn_io_open_unique_file (&tmp_file, &tmp_filename, "", ".tmp",
- TRUE, pool));
-
- tmp_stream = svn_stream_from_aprfile (tmp_file, pool);
-
- SVN_ERR (ra_lib->get_file (session, "", rev, tmp_stream,
- NULL, NULL, pool));
-
- /* rewind our stream. */
- apr_err = apr_file_seek (tmp_file, APR_SET, &off);
- if (apr_err)
- return svn_error_createf (apr_err, NULL, "seek failed on '%s'.",
- tmp_filename);
+ if (from_repos)
+ {
+ /* Make a temporary copy of the specified revision and open it
+ as "pristine_stream". */
+ apr_file_t *tmp_file;
+ const char *tmp_filename;
+ apr_status_t apr_err;
+ apr_off_t off = 0;
+
+ /* grab a temporary file to write the target to. */
+ SVN_ERR (svn_io_open_unique_file (&tmp_file, &tmp_filename,
+ "", ".tmp", TRUE, pool));
+
+ pristine_stream = svn_stream_from_aprfile (tmp_file, pool);
+
+ SVN_ERR (ra_lib->get_file (session, "", rev, pristine_stream,
+ NULL, NULL, pool));
+
+ /* rewind our stream. */
+ apr_err = apr_file_seek (tmp_file, APR_SET, &off);
+ if (apr_err)
+ return svn_error_createf (apr_err, NULL, "seek failed on '%s'.",
+ tmp_filename);
+ }
+ else
+ {
+ /* Open the text base as "pristine_stream". */
+ SVN_ERR (open_text_base_RO (&pristine_stream, path_or_url, pool));
+ }
 
       if (eol_style)
         svn_subst_eol_style_from_value (&style, &eol, eol_style->data);
 
       if (keywords)
         {
- svn_string_t *cmt_rev, *cmt_date, *cmt_author;
+ const char *where, *who;
           apr_time_t when = 0;
 
- cmt_rev = apr_hash_get (props, SVN_PROP_ENTRY_COMMITTED_REV,
- APR_HASH_KEY_STRING);
- cmt_date = apr_hash_get (props, SVN_PROP_ENTRY_COMMITTED_DATE,
- APR_HASH_KEY_STRING);
- cmt_author = apr_hash_get (props, SVN_PROP_ENTRY_LAST_AUTHOR,
- APR_HASH_KEY_STRING);
- if (cmt_date)
- SVN_ERR (svn_time_from_cstring (&when, cmt_date->data, pool));
+ /* get entry props */
+ if (from_repos)
+ {
+ svn_string_t *cmt_rev, *cmt_date, *cmt_author;
+ cmt_rev = apr_hash_get (props, SVN_PROP_ENTRY_COMMITTED_REV,
+ APR_HASH_KEY_STRING);
+ cmt_date = apr_hash_get (props, SVN_PROP_ENTRY_COMMITTED_DATE,
+ APR_HASH_KEY_STRING);
+ cmt_author = apr_hash_get (props, SVN_PROP_ENTRY_LAST_AUTHOR,
+ APR_HASH_KEY_STRING);
+ where = cmt_rev->data;
+ if (cmt_date)
+ SVN_ERR (svn_time_from_cstring (&when, cmt_date->data, pool));
+ who = cmt_author ? cmt_author->data : NULL;
+ }
+ else
+ {
+ const svn_wc_entry_t *entry;
+ svn_wc_entry (&entry, path_or_url, adm_access, TRUE, pool);
+ where = apr_psprintf (pool, "%"SVN_REVNUM_T_FMT, entry->cmt_rev);
+ when = entry->cmt_date;
+ who = entry->cmt_author;
+ }
 
           SVN_ERR (svn_subst_build_keywords
                    (&kw, keywords->data,
- cmt_rev->data,
+ where,
                     url,
                     when,
- cmt_author ? cmt_author->data : NULL,
+ who,
                     pool));
         }
 
- SVN_ERR (svn_subst_translate_stream (tmp_stream, out, eol, FALSE, &kw,
- TRUE));
+ SVN_ERR (svn_subst_translate_stream (pristine_stream, out,
+ eol, FALSE, &kw, TRUE));
 
- SVN_ERR (svn_stream_close (tmp_stream));
+ SVN_ERR (svn_stream_close (pristine_stream));
     }
 
+ if (! from_repos)
+ SVN_ERR (svn_wc_adm_close (adm_access));
+
   return SVN_NO_ERROR;
 }

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org
Received on Mon Sep 22 15:34:18 2003

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.