Added a function to the RA layer to efficiently retrieve all revisions
of a file in a range.  Implemented this function in libsvn_ra_local
and used it to optimize the blame command.

* include/svn_io.h (svn_io_file_name_get): New function.


* include/svn_ra.h (svn_ra_file_rev_handler_t): New typedef.
  (svn_ra_plugin_t): Added get_file_revs member.

* libsvn_subr/io.c (file_name_get): Renamed to svn_io_file_name_get and made
  non-static.  Caller updated.

* libsvn_ra_local/ra_plugin.c (svn_ra_local__get_file_revs): New function.
  (ra_local_plugin): Added svn_ra_local__get_file_revs.


* libsvn_client/blame.c (diff_baton): Removed.
  (file_rev_baton): New struct.
  (blame_create, blame_destroy, blame_delete_range, blame_insert_range,
  output_diff_modified): Take file_rev_baton instead of diff_baton.
  (file_rev_handler): New function.
  (old_blame): "New" function, which really is the old svn_client_blame.
  Use file_rev_baton instead of diff_baton.  Marked error messages for
  translation while here.
  (svn_client_blame): Use new RA get_file_revs call if available in the
  RA implementation used.  Else fall back on the old blame code.
  This is temporary until we've updated all RA implementations.  Then
  we'll have to fall back if the server doesn't support the new request.

Index: subversion/include/svn_io.h
===================================================================
--- subversion/include/svn_io.h	(revision 9908)
+++ subversion/include/svn_io.h	(arbetskopia)
@@ -676,6 +676,16 @@
                   apr_pool_t *pool);
 
 
+/**
+ * @since New in 1.1.
+ *
+ * Wrapper for @c apr_file_name_get, which see.  @a fname will be set
+ * to an UTF-8-encoded string allocated in @a pool. */
+svn_error_t *
+svn_io_file_name_get (const char **fname,
+                      apr_file_t *file,
+                      apr_pool_t *pool);
+
 /** Wrapper for @c apr_file_close(), which see. */
 svn_error_t *
 svn_io_file_close (apr_file_t *file, apr_pool_t *pool);
Index: subversion/include/svn_ra.h
===================================================================
--- subversion/include/svn_ra.h	(revision 9908)
+++ subversion/include/svn_ra.h	(arbetskopia)
@@ -109,6 +109,22 @@
        (void *session_baton,
         svn_revnum_t *latest_revnum);
 
+/** A callback function type for use in @c get_file_revs.
+ * @a baton is provided by the caller, @a path is the pathname of the file
+ * in revision @a rev and @a rev_props are the revision properties.
+ * @a contents_filename is the name of a temporary file containing the contents
+ * of the file in this revision and @a props are the file properties.
+ * @a pool may be used for temporary allocations, but you can't rely
+ * on object allocated to live outside of this particular call. */
+typedef svn_error_t *(*svn_ra_file_rev_handler_t)
+       (void *baton,
+        const char *path,
+        svn_revnum_t rev,
+        apr_hash_t *rev_props,
+        const char *contents_filename,
+        apr_hash_t *props,
+        apr_pool_t *pool);
+
 
 /** The update Reporter.
  *
@@ -711,6 +727,30 @@
                                   const char **url,
                                   apr_pool_t *pool);
 
+  /** Retrieve a subset of the interesting revisions for a file @a path
+   * as seen in revision @a end.  INvoke @a handler with @a handler_baton
+   * as its first argument for each suhc revision.  @a sesson_baton is
+   * an open RA session.  @a pool is used for all allocations.
+   *
+   * If there is an interesting revision of the file that is less than or
+   * equal to start, the iteration will start at that revision.  Else, the
+   * iteration will start at the first revision of the file in the repository,
+   * whic has to be less than or equal to end.  Note that if the function
+   * succeeds, @a handler will have been called at least once.
+   *
+   * During the second and following calls to @a handler, the temporary
+   * file from the preceding call is still available.  Furthermore, the file
+   * for the last call will be allocated in @a pool, so it is guaranteed not to
+   * be deleted until @a pool is cleared or destroyed. */
+
+  svn_error_t *(*get_file_revs) (void *session_baton,
+                                 const char *path,
+                                 svn_revnum_t start,
+                                 svn_revnum_t end,
+                                 svn_ra_file_rev_handler_t handler,
+                                 void *handler_baton,
+                                 apr_pool_t *pool);
+
 } svn_ra_plugin_t;
 
 
Index: subversion/libsvn_subr/io.c
===================================================================
--- subversion/libsvn_subr/io.c	(revision 9908)
+++ subversion/libsvn_subr/io.c	(arbetskopia)
@@ -1046,9 +1046,10 @@
 }
 
 
-/* Get the name of FILE, or NULL if FILE is an unnamed stream. */
-static svn_error_t *
-file_name_get (const char **fname_utf8, apr_file_t *file, apr_pool_t *pool)
+svn_error_t *
+svn_io_file_name_get (const char **fname_utf8,
+                      apr_file_t *file,
+                      apr_pool_t *pool)
 {
   apr_status_t apr_err;
   const char *fname;
@@ -1721,7 +1722,7 @@
   if (! status)
     return SVN_NO_ERROR;
 
-  err = file_name_get (&name, file, pool);
+  err = svn_io_file_name_get (&name, file, pool);
   name = (! err && name) ? apr_psprintf (pool, "file '%s'", name) : "stream";
   svn_error_clear (err);
 
Index: subversion/libsvn_ra_local/ra_plugin.c
===================================================================
--- subversion/libsvn_ra_local/ra_plugin.c	(revision 9908)
+++ subversion/libsvn_ra_local/ra_plugin.c	(arbetskopia)
@@ -23,10 +23,13 @@
 #include "svn_repos.h"
 #include "svn_pools.h"
 #include "svn_time.h"
+#include "svn_private_config.h"
 
 #define APR_WANT_STRFUNC
 #include <apr_want.h>
 
+#include <assert.h>
+
 /*----------------------------------------------------------------*/
 
 
@@ -840,8 +843,129 @@
   return SVN_NO_ERROR;
 }
 
+static svn_error_t *
+svn_ra_local__get_file_revs (void *session_baton,
+                             const char *path,
+                             svn_revnum_t start,
+                             svn_revnum_t end,
+                             svn_ra_file_rev_handler_t handler,
+                             void *handler_baton,
+                             apr_pool_t *pool)
+{
+  apr_pool_t *iter_pool;
+  apr_pool_t *last_pool;
+  svn_ra_local__session_baton_t *sbaton = session_baton;
+  const char *abs_path = sbaton->fs_path;
+  svn_fs_history_t *history;
+  apr_array_header_t *revnums = apr_array_make (pool, 0,
+                                                sizeof (svn_revnum_t));
+  apr_array_header_t *paths = apr_array_make (pool, 0, sizeof (char *));
+  svn_fs_root_t *root;
+  int i;
 
+  /* Concatenate paths */
+  if (path)
+    abs_path = svn_path_join (abs_path, path, pool);
 
+  /* We switch betwwen two pools while looping, since we need information from
+     the last iteration to be available. */
+  iter_pool = svn_pool_create (pool);
+  last_pool = svn_pool_create (pool);
+
+  /* Open revision root for path@end. */
+  SVN_ERR (svn_fs_revision_root (&root, sbaton->fs, end, pool));
+
+  /* Open a history object. */
+  SVN_ERR (svn_fs_node_history (&history, root, abs_path, last_pool));
+  
+  /* Get the revisions we are interested in. */
+  while (1)
+    {
+      const char* rev_path;
+      svn_revnum_t rev;
+      apr_pool_t *tmp_pool;
+
+      svn_pool_clear (iter_pool);
+
+      SVN_ERR (svn_fs_history_prev (&history, history, TRUE, iter_pool));
+      if (!history)
+        break;
+      SVN_ERR (svn_fs_history_location (&rev_path, &rev, history, iter_pool));
+      *(svn_revnum_t*) apr_array_push (revnums) = rev;
+      *(char **) apr_array_push (paths) = apr_pstrdup (pool, rev_path);
+      if (rev <= start)
+        break;
+
+      /* Swap pools. */
+      tmp_pool = iter_pool;
+      iter_pool = last_pool;
+      last_pool = tmp_pool;
+    }
+
+  /* We must have at least one revision to get. */
+  assert (revnums->nelts > 0);
+
+  /* Walk through the revisions in chronological order. */
+  for (i = revnums->nelts; i > 0; --i)
+    {
+      svn_revnum_t rev = APR_ARRAY_IDX (revnums, i - 1, svn_revnum_t);
+      const char *rev_path = APR_ARRAY_IDX (paths, i - 1, const char *);
+      apr_hash_t *rev_props;
+      apr_hash_t *file_props;
+      svn_stream_t *contents;
+      apr_file_t *file;
+      const char *filename;
+      svn_stream_t *file_stream;
+      apr_status_t apr_err;
+      apr_pool_t *tmp_pool;
+
+      svn_pool_clear (iter_pool);
+
+      /* Get the revision properties. */
+      SVN_ERR (svn_fs_revision_proplist (&rev_props, sbaton->fs,
+                                         rev, iter_pool));
+
+      /* Open the revision root. */
+      SVN_ERR (svn_fs_revision_root (&root, sbaton->fs, rev, iter_pool));
+
+      /* Get the contents of the file and put them in a temporary file. */
+      SVN_ERR (svn_fs_file_contents (&contents, root, rev_path, iter_pool));
+      /* If this is the last iteration, create the file in the main pool, so
+         it is available after we return. */
+      SVN_ERR (sbaton->callbacks->open_tmp_file (&file, sbaton->callback_baton,
+                                                 i > 1 ? iter_pool : pool));
+      SVN_ERR (svn_io_file_name_get (&filename, file, iter_pool));
+      file_stream = svn_stream_from_aprfile (file, iter_pool);
+      SVN_ERR (svn_stream_copy (contents, file_stream, iter_pool));
+      SVN_ERR (svn_stream_close (contents));
+      SVN_ERR (svn_stream_close (file_stream));
+
+      /* We can't close the file, since then it will be deleted..  Flush
+         it, so that the handler can open it and see the correct contents. */
+      apr_err = apr_file_flush (file);
+      if (apr_err)
+        return svn_error_wrap_apr
+          (apr_err, _("Can't flush temporary file '%s'"), filename);
+
+      /* Get the file's properties for this revision. */
+      SVN_ERR (svn_fs_node_proplist (&file_props, root, rev_path, iter_pool));
+
+      /* We have all we need, give to the handler. */
+      SVN_ERR (handler (handler_baton, rev_path, rev, rev_props,
+               filename, file_props, iter_pool));
+
+      /* Swap the pools. */
+      tmp_pool = iter_pool;
+      iter_pool = last_pool;
+      last_pool = tmp_pool;
+    }
+
+  svn_pool_destroy (last_pool);
+  svn_pool_destroy (iter_pool);
+
+  return SVN_NO_ERROR;
+}
+
 /*----------------------------------------------------------------*/
 
 /** The ra_plugin **/
@@ -866,7 +990,8 @@
   svn_ra_local__get_log,
   svn_ra_local__do_check_path,
   svn_ra_local__get_uuid,
-  svn_ra_local__get_repos_root
+  svn_ra_local__get_repos_root,
+  svn_ra_local__get_file_revs
 };
 
 
Index: subversion/libsvn_client/blame.c
===================================================================
--- subversion/libsvn_client/blame.c	(revision 9908)
+++ subversion/libsvn_client/blame.c	(arbetskopia)
@@ -29,12 +29,15 @@
 #include "svn_path.h"
 #include "svn_sorts.h"
 
+#include <assert.h>
+
 /* The metadata associated with a particular revision. */
 struct rev
 {
   svn_revnum_t revision; /* the revision number */
   const char *author;    /* the author of the revision */
   const char *date;      /* the date of the revision */
+  /* ### Only used by the old code. */
   const char *path;      /* the absolute repository path */
   struct rev *next;      /* the next revision */
 };
@@ -47,19 +50,23 @@
   struct blame *next; /* the next chunk */
 };
 
-/* The baton used for svn_diff operations */
-struct diff_baton
-{
-  struct rev *rev;     /* the rev for which blame is being assigned */
+/* The baton used for a file revision. Also used for the diff output
+   routine. */
+struct file_rev_baton {
+  svn_revnum_t start_rev, end_rev;
+  svn_client_ctx_t *ctx;
+  const char *last_filename;
+  struct rev *rev;     /* the rev for which blame is being assigned
+                          during a diff */
   struct blame *blame; /* linked list of blame chunks */
   struct blame *avail; /* linked list of free blame chunks */
-  apr_pool_t *pool;
+  apr_pool_t *pool;     /* Lives between calls */
 };
 
 /* Create a blame chunk associated with REV for a change starting
    at token START. */
 static struct blame *
-blame_create (struct diff_baton *baton, struct rev *rev, apr_off_t start)
+blame_create (struct file_rev_baton *baton, struct rev *rev, apr_off_t start)
 {
   struct blame *blame;
   if (baton->avail)
@@ -77,7 +84,7 @@
 
 /* Destroy a blame chunk. */
 static void
-blame_destroy (struct diff_baton *baton, struct blame *blame)
+blame_destroy (struct file_rev_baton *baton, struct blame *blame)
 {
   blame->next = baton->avail;
   baton->avail = blame;
@@ -113,7 +120,7 @@
 /* Delete the blame associated with the region from token START to
    START + LENGTH */
 static svn_error_t *
-blame_delete_range (struct diff_baton *db, apr_off_t start, apr_off_t length)
+blame_delete_range (struct file_rev_baton *db, apr_off_t start, apr_off_t length)
 {
   struct blame *first = blame_find (db->blame, start);
   struct blame *last = blame_find (db->blame, start + length);
@@ -153,7 +160,7 @@
 /* Insert a chunk of blame associated with DB->REV starting
    at token START and continuing for LENGTH tokens */
 static svn_error_t *
-blame_insert_range (struct diff_baton *db, apr_off_t start, apr_off_t length)
+blame_insert_range (struct file_rev_baton *db, apr_off_t start, apr_off_t length)
 {
   struct blame *head = db->blame;
   struct blame *point = blame_find (head, start);
@@ -196,7 +203,7 @@
                       apr_off_t latest_start,
                       apr_off_t latest_length)
 {
-  struct diff_baton *db = baton;
+  struct file_rev_baton *db = baton;
 
   if (original_length)
     SVN_ERR (blame_delete_range (db, modified_start, original_length));
@@ -207,6 +214,7 @@
   return SVN_NO_ERROR;
 }
 
+/* ### Used only by the old code. */
 /* The baton used for RA->get_log */
 struct log_message_baton {
   const char *path;        /* The path to be processed */
@@ -257,6 +265,96 @@
   return SVN_NO_ERROR;
 }
 
+static svn_error_t *
+file_rev_handler (void *baton, const char *path, svn_revnum_t revnum,
+                  apr_hash_t *rev_props,
+                  const char *filename, apr_hash_t *props,
+                  apr_pool_t *pool)
+{
+  struct file_rev_baton *frb = baton;
+  struct rev *rev;
+  svn_string_t *mimetype;
+
+  /* If this file has a non-textual mime-type, bail out. */
+  if (props && 
+      ((mimetype = apr_hash_get (props, SVN_PROP_MIME_TYPE, 
+                                 sizeof (SVN_PROP_MIME_TYPE) - 1))))
+    {
+      if (svn_mime_type_is_binary (mimetype->data))
+        return svn_error_createf 
+          (SVN_ERR_CLIENT_IS_BINARY_FILE, 0,
+           _("Cannot calculate blame information for binary file '%s'"),
+           path);
+    }
+
+  if (frb->ctx->notify_func)
+    frb->ctx->notify_func (frb->ctx->notify_baton,
+                           path,
+                           svn_wc_notify_blame_revision,
+                           svn_node_none,
+                           NULL,
+                           svn_wc_notify_state_inapplicable,
+                           svn_wc_notify_state_inapplicable,
+                           revnum);
+
+  if (frb->ctx->cancel_func)
+    SVN_ERR (frb->ctx->cancel_func (frb->ctx->cancel_baton));
+
+  if (revnum < frb->start_rev)
+    {
+      /* We shouldn't get more than one revision before start. */
+      assert (frb->last_filename == NULL);
+
+      /* The file existed before start_rev; generate no blame info for
+         lines from this revision (or before). */
+      rev = apr_palloc (frb->pool, sizeof (*rev));
+      rev->revision = SVN_INVALID_REVNUM;
+      rev->author = NULL;
+      rev->date = NULL;
+      frb->blame = blame_create (frb, rev, 0);
+    }
+  else
+    {
+      svn_string_t *str;
+      assert (revnum <= frb->end_rev);
+
+      /* Create a rev structure we the revision properties. */
+      rev = apr_palloc (frb->pool, sizeof (*rev));
+      rev->revision = revnum;
+
+      if ((str = apr_hash_get (rev_props, SVN_PROP_REVISION_AUTHOR,
+                               sizeof (SVN_PROP_REVISION_AUTHOR) - 1)))
+        rev->author = apr_pstrdup (frb->pool, str->data);
+      else
+        rev->author = NULL;
+
+      if ((str = apr_hash_get (rev_props, SVN_PROP_REVISION_DATE,
+                               sizeof (SVN_PROP_REVISION_DATE) - 1)))
+        rev->date = apr_pstrdup (frb->pool, str->data);
+
+      if (!frb->last_filename)
+        {
+          /* File must have been created after start_rev.  We create blame
+             information for all of this file. */
+          assert (!frb->blame);
+          frb->blame = blame_create (frb, rev, 0);
+        } else {
+          svn_diff_t *diff;
+
+          /* We have a previous file.  Get the diff and adjust blame info. */
+          frb->rev = rev;
+          SVN_ERR (svn_diff_file_diff (&diff, frb->last_filename, filename,
+                                       pool));
+          SVN_ERR (svn_diff_output (diff, frb, &output_fns));
+        }
+    }
+
+  /* Remember the file name so we can diff it with the next revision.
+     It is guaranteed to exist during the next call too. */
+  frb->last_filename = apr_pstrdup (frb->pool, filename);
+  return SVN_NO_ERROR;
+}
+
 static apr_status_t
 cleanup_tempfile (void *f)
 {
@@ -274,6 +372,15 @@
   return apr_err;
 }
 
+static svn_error_t *
+old_blame (const char *target,
+           const svn_opt_revision_t *start,
+           const svn_opt_revision_t *end,
+           svn_client_blame_receiver_t receiver,
+           void *receiver_baton,
+           svn_client_ctx_t *ctx,
+           apr_pool_t *pool);
+
 svn_error_t *
 svn_client_blame (const char *target,
                   const svn_opt_revision_t *start,
@@ -283,6 +390,112 @@
                   svn_client_ctx_t *ctx,
                   apr_pool_t *pool)
 {
+  struct file_rev_baton frb;
+  svn_ra_plugin_t *ra_lib; 
+  void *session;
+  const char *url;
+  svn_revnum_t start_revnum, end_revnum;
+  struct blame *walk;
+  apr_file_t *file;
+  apr_pool_t *iterpool;
+  svn_stream_t *stream;
+
+  if (start->kind == svn_opt_revision_unspecified
+      || end->kind == svn_opt_revision_unspecified)
+    return svn_error_create
+      (SVN_ERR_CLIENT_BAD_REVISION, NULL, NULL);
+
+  /* Get an RA plugin for this filesystem object. */
+  SVN_ERR (svn_client__ra_lib_from_path (&ra_lib, &session, &end_revnum,
+                                         &url, target, end,
+                                         ctx, pool));
+
+  /* ## This is temporary until all RA implementations support
+     get_file_revs. */
+  if (!ra_lib->get_file_revs)
+    /* Let the old code take over. */
+    return old_blame (target, start, end, receiver, receiver_baton, ctx,
+                      pool);
+  SVN_ERR (svn_client__get_revision_number (&start_revnum, ra_lib, session,
+                                            start, target, pool));
+
+  if (end_revnum < start_revnum)
+    return svn_error_create
+      (SVN_ERR_CLIENT_BAD_REVISION, NULL,
+       _("Start revision must precede end revision"));
+
+  /* Collect all blame information.
+     We need to ensure that we get one revision before the start_rev,
+     if available so that we can know what was actually changed in the start
+     revision. */
+
+  frb.start_rev = start_revnum;
+  frb.end_rev = end_revnum;
+  frb.ctx = ctx;
+  frb.last_filename = NULL;
+  frb.blame = NULL;
+  frb.avail = NULL;
+  frb.pool = pool;
+  SVN_ERR (ra_lib->get_file_revs (session, "",
+                                  start_revnum - (start_revnum > 0 ? 1 : 0),
+                                  end_revnum,
+                                  file_rev_handler, &frb, pool));
+
+  /* Report the blame to the caller. */
+
+  /* The callback has to have been called at least once. */
+  assert (frb.last_filename != NULL);
+
+  /* Create a pool for the iteration below. */
+  iterpool = svn_pool_create (pool);
+
+  /* Open the last file and get a stream. */
+  SVN_ERR (svn_io_file_open (&file, frb.last_filename, APR_READ,
+                             APR_OS_DEFAULT, pool));
+  stream = svn_stream_from_aprfile (file, pool);
+
+  /* Process each blame item. */
+  for (walk = frb.blame; walk; walk = walk->next)
+    {
+      apr_off_t line_no;
+      for (line_no = walk->start;
+           !walk->next || line_no < walk->next->start;
+           ++line_no)
+        {
+          svn_boolean_t eof;
+          svn_stringbuf_t *sb;
+          apr_pool_clear (iterpool);
+          SVN_ERR (svn_stream_readline (stream, &sb, "\n", &eof, iterpool));
+          if (ctx->cancel_func)
+            SVN_ERR (ctx->cancel_func (ctx->cancel_baton));
+          if (!eof || sb->len)
+            SVN_ERR (receiver (receiver_baton, line_no, walk->rev->revision,
+                               walk->rev->author, walk->rev->date,
+                               sb->data, iterpool));
+          if (eof) break;
+        }
+    }
+
+  SVN_ERR (svn_stream_close (stream));
+
+  /* We don't need the temp file any more. */
+  SVN_ERR (svn_io_file_close (file, pool));
+
+  return SVN_NO_ERROR;
+}
+
+
+/* This is used when there is no get_file_revs avaliable. */
+
+static svn_error_t *
+old_blame (const char *target,
+           const svn_opt_revision_t *start,
+           const svn_opt_revision_t *end,
+           svn_client_blame_receiver_t receiver,
+           void *receiver_baton,
+           svn_client_ctx_t *ctx,
+           apr_pool_t *pool)
+{
   const char *reposURL;
   struct log_message_baton lmb;
   apr_array_header_t *condensed_targets;
@@ -297,7 +510,7 @@
   struct rev *rev;
   apr_status_t apr_err;
   svn_node_kind_t kind;
-  struct diff_baton db;
+  struct file_rev_baton db;
   const char *last = NULL;
 
   if (start->kind == svn_opt_revision_unspecified
@@ -319,7 +532,7 @@
   if (end_revnum < start_revnum)
     return svn_error_create
       (SVN_ERR_CLIENT_BAD_REVISION, NULL,
-       "Start revision must precede end revision");
+       _("Start revision must precede end revision"));
 
   SVN_ERR (ra_lib->check_path (session, "", end_revnum, &kind, pool));
 
@@ -443,7 +656,7 @@
           if (svn_mime_type_is_binary (mimetype->data))
             return svn_error_createf 
               (SVN_ERR_CLIENT_IS_BINARY_FILE, 0,
-               "Cannot calculate blame information for binary file '%s'",
+               _("Cannot calculate blame information for binary file '%s'"),
                target);
         }
 
