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

[PATCH] operation logging

From: <kfogel_at_collab.net>
Date: 2005-06-05 20:45:32 CEST

This is a proposal and patch for operation logging.

Goal:
=====

Have the server log "high level" client operations, such as checkout,
update, switch, diff, commit, log, cat, etc.

Think of it as glorified "CVSROOT/history" functionality :-).

Design:
=======

Instead of having the server deduce operations, have the client
declare them.

Remember that, depending on the RA layer, some operations look the
same over the wire: updates, checkouts, and switches are all very
similar, for example. Diff and merge likewise. Thus, deducing the
operation by looking at the underlying RA requests can be difficult,
and in some cases impossible.

So here's how we do it instead:

   1. New 'operation_name' field inside svn_ra_session_t, and a new
      public RA function to set its value.

   2. All svn_client_XXX() functions set operation_name at critical
      points before invoking the relevant svn_ra_foo().

   3. If the operation_name is non-NULL in the svn_ra_session_t
      object, the RA layer sends the operation name to the server as
      request metadata (then it sets the operation name to NULL, so
      that one operation doesn't get logged multiple times)

   4. The server notices the string and logs it.

In this patch, Karl taught libsvn_client when to announce operations
and when not to. Ben taught ra_dav to send the operation_name in a
custom HTTP header, and have mod_dav_svn parse the string and log it
to Apache's logging system.

We haven't done ra_svn/svnserve yet, but we will if this flies.

Advantages:

   * Ensures consistent logs across RA implementations, because the
     client is declaring, rather than server deducing.

   * The door is still open for a 3rd-party logging library. If we
     give libsvn_repos a generalized logging facility, then both
     svnserve *and* mod_dav_svn can use it. Of course, mod_dav_svn
     should continue to log to Apache's native logs, and that's the
     part we implemented in this patch, because it allowed us to punt
     on the question of a generalized logging library for now.

Here's the patch. Ben Collins-Sussman and I decided to do this
skunkworks-style, because the design was so simple that the best way
to express it was just to code it :-). Don't be put off by the size
of the patch -- most of it is just the expected fallout from a few
small changes, so it should be pretty easy to follow.

The patch works, but note that for certain operations, sometimes an
empty or absent path gets logged. This is due to some mod_dav_svn
trickery and is completely fixeable. We just haven't bothered to fix
it yet, since the fix won't affect the overall design, and we wanted
to get this in front of the list as soon as it was working.

[[[
With Ben Collins-Sussman, implement operation logging for ra_dav.

Make libsvn_client tell the RA layer the operation name:

* subversion/libsvn_ra/ra_loader.h
  (struct svn_ra_session_t): New operation_name field.

* subversion/libsvn_client/client.h,
  (svn_client__update_internal, svn_client__checkout_internal): Take
  new operation_name parameter.

* subversion/libsvn_ra/ra_loader.c
  (svn_ra_set_operation_name): New function.
  (svn_ra_open): Initialize the session's operation_name to NULL.

* subversion/libsvn_client/add.c
  (mkdir_urls): Set operation name to "mkdir".

* subversion/libsvn_client/blame.c
  (svn_client_blame2): Set operation name to "blame".
  (old_blame): Set operation name to "blame", but leave a comment
  speculating that "blame-old" might be better.

* subversion/libsvn_client/cat.c
  (svn_client_cat2): Set operation name to "cat".

* subversion/libsvn_client/checkout.c
  (svn_client__checkout_internal): Take new operation_name parameter,
  pass it along to svn_client__update_internal.

* subversion/libsvn_client/commit.c
  (svn_client_import): Set the operation name to "import".
  (svn_client_commit2): Set the operation name to "commit".

* subversion/libsvn_client/copy.c
  (repos_to_repos_copy): Set operation name to "copy-repos-to-repos".
  (wc_to_repos_copy): Set operation name to "copy-wc-to-repos".
  (repos_to_wc_copy): Set operation name to "copy-repos-to-wc-dir" in
  the directory case, and "copy-repos-to-wc-file" in the file case.
  ### For other kinds of copies, the dir-vs-file distinction would ###
  ### be hard to detect, so we just report that they're copies. ###
  ### This makes our reporting a bit inconsistent. Is that bad? ###

* subversion/libsvn_client/delete.c
  (delete_urls): Set operation name to "delete".

* subversion/libsvn_client/diff.c
  (do_merge): Set operation name to "merge".
  (single_file_merge_get_file): Set operation name to "merge-single-file".
  (diff_repos_repos): Set operation name to "diff-repos-to-repos".
  (diff_repos_wc): Set operation name to "diff-repos-to-wc".

* subversion/libsvn_client/export.c
  (svn_client_export3): Set operation name to "export-file" or
  "export-directory" as appropriate.

* subversion/libsvn_client/info.c
  (svn_client_info): Set operation name to "info", in both first
  attempt and in fallback case.

* subversion/libsvn_client/locking_commands.c
  (svn_client_lock): Set operation name to "lock".
  (svn_client_unlock): Set operation name to "unlock".

* subversion/libsvn_client/log.c
  (svn_client_log2): Set operation name to "log".

* subversion/libsvn_client/ls.c
  (svn_client_ls2): Set operation name to "list-dir" or "list-file" as
  appropriate.

* subversion/libsvn_client/prop_commands.c
  (svn_client_revprop_set): Set operation name to "revprop-set".
  (svn_client_revprop_get): Set operation name to "revprop-get".
  (svn_client_revprop_list): Set operation name to "revprop-list".
  (svn_client_propget2): Set operation name to "prop-get".
  (svn_client_proplist2): Set operation name to "prop-list".

* subversion/libsvn_client/relocate.c
  (validator_func): Leave a comment about how to do operation logging.

* subversion/libsvn_client/status.c
  (svn_client_status2): Set operation name to "status". Leave a
  comment about a possible better way to do it.

* subversion/libsvn_client/update.c
  (svn_client__update_internal): Take new operation_name name
  parameter, set the ra_session's operation name to it right before
  the reporter drive. Leave a comment about possible better way.

* subversion/libsvn_client/switch.c
  (svn_client_switch): Set operation name to "switch". Leave a
  comment about possible better way.

Make ra_dav transmit the operation name:

* subversion/include/svn_dav.h
  (SVN_DAV_OPERATION_NAME_HEADER): New request header.

* subversion/mod_dav_svn/repos.c
  (dav_svn_get_resource): Notice new header, log its value as at INFO
  level in apache's errorlog.

* subversion/libsvn_ra_dav/ra_dav.h
  (svn_ra_dav__merge_activity): Take an svn_ra_session_t argument.
  (svn_ra_dav__get_props): Take an extra_headers argument.
  (struct lock_request_baton): Add an operation_name field.

* subversion/libsvn_ra_dav/props.c
  (svn_ra_dav__get_props): Take incoming extra_headers arg and add to
  it, rather than creating the hash.
  (svn_ra_dav__do_stat): If session->operation_name is present, send
  it in the PROPFIND. This covers 'svn info'.
  (svn_ra_dav__get_props_resource): Pass NULL extra_headers to
  svn_ra_dav__get_props.

* subversion/libsvn_ra_dav/fetch.c
  (report_baton_t): Store svn_ra_session_t as field in report baton.
  (make_reporter): Set the new field.
  (svn_ra_dav__change_rev_prop): If session->operation_name is
  present, send it in the PROPPATCH. This covers 'svn ps --revprop'.
  (reporter_finish_report, svn_ra_dav__get_dated_revision,
  svn_ra_dav__get_locations, svn_ra_dav__get_locks): If
  session->operation_name is present, send it in the REPORT headers.
  This covers 'svn co/export/up/switch/diff/merge'.
  (svn_ra_dav__get_dir): If operation_name is present, pass it as an
  extra header to __get_props(). This covers 'svn ls' and 'svn pl --revprop'.
  (custom_get_request): Take 'operation_name' parameter, attach to request.
  (simple_fetch_file): Pass NULL to new custom_get_request() param.
  (svn_ra_dav__get_file): Pass session->operation_name to
  custom_get_request(). This covers 'svn cat'.
  (start_element): Pass NULL extra_headers to svn_ra_dav__get_props.

* subversion/libsvn_ra_dav/file_revs.c
  (svn_ra_dav__get_file_revs): If operation_name is present, pass it
  as an extra REPORT header. This covers 'svn blame'.

* subversion/libsvn_ra_dav/log.c
  (svn_ra_dav__get_log): If operation_name is present, send it as
  an extra header in the REPORT request. This covers 'svn log'.

* subversion/libsvn_ra_dav/commit.c
  (struct commit_ctx_t): Add svn_ra_session_t field.
  (svn_ra_dav__get_commit_editor): Set the session field.
  (commit_close_edit): Pass the session to __merge_activity().

* subversion/libsvn_ra_dav/merge.c: Include ../libsvn_ra/ra_loader.h.
  (svn_ra_dav__merge_activity): If operation_name is present, send it
  as an extra header in the MERGE request. This covers any client
  command that causes a commit to happen.

* subversion/libsvn_ra_dav/session.c
  (svn_ra_dav__lock): Pass session->operation_name to shim_ra_dav__lock().
  (shim_svn_ra_dav__lock): Take an operation_name arg, place it in lrb.
  (pre_send_hook): If lrb->operation_name is present, attach as header
  to LOCK/UNLOCK request. This covers 'svn lock/unlock'.
]]]

Index: subversion/libsvn_ra/ra_loader.c
===================================================================
--- subversion/libsvn_ra/ra_loader.c (revision 14982)
+++ subversion/libsvn_ra/ra_loader.c (working copy)
@@ -272,6 +272,7 @@
   session = apr_pcalloc (pool, sizeof (*session));
   session->vtable = vtable;
   session->pool = pool;
+ session->operation_name = NULL;
 
   /* Ask the library to open the session. */
   SVN_ERR (vtable->open (session, repos_URL, callbacks, callback_baton,
@@ -281,6 +282,12 @@
   return SVN_NO_ERROR;
 }
 
+void svn_ra_set_operation_name(svn_ra_session_t *session,
+ const char *operation_name)
+{
+ session->operation_name = operation_name;
+}
+
 svn_error_t *svn_ra_get_latest_revnum (svn_ra_session_t *session,
                                        svn_revnum_t *latest_revnum,
                                        apr_pool_t *pool)
Index: subversion/libsvn_ra/ra_loader.h
===================================================================
--- subversion/libsvn_ra/ra_loader.h (revision 14982)
+++ subversion/libsvn_ra/ra_loader.h (working copy)
@@ -207,6 +207,26 @@
   /* Pool used to manage this session. */
   apr_pool_t *pool;
 
+ /** The name of the operation the client wants to perform, to assist
+ * the server in logging the operation.
+ *
+ * If NULL, do not report an operation name to the server.
+ *
+ * Else, report the operation name exactly once, then set this field
+ * to NULL. That way, when a single operation involves multiple RA
+ * requests, the operation does not get logged multiple times.
+ *
+ * @note If you reuse the same svn_ra_session_t for multiple
+ * operations, remember to reset operation_name between them.
+ *
+ * This field is necessary because sometimes the same RA API is used
+ * for different operations, e.g., checkout and update both use
+ * svn_ra_do_update(). For the server to log the operation in a way
+ * that reflects what the client intended to do, the client must
+ * tell the RA layer the operation's name.
+ */
+ const char *operation_name;
+
   /* Private data for the RA implementation. */
   void *priv;
 };
Index: subversion/include/svn_dav.h
===================================================================
--- subversion/include/svn_dav.h (revision 14982)
+++ subversion/include/svn_dav.h (working copy)
@@ -107,6 +107,12 @@
 #define SVN_DAV_RESULT_FULLTEXT_MD5_HEADER "X-SVN-Result-Fulltext-MD5"
 /** @} */
 
+
+/** A header possibly attached to outbound ra_dav requests which
+ announces the client operation being performed. */
+#define SVN_DAV_OPERATION_NAME_HEADER "X-SVN-Operation-Name"
+
+
 /* ### should add strings for the various XML elements in the reports
    ### and things. also the custom prop names. etc.
 */
Index: subversion/include/svn_ra.h
===================================================================
--- subversion/include/svn_ra.h (revision 14982)
+++ subversion/include/svn_ra.h (working copy)
@@ -397,6 +397,25 @@
                           apr_hash_t *config,
                           apr_pool_t *pool);
 
+/* Set the client operation name for @a session to @a operation_name.
+ * This is the name the session should report to the server for the
+ * next operation, so the server can log that if it wants to.
+ *
+ * The session will only transmit this name once -- after the first
+ * time, it sets the name to NULL, so that if @a session is used
+ * multiple times as part of the same operation, the operation does
+ * not get logged multiple times.
+ *
+ * There is no corresponding get() function because only the RA code
+ * should ever need to get the operation name, and it has direct
+ * access to @a session's internals.
+ *
+ * @since New in 1.3.
+ */
+void svn_ra_set_operation_name(svn_ra_session_t *session,
+ const char *operation_name);
+
+
 /**
  * Get the latest revision number from the repository of @a session.
  *
Index: subversion/libsvn_client/relocate.c
===================================================================
--- subversion/libsvn_client/relocate.c (revision 14982)
+++ subversion/libsvn_client/relocate.c (working copy)
@@ -39,7 +39,6 @@
   const char *path;
   apr_hash_t *url_uuids;
   apr_pool_t *pool;
-
 };
 
 
@@ -100,6 +99,16 @@
     SVN_ERR (svn_client__open_ra_session_internal (&ra_session, url, NULL,
                                                    NULL, NULL, FALSE, TRUE,
                                                    b->ctx, subpool));
+
+ /* ### If we wanted to log relocation operations on the server
+ * ### side, we would do more than just call
+ * ### svn_ra_set_operation_name() here. We'd add a new field
+ * ### 'operation_name' to validator_baton_t, call
+ * ### svn_ra_set_operation_name(ra_session, b->operation_name),
+ * ### and then set b->operation_name to NULL, so that NULL, so
+ * ### the operation isn't recorded again for each working copy
+ * ### subdirectory involved.
+ */
     SVN_ERR (svn_ra_get_uuid (ra_session, &ra_uuid, subpool));
     ra_uuid = apr_pstrdup (pool, ra_uuid);
     svn_pool_destroy (subpool);
Index: subversion/libsvn_client/switch.c
===================================================================
--- subversion/libsvn_client/switch.c (revision 14982)
+++ subversion/libsvn_client/switch.c (working copy)
@@ -146,7 +146,15 @@
 
      We pass NULL for traversal_info because this is a switch, not an
      update, and therefore we don't want to handle any externals
- except the ones directly affected by the switch. */
+ except the ones directly affected by the switch.
+
+ ### Set the operation name here, even though a better place would
+ ### be inside svn_wc_crawl_revisions2() right before
+ ### reporter->finish_report() is called. We'd have to rev the
+ ### API to svn_wc_crawl_revisions3() to do that, though, and it's
+ ### probably not worth it.
+ */
+ svn_ra_set_operation_name (ra_session, "switch");
   err = svn_wc_crawl_revisions2 (path, dir_access, reporter, report_baton,
                                  TRUE, recurse, use_commit_times,
                                  ctx->notify_func2, ctx->notify_baton2,
Index: subversion/libsvn_client/delete.c
===================================================================
--- subversion/libsvn_client/delete.c (revision 14982)
+++ subversion/libsvn_client/delete.c (working copy)
@@ -192,6 +192,7 @@
                                      pool));
 
   /* Call the path-based editor driver. */
+ svn_ra_set_operation_name (ra_session, "delete");
   err = svn_delta_path_driver (editor, edit_baton, SVN_INVALID_REVNUM,
                                targets, path_driver_cb_func,
                                (void *)editor, pool);
Index: subversion/libsvn_client/externals.c
===================================================================
--- subversion/libsvn_client/externals.c (revision 14982)
+++ subversion/libsvn_client/externals.c (working copy)
@@ -249,7 +249,8 @@
                                                 &(new_item->revision),
                                                 TRUE, FALSE,
                                                 ib->timestamp_sleep,
- ib->ctx, ib->pool));
+ "checkout-external", ib->ctx,
+ ib->pool));
     }
   else if (! new_item)
     {
@@ -306,7 +307,8 @@
                                               &(new_item->revision),
                                               TRUE, FALSE,
                                               ib->timestamp_sleep,
- ib->ctx, ib->pool));
+ "checkout-external", ib->ctx,
+ ib->pool));
     }
   else if (ib->update_unchanged)
     {
@@ -345,7 +347,8 @@
                                                     &(new_item->revision),
                                                     TRUE, FALSE,
                                                     ib->timestamp_sleep,
- ib->ctx, ib->pool));
+ "update-external", ib->ctx,
+ ib->pool));
             }
           /* If, however, the URLs don't match, we need to relegate
              the one subdir, and then checkout a new one. */
@@ -363,6 +366,7 @@
                                                       &(new_item->revision),
                                                       TRUE, FALSE,
                                                       ib->timestamp_sleep,
+ "checkout-external",
                                                       ib->ctx, ib->pool));
             }
         }
@@ -379,7 +383,8 @@
                                                   &(new_item->revision),
                                                   TRUE, FALSE,
                                                   ib->timestamp_sleep,
- ib->ctx, ib->pool));
+ "checkout-external", ib->ctx,
+ ib->pool));
         }
     }
 
Index: subversion/libsvn_client/export.c
===================================================================
--- subversion/libsvn_client/export.c (revision 14982)
+++ subversion/libsvn_client/export.c (working copy)
@@ -808,6 +808,7 @@
 
           /* Step outside the editor-likeness for a moment, to actually talk
            * to the repository. */
+ svn_ra_set_operation_name (ra_session, "export-file");
           SVN_ERR (svn_ra_get_file (ra_session, "", revnum,
                                     svn_stream_from_aprfile (fb->tmp_file,
                                                              pool),
@@ -866,6 +867,7 @@
                                        TRUE, /* "help, my dir is empty!" */
                                        NULL, pool));
 
+ svn_ra_set_operation_name (ra_session, "export-directory");
           SVN_ERR (reporter->finish_report (report_baton, pool));
  
           /* Special case: Due to our sly export/checkout method of
Index: subversion/libsvn_client/client.h
===================================================================
--- subversion/libsvn_client/client.h (revision 14982)
+++ subversion/libsvn_client/client.h (working copy)
@@ -317,7 +317,12 @@
    function will sleep before returning to ensure timestamp integrity.
    If TIMESTAMP_SLEEP is not NULL then the function will not sleep but
    will set *TIMESTAMP_SLEEP to TRUE if a sleep is required, and will
- not change *TIMESTAMP_SLEEP if no sleep is required. */
+ not change *TIMESTAMP_SLEEP if no sleep is required.
+
+ OPERATION_NAME is the name of the actual operation the client is
+ performing (it might be checkout or switch instead of update, for
+ example), or NULL if the caller does not want the server to log the
+ precise operation. */
 svn_error_t *
 svn_client__update_internal (svn_revnum_t *result_rev,
                              const char *path,
@@ -325,6 +330,7 @@
                              svn_boolean_t recurse,
                              svn_boolean_t ignore_externals,
                              svn_boolean_t *timestamp_sleep,
+ const char *operation_name,
                              svn_client_ctx_t *ctx,
                              apr_pool_t *pool);
 
@@ -335,7 +341,11 @@
    timestamp integrity. If TIMESTAMP_SLEEP is not NULL then the
    function will not sleep but will set *TIMESTAMP_SLEEP to TRUE if a
    sleep is required, and will not change *TIMESTAMP_SLEEP if no sleep
- is required. */
+ is required.
+
+ OPERATION_NAME is the name of the actual operation the client is
+ performing (it might be "externals-checkout", for example), or NULL
+ if the caller does not want the server to log the precise operation. */
 svn_error_t *
 svn_client__checkout_internal (svn_revnum_t *result_rev,
                                const char *URL,
@@ -345,6 +355,7 @@
                                svn_boolean_t recurse,
                                svn_boolean_t ignore_externals,
                                svn_boolean_t *timestamp_sleep,
+ const char *operation_name,
                                svn_client_ctx_t *ctx,
                                apr_pool_t *pool);
 
Index: subversion/libsvn_client/status.c
===================================================================
--- subversion/libsvn_client/status.c (revision 14982)
+++ subversion/libsvn_client/status.c (working copy)
@@ -312,7 +312,15 @@
           /* Drive the reporter structure, describing the revisions
              within PATH. When we call reporter->finish_report,
              EDITOR will be driven to describe differences between our
- working copy and HEAD. */
+ working copy and HEAD.
+
+ ### Set the operation name here, even though a better
+ ### place would be inside svn_wc_crawl_revisions2() right
+ ### before reporter->finish_report() is called. We'd
+ ### have to rev the API to svn_wc_crawl_revisions3() to
+ ### do that, though, and it's probably not worth it.
+ */
+ svn_ra_set_operation_name (ra_session, "status");
           SVN_ERR (svn_wc_crawl_revisions2 (path, target_access,
                                             &lock_fetch_reporter, &rb, FALSE,
                                             recurse, FALSE, NULL, NULL, NULL,
Index: subversion/libsvn_client/info.c
===================================================================
--- subversion/libsvn_client/info.c (revision 14982)
+++ subversion/libsvn_client/info.c (working copy)
@@ -304,6 +304,7 @@
   base_name = svn_path_uri_decode(base_name, pool);
   
   /* Get the dirent for the URL itself. */
+ svn_ra_set_operation_name (ra_session, "info");
   err = svn_ra_stat (ra_session, "", rev, &the_ent, pool);
 
   /* svn_ra_stat() will work against old versions of mod_dav_svn, but
@@ -314,6 +315,7 @@
       /* Fall back to pre-1.2 strategy for fetching dirent's URL. */
       svn_error_clear (err);
 
+ svn_ra_set_operation_name (ra_session, "info");
       SVN_ERR (svn_ra_check_path (ra_session, "", rev, &url_kind, pool));
       if (url_kind == svn_node_none)
         return svn_error_createf (SVN_ERR_RA_ILLEGAL_URL, NULL,
Index: subversion/libsvn_client/prop_commands.c
===================================================================
--- subversion/libsvn_client/prop_commands.c (revision 14982)
+++ subversion/libsvn_client/prop_commands.c (working copy)
@@ -291,6 +291,7 @@
            (set_rev, ra_session, revision, NULL, pool));
 
   /* The actual RA call. */
+ svn_ra_set_operation_name (ra_session, "revprop-set");
   SVN_ERR (svn_ra_change_rev_prop (ra_session, *set_rev, propname, propval,
                                    pool));
 
@@ -580,6 +581,7 @@
 
       SVN_ERR (svn_ra_check_path (ra_session, "", revnum, &kind, pool));
 
+ svn_ra_set_operation_name (ra_session, "prop-get");
       SVN_ERR (remote_propget (*props, propname, url, "",
                                kind, revnum, ra_session,
                                recurse, pool));
@@ -678,6 +680,7 @@
            (set_rev, ra_session, revision, NULL, pool));
 
   /* The actual RA call. */
+ svn_ra_set_operation_name (ra_session, "revprop-get");
   SVN_ERR (svn_ra_rev_prop (ra_session, *set_rev, propname, propval, pool));
 
   return SVN_NO_ERROR;
@@ -932,6 +935,7 @@
       
       SVN_ERR (svn_ra_check_path (ra_session, "", revnum, &kind, pool));
 
+ svn_ra_set_operation_name (ra_session, "prop-list");
       SVN_ERR (remote_proplist (*props, url, "",
                                 kind, revnum, ra_session,
                                 recurse, pool, svn_pool_create (pool)));
@@ -1026,6 +1030,7 @@
            (set_rev, ra_session, revision, NULL, pool));
 
   /* The actual RA call. */
+ svn_ra_set_operation_name (ra_session, "revprop-list");
   SVN_ERR (svn_ra_rev_proplist (ra_session, *set_rev, &proplist, pool));
 
   *props = proplist;
Index: subversion/libsvn_client/ra.c
===================================================================
--- subversion/libsvn_client/ra.c (revision 14982)
+++ subversion/libsvn_client/ra.c (working copy)
@@ -840,6 +840,9 @@
   svn_revnum_t rev;
   const char *ignored_url;
   
+ /* ### This function should also return the svn_ra_callbacks_t
+ ### associated with *RA_SESSION_P. */
+
   SVN_ERR (svn_client_url_from_path (&initial_url, path_or_url, pool));
   if (! initial_url)
     return svn_error_createf (SVN_ERR_ENTRY_MISSING_URL, NULL,
Index: subversion/libsvn_client/checkout.c
===================================================================
--- subversion/libsvn_client/checkout.c (revision 14982)
+++ subversion/libsvn_client/checkout.c (working copy)
@@ -49,6 +49,7 @@
                                svn_boolean_t recurse,
                                svn_boolean_t ignore_externals,
                                svn_boolean_t *timestamp_sleep,
+ const char *operation_name,
                                svn_client_ctx_t *ctx,
                                apr_pool_t *pool)
 {
@@ -80,7 +81,8 @@
       /* Get the RA connection. */
       SVN_ERR (svn_client__ra_session_from_path (&ra_session, &revnum,
                                                  &URL, url, peg_revision,
- revision, ctx, pool));
+ revision,
+ ctx, pool));
       
       SVN_ERR (svn_ra_check_path (ra_session, "", revnum, &kind, pool));
       if (kind == svn_node_none)
@@ -107,7 +109,8 @@
           /* Have update fix the incompleteness. */
           err = svn_client__update_internal (result_rev, path, revision,
                                              recurse, ignore_externals,
- use_sleep, ctx, pool);
+ use_sleep, operation_name, ctx,
+ pool);
         }
       else if (kind == svn_node_dir)
         {
@@ -122,7 +125,8 @@
               SVN_ERR (svn_wc_ensure_adm (path, uuid, URL, revnum, pool));
               err = svn_client__update_internal (result_rev, path, revision,
                                                  recurse, ignore_externals,
- use_sleep, ctx, pool);
+ use_sleep, operation_name,
+ ctx, pool);
               goto done;
             }
 
@@ -140,7 +144,8 @@
             {
               err = svn_client__update_internal (result_rev, path, revision,
                                                  recurse, ignore_externals,
- use_sleep, ctx, pool);
+ use_sleep, operation_name,
+ ctx, pool);
             }
           else
             {
@@ -202,7 +207,7 @@
 {
   return svn_client__checkout_internal (result_rev, URL, path, peg_revision,
                                         revision, recurse, ignore_externals,
- NULL, ctx, pool);
+ NULL, "checkout", ctx, pool);
 }
 
 svn_error_t *
@@ -220,5 +225,5 @@
   
   return svn_client__checkout_internal (result_rev, URL, path, &peg_revision,
                                         revision, recurse, FALSE, NULL,
- ctx, pool);
+ "checkout", ctx, pool);
 }
Index: subversion/libsvn_client/locking_commands.c
===================================================================
--- subversion/libsvn_client/locking_commands.c (revision 14982)
+++ subversion/libsvn_client/locking_commands.c (working copy)
@@ -444,6 +444,7 @@
   cb.ctx = ctx;
 
   /* Lock the paths. */
+ svn_ra_set_operation_name (ra_session, "lock");
   SVN_ERR (svn_ra_lock (ra_session, path_revs, comment,
                         steal_lock, store_locks_callback, &cb, pool));
 
@@ -498,6 +499,7 @@
   cb.ctx = ctx;
 
   /* Unlock the paths. */
+ svn_ra_set_operation_name (ra_session, "unlock");
   SVN_ERR (svn_ra_unlock (ra_session, path_tokens, break_lock,
                           store_locks_callback, &cb, pool));
 
Index: subversion/libsvn_client/cat.c
===================================================================
--- subversion/libsvn_client/cat.c (revision 14982)
+++ subversion/libsvn_client/cat.c (working copy)
@@ -210,6 +210,8 @@
   eol_style = apr_hash_get (props, SVN_PROP_EOL_STYLE, APR_HASH_KEY_STRING);
   keywords = apr_hash_get (props, SVN_PROP_KEYWORDS, APR_HASH_KEY_STRING);
 
+ svn_ra_set_operation_name (ra_session, "cat");
+
   if (! eol_style && ! keywords)
     {
       /* It's a file with no special eol style or keywords. */
Index: subversion/libsvn_client/diff.c
===================================================================
--- subversion/libsvn_client/diff.c (revision 14982)
+++ subversion/libsvn_client/diff.c (working copy)
@@ -1479,6 +1479,7 @@
   SVN_ERR (svn_client__open_ra_session_internal (&ra_session, URL1, NULL,
                                                  NULL, NULL, FALSE, TRUE,
                                                  ctx, pool));
+
   /* Resolve the revision numbers. */
   SVN_ERR (svn_client__get_revision_number
            (&start_revnum, ra_session, revision1, path1, pool));
@@ -1523,6 +1524,7 @@
   SVN_ERR (reporter->set_path (report_baton, "", start_revnum, FALSE, NULL,
                                pool));
   
+ svn_ra_set_operation_name (ra_session, "merge");
   SVN_ERR (reporter->finish_report (report_baton, pool));
   
   return SVN_NO_ERROR;
@@ -1555,6 +1557,7 @@
   SVN_ERR (svn_io_open_unique_file (&fp, filename,
                                     merge_b->target, ".tmp",
                                     FALSE, pool));
+ svn_ra_set_operation_name (ra_session, "merge-single-file");
   SVN_ERR (svn_ra_get_file (ra_session, "", *rev,
                             svn_stream_from_aprfile (fp, pool),
                             NULL, props, pool));
@@ -1839,12 +1842,12 @@
     }
   
   /* Open temporary RA sessions to each URL. */
- SVN_ERR (svn_client__open_ra_session_internal (&ra_session1, url1, NULL,
- NULL, NULL, FALSE, TRUE,
- ctx, temppool));
- SVN_ERR (svn_client__open_ra_session_internal (&ra_session2, url2, NULL,
- NULL, NULL, FALSE, TRUE,
- ctx, temppool));
+ SVN_ERR (svn_client__open_ra_session_internal
+ (&ra_session1, url1, NULL, NULL, NULL, FALSE, TRUE,
+ ctx, temppool));
+ SVN_ERR (svn_client__open_ra_session_internal
+ (&ra_session2, url2, NULL, NULL, NULL, FALSE, TRUE,
+ ctx, temppool));
 
   /* Resolve named revisions to real numbers. */
   SVN_ERR (svn_client__get_revision_number
@@ -1926,6 +1929,7 @@
 
   /* Drive the reporter; do the diff. */
   SVN_ERR (reporter->set_path (report_baton, "", rev1, FALSE, NULL, pool));
+ svn_ra_set_operation_name (ra_session1, "diff-repos-to-repos");
   SVN_ERR (reporter->finish_report (report_baton, pool));
 
   return SVN_NO_ERROR;
@@ -2016,7 +2020,7 @@
   SVN_ERR (svn_client__open_ra_session_internal (&ra_session, anchor_url,
                                                  NULL, NULL, NULL, FALSE, TRUE,
                                                  ctx, pool));
-
+
   SVN_ERR (svn_wc_get_diff_editor3 (adm_access, target,
                                     callbacks, callback_baton,
                                     recurse,
@@ -2041,6 +2045,16 @@
                            url1,
                            diff_editor, diff_edit_baton, pool));
 
+ /* ### I'd love to set the operation name right before the call to
+ ### reporter->finish_report(). Unfortunately, that happens down
+ ### in libsvn_wc, where the sun don't shine. We could just add a
+ ### parameter and rev the API to svn_wc_crawl_revisions3(). I
+ ### think, though, that it's better just to set the operation
+ ### here, and hope that reporter->finish_report() has the good
+ ### sense to be the only place in the RA reporter code that sends
+ ### the operation name. Can we count on that? Ben? Bueller? */
+ svn_ra_set_operation_name (ra_session, "diff-repos-to-wc");
+
   /* Create a txn mirror of path2; the diff editor will print
      diffs in reverse. :-) */
   SVN_ERR (svn_wc_crawl_revisions2 (path2, dir_access,
Index: subversion/libsvn_client/copy.c
===================================================================
--- subversion/libsvn_client/copy.c (revision 14982)
+++ subversion/libsvn_client/copy.c (working copy)
@@ -473,6 +473,7 @@
   cb_baton.resurrection = resurrection;
 
   /* Call the path-based editor driver. */
+ svn_ra_set_operation_name (ra_session, "copy-repos-to-repos");
   err = svn_delta_path_driver (editor, edit_baton, youngest, paths,
                                path_driver_cb_func, &cb_baton, pool);
   if (err)
@@ -718,6 +719,7 @@
   commit_in_progress = TRUE;
 
   /* Perform the commit. */
+ svn_ra_set_operation_name (ra_session, "copy-wc-to-repos");
   cmt_err = svn_client__do_commit (base_url, commit_items, adm_access,
                                    editor, edit_baton,
                                    0, /* ### any notify_path_offset needed? */
@@ -893,7 +895,7 @@
     {
       SVN_ERR (svn_client__checkout_internal
                (NULL, src_url, dst_path, &revision, &revision,
- TRUE, FALSE, NULL, ctx, pool));
+ TRUE, FALSE, NULL, "copy-repos-to-wc-dir", ctx, pool));
 
       if ((revision.kind == svn_opt_revision_head) && same_repositories)
         {
@@ -964,6 +966,7 @@
                (&fp, &new_text_path, dst_path, ".tmp", FALSE, pool));
 
       fstream = svn_stream_from_aprfile (fp, pool);
+ svn_ra_set_operation_name (ra_session, "copy-repos-to-wc-file");
       SVN_ERR (svn_ra_get_file (ra_session, "", src_revnum, fstream, &real_rev,
                                 &new_props, pool));
       SVN_ERR (svn_stream_close (fstream));
Index: subversion/libsvn_client/ls.c
===================================================================
--- subversion/libsvn_client/ls.c (revision 14982)
+++ subversion/libsvn_client/ls.c (working copy)
@@ -96,6 +96,7 @@
     {
       *dirents = apr_hash_make (pool);
 
+ svn_ra_set_operation_name (ra_session, "list-dir");
       SVN_ERR (get_dir_contents (*dirents, "", rev, ra_session, recurse,
                                  ctx, pool));
     }
@@ -117,6 +118,7 @@
                                                      ctx, pool));
 
       /* Get all parent's entries, no props. */
+ svn_ra_set_operation_name (ra_session, "list-file");
       SVN_ERR (svn_ra_get_dir (ra_session, "", rev, &parent_ents,
                                NULL, NULL, pool));
 
Index: subversion/libsvn_client/blame.c
===================================================================
--- subversion/libsvn_client/blame.c (revision 14982)
+++ subversion/libsvn_client/blame.c (working copy)
@@ -560,6 +560,7 @@
      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. */
+ svn_ra_set_operation_name (ra_session, "blame");
   err = svn_ra_get_file_revs (ra_session, "",
                               start_revnum - (start_revnum > 0 ? 1 : 0),
                               end_revnum,
@@ -689,6 +690,8 @@
   SVN_ERR (svn_client__open_ra_session_internal (&ra_session, reposURL, NULL,
                                                  NULL, NULL, FALSE, FALSE,
                                                  frb->ctx, pool));
+ /* ### Should this be "blame-old" instead? */
+ svn_ra_set_operation_name (ra_session, "blame");
 
   /* Inspect the first revision's change metadata; if there are any
      prior revisions, compute a new starting revision/path. If no
Index: subversion/libsvn_client/log.c
===================================================================
--- subversion/libsvn_client/log.c (revision 14982)
+++ subversion/libsvn_client/log.c (working copy)
@@ -198,6 +198,8 @@
       SVN_ERR (svn_client__get_revision_number
                (&end_revnum, ra_session, end, base_name, pool));
 
+ svn_ra_set_operation_name (ra_session, "log");
+
     if (start_is_local || end_is_local)
       {
         /* ### FIXME: At least one revision is locally dynamic, that
Index: subversion/libsvn_client/update.c
===================================================================
--- subversion/libsvn_client/update.c (revision 14982)
+++ subversion/libsvn_client/update.c (working copy)
@@ -45,6 +45,7 @@
                              svn_boolean_t recurse,
                              svn_boolean_t ignore_externals,
                              svn_boolean_t *timestamp_sleep,
+ const char *operation_name,
                              svn_client_ctx_t *ctx,
                              apr_pool_t *pool)
 {
@@ -134,7 +135,15 @@
 
   /* Drive the reporter structure, describing the revisions within
      PATH. When we call reporter->finish_report, the
- update_editor will be driven by svn_repos_dir_delta. */
+ update_editor will be driven by svn_repos_dir_delta.
+
+ ### Set the operation name here, even though a better place would
+ ### be inside svn_wc_crawl_revisions2() right before
+ ### reporter->finish_report() is called. We'd have to rev the
+ ### API to svn_wc_crawl_revisions3() to do that, though, and it's
+ ### probably not worth it.
+ */
+ svn_ra_set_operation_name (ra_session, operation_name);
   err = svn_wc_crawl_revisions2 (path, dir_access, reporter, report_baton,
                                  TRUE, recurse, use_commit_times,
                                  ctx->notify_func2, ctx->notify_baton2,
@@ -211,7 +220,7 @@
 
       err = svn_client__update_internal (&result_rev, path, revision,
                                          recurse, ignore_externals,
- &sleep, ctx, subpool);
+ &sleep, "update", ctx, subpool);
       if (err && err->apr_err != SVN_ERR_WC_NOT_DIRECTORY)
         {
           return err;
@@ -247,5 +256,5 @@
                    apr_pool_t *pool)
 {
   return svn_client__update_internal (result_rev, path, revision, recurse,
- FALSE, NULL, ctx, pool);
+ FALSE, NULL, "update", ctx, pool);
 }
Index: subversion/libsvn_client/commit.c
===================================================================
--- subversion/libsvn_client/commit.c (revision 14982)
+++ subversion/libsvn_client/commit.c (working copy)
@@ -739,12 +739,12 @@
        /* ### Is svn_path_local_style() really necessary for this? */
        svn_path_local_style (SVN_WC_ADM_DIR_NAME, pool));
 
-
- /* If an error occurred during the commit, abort the edit and return
- the error. We don't even care if the abort itself fails. */
+ svn_ra_set_operation_name (ra_session, "import");
   if ((err = import (path, new_entries, editor, edit_baton,
                      nonrecursive, excludes, ctx, subpool)))
     {
+ /* If an error occurred during the commit, abort the edit and return
+ the error. We don't even care if the abort itself fails. */
       svn_error_clear (editor->abort_edit (edit_baton, subpool));
       return err;
     }
@@ -1467,6 +1467,7 @@
   display_dir = svn_path_get_longest_ancestor (display_dir, base_dir, pool);
 
   /* Perform the commit. */
+ svn_ra_set_operation_name (ra_session, "commit");
   cmt_err = svn_client__do_commit (base_url, commit_items, base_dir_access,
                                    editor, edit_baton,
                                    display_dir,
Index: subversion/libsvn_client/add.c
===================================================================
--- subversion/libsvn_client/add.c (revision 14982)
+++ subversion/libsvn_client/add.c (working copy)
@@ -585,6 +585,7 @@
                                      pool));
   
   /* Call the path-based editor driver. */
+ svn_ra_set_operation_name (ra_session, "mkdir");
   err = svn_delta_path_driver (editor, edit_baton, SVN_INVALID_REVNUM,
                                targets, path_driver_cb_func,
                                (void *)editor, pool);
Index: subversion/mod_dav_svn/repos.c
===================================================================
--- subversion/mod_dav_svn/repos.c (revision 14982)
+++ subversion/mod_dav_svn/repos.c (working copy)
@@ -1317,6 +1317,19 @@
     if (ua && (ap_strstr_c(ua, "SVN/") == ua))
       repos->is_svn_client = TRUE;
   }
+
+ /* See if the svn client has declared an operation name to log. */
+ {
+ const char *operation_name = apr_table_get(r->headers_in,
+ SVN_DAV_OPERATION_NAME_HEADER);
+ if (operation_name)
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
+ "svn operation: "
+ "user '%s', repos '%s': '%s' on path '%s'",
+ repos->username ? repos->username : "anonymous",
+ repos->fs_path, operation_name,
+ repos_path ? repos_path : "(unknown)");
+ }
   
   /* Retrieve/cache open repository */
   repos_key = apr_pstrcat(r->pool, "mod_dav_svn:", fs_path, NULL);
Index: subversion/libsvn_ra_dav/file_revs.c
===================================================================
--- subversion/libsvn_ra_dav/file_revs.c (revision 14982)
+++ subversion/libsvn_ra_dav/file_revs.c (working copy)
@@ -306,7 +306,18 @@
   int http_status = 0;
   struct report_baton rb;
   svn_error_t *err;
+ apr_hash_t *extra_headers = NULL;
 
+ /* If available, send a custom header describing the operation. */
+ if (session->operation_name != NULL)
+ {
+ extra_headers = apr_hash_make(pool);
+ apr_hash_set(extra_headers, SVN_DAV_OPERATION_NAME_HEADER,
+ APR_HASH_KEY_STRING,
+ session->operation_name);
+ session->operation_name = NULL;
+ }
+
   static const char request_head[]
     = "<S:file-revs-report xmlns:S=\"" SVN_XML_NAMESPACE "\">" DEBUG_CR;
   static const char request_tail[]
@@ -350,7 +361,7 @@
   err = svn_ra_dav__parsed_request (ras->sess, "REPORT", final_bc_url,
                                     request_body->data, NULL, NULL,
                                     start_element, cdata_handler, end_element,
- &rb, NULL, &http_status, FALSE,
+ &rb, extra_headers, &http_status, FALSE,
                                     ras->pool);
 
   /* Map status 501: Method Not Implemented to our not implemented error.
Index: subversion/libsvn_ra_dav/merge.c
===================================================================
--- subversion/libsvn_ra_dav/merge.c (revision 14982)
+++ subversion/libsvn_ra_dav/merge.c (working copy)
@@ -35,6 +35,8 @@
 #include "svn_props.h"
 #include "svn_xml.h"
 
+#include "../libsvn_ra/ra_loader.h"
+
 #include "svn_private_config.h"
 
 #include "ra_dav.h"
@@ -641,6 +643,7 @@
     svn_revnum_t *new_rev,
     const char **committed_date,
     const char **committed_author,
+ svn_ra_session_t *session,
     svn_ra_dav__session_t *ras,
     const char *repos_url,
     const char *activity_url,
@@ -688,6 +691,16 @@
                     value);
     }
 
+ if (session->operation_name != NULL)
+ {
+ if (! extra_headers)
+ extra_headers = apr_hash_make(pool);
+ apr_hash_set(extra_headers, SVN_DAV_OPERATION_NAME_HEADER,
+ APR_HASH_KEY_STRING,
+ apr_pstrdup(pool, session->operation_name));
+ session->operation_name = NULL;
+ }
+
   /* Need to marshal the whole [path->token] hash to the server as
      a string within the body of the MERGE request. */
   if ((lock_tokens != NULL)
Index: subversion/libsvn_ra_dav/log.c
===================================================================
--- subversion/libsvn_ra_dav/log.c (revision 14982)
+++ subversion/libsvn_ra_dav/log.c (working copy)
@@ -322,6 +322,7 @@
   svn_string_t bc_url, bc_relative;
   const char *final_bc_url;
   svn_revnum_t use_rev;
+ apr_hash_t *extra_headers = NULL;
 
   /* ### todo: I don't understand why the static, file-global
      variables shared by update and status are called `report_head'
@@ -353,8 +354,17 @@
       { "DAV:", "comment", ELEM_comment, SVN_RA_DAV__XML_CDATA },
       { NULL }
     };
+
+ /* If available, send a custom header describing the operation. */
+ if (session->operation_name != NULL)
+ {
+ extra_headers = apr_hash_make(pool);
+ apr_hash_set(extra_headers, SVN_DAV_OPERATION_NAME_HEADER,
+ APR_HASH_KEY_STRING,
+ session->operation_name);
+ session->operation_name = NULL;
+ }
   
-
   /* Construct the request body. */
   svn_stringbuf_appendcstr(request_body, log_request_head);
   svn_stringbuf_appendcstr(request_body,
@@ -434,7 +444,7 @@
                                              log_start_element,
                                              log_end_element,
                                              &lb,
- NULL,
+ extra_headers,
                                              NULL,
                                              FALSE,
                                              ras->pool) );
Index: subversion/libsvn_ra_dav/ra_dav.h
===================================================================
--- subversion/libsvn_ra_dav/ra_dav.h (revision 14982)
+++ subversion/libsvn_ra_dav/ra_dav.h (working copy)
@@ -143,6 +143,9 @@
   /* If <D:error> is returned, here's where the parsed result goes. */
   svn_error_t *err;
 
+ /* Name of operation, possibly declared by client */
+ const char *operation_name;
+
   /* A place for allocating fields in this structure. */
   apr_pool_t *pool;
 };
@@ -394,6 +397,7 @@
                                     int depth,
                                     const char *label,
                                     const ne_propname *which_props,
+ apr_hash_t *extra_headers,
                                     apr_pool_t *pool);
 
 /* fetch a single resource's props from the server. */
@@ -708,6 +712,7 @@
     svn_revnum_t *new_rev,
     const char **committed_date,
     const char **committed_author,
+ svn_ra_session_t *session,
     svn_ra_dav__session_t *ras,
     const char *repos_url,
     const char *activity_url,
Index: subversion/libsvn_ra_dav/session.c
===================================================================
--- subversion/libsvn_ra_dav/session.c (revision 14982)
+++ subversion/libsvn_ra_dav/session.c (working copy)
@@ -970,6 +970,17 @@
         }
     }
 
+ /* Possibly add an X-SVN-Operation-Name: header for logging. */
+ if (lrb->operation_name
+ && ((strcmp(lrb->method, "LOCK") == 0)
+ || (strcmp(lrb->method, "UNLOCK") == 0)))
+ {
+ char *hdr = apr_psprintf(lrb->pool, "%s: %s\r\n",
+ SVN_DAV_OPERATION_NAME_HEADER,
+ lrb->operation_name);
+ ne_buffer_zappend(header, hdr);
+ }
+
   /* Register a response handler capable of parsing <D:error> */
   lrb->error_parser = ne_xml_create();
   svn_ra_dav__add_error_handler(req, lrb->error_parser,
@@ -1005,6 +1016,7 @@
                       const char *comment,
                       svn_boolean_t force,
                       svn_revnum_t current_rev,
+ const char *operation_name,
                       apr_pool_t *pool)
 {
   svn_ra_dav__session_t *ras = session->priv;
@@ -1026,6 +1038,7 @@
   ras->lrb->pool = pool;
   ras->lrb->current_rev = current_rev;
   ras->lrb->force = force;
+ ras->lrb->operation_name = operation_name;
 
   /* Make a neon lock structure. */
   nlock = ne_lock_create();
@@ -1122,7 +1135,8 @@
       revnum = val;
 
       err = shim_svn_ra_dav__lock(session, &lock, path, comment,
- force, *revnum, iterpool);
+ force, *revnum,
+ session->operation_name, iterpool);
 
       if (err && !SVN_ERR_IS_LOCK_ERROR (err))
         return err;
Index: subversion/libsvn_ra_dav/props.c
===================================================================
--- subversion/libsvn_ra_dav/props.c (revision 14982)
+++ subversion/libsvn_ra_dav/props.c (working copy)
@@ -471,13 +471,16 @@
                                     int depth,
                                     const char *label,
                                     const ne_propname *which_props,
+ apr_hash_t *extra_headers,
                                     apr_pool_t *pool)
 {
   svn_error_t *err = SVN_NO_ERROR;
   propfind_ctx_t pc;
   ne_buffer *body;
- apr_hash_t *extra_headers = apr_hash_make(pool);
 
+ if (extra_headers == NULL)
+ extra_headers = apr_hash_make(pool);
+
   /* Add a Depth header. */
   if (depth == NE_DEPTH_ZERO)
     apr_hash_set(extra_headers, "Depth", 5, "0");
@@ -553,7 +556,7 @@
       url_path[len - 1] = '\0';
 
   SVN_ERR( svn_ra_dav__get_props(&props, sess, url_path, NE_DEPTH_ZERO,
- label, which_props, pool) );
+ label, which_props, NULL, pool) );
 
   /* ### HACK. We need to have the client canonicalize paths, get rid
      of double slashes and such. This check is just a check against
@@ -1186,6 +1189,7 @@
   const char *url = ras->url;
   const char *final_url;
   apr_hash_t *resources;
+ apr_hash_t *extra_headers = NULL;
   apr_hash_index_t *hi;
   svn_error_t *err;
 
@@ -1223,9 +1227,19 @@
                                              pool);
     }
 
+ if (session->operation_name != NULL)
+ {
+ extra_headers = apr_hash_make(pool);
+ apr_hash_set(extra_headers, SVN_DAV_OPERATION_NAME_HEADER,
+ APR_HASH_KEY_STRING,
+ session->operation_name);
+ session->operation_name = NULL;
+ }
+
   /* Depth-zero PROPFIND is the One True DAV Way. */
   err = svn_ra_dav__get_props(&resources, ras->sess, final_url, NE_DEPTH_ZERO,
- NULL, NULL /* all props */, pool);
+ NULL, NULL /* all props */,
+ extra_headers, pool);
   if (err)
     {
       if (err->apr_err == SVN_ERR_RA_DAV_PATH_NOT_FOUND)
Index: subversion/libsvn_ra_dav/commit.c
===================================================================
--- subversion/libsvn_ra_dav/commit.c (revision 14982)
+++ subversion/libsvn_ra_dav/commit.c (working copy)
@@ -96,6 +96,7 @@
 
 typedef struct
 {
+ svn_ra_session_t *session;
   svn_ra_dav__session_t *ras;
   const char *activity_url;
 
@@ -1427,6 +1428,7 @@
   SVN_ERR( svn_ra_dav__merge_activity(&new_rev,
                                       &committed_date,
                                       &committed_author,
+ cc->session,
                                       cc->ras,
                                       cc->ras->root.path,
                                       cc->activity_url,
@@ -1550,6 +1552,7 @@
 
   /* Build the main commit editor's baton. */
   cc = apr_pcalloc(pool, sizeof(*cc));
+ cc->session = session;
   cc->ras = ras;
   cc->valid_targets = apr_hash_make(pool);
   cc->get_func = ras->callbacks->get_wc_prop;
Index: subversion/libsvn_ra_dav/fetch.c
===================================================================
--- subversion/libsvn_ra_dav/fetch.c (revision 14982)
+++ subversion/libsvn_ra_dav/fetch.c (working copy)
@@ -132,6 +132,10 @@
 } dir_item_t;
 
 typedef struct {
+ /* Public session object from the client */
+ svn_ra_session_t *session;
+
+ /* Private ra_dav session object, really just session->priv */
   svn_ra_dav__session_t *ras;
 
   apr_file_t *tmpfile;
@@ -391,6 +395,7 @@
                                        svn_ra_get_wc_prop_func_t get_wc_prop,
                                        void *cb_baton,
                                        svn_boolean_t use_base,
+ const char *operation_name,
                                        apr_pool_t *pool)
 {
   custom_get_ctx_t cgc = { 0 };
@@ -442,6 +447,12 @@
       ne_add_request_header(req, SVN_DAV_DELTA_BASE_HEADER, delta_base);
     }
 
+ if (operation_name)
+ {
+ ne_add_request_header(req, SVN_DAV_OPERATION_NAME_HEADER,
+ operation_name);
+ }
+
   /* add in a reader to capture the body of the response. */
   if (ras->compression)
     {
@@ -621,7 +632,7 @@
   SVN_ERR( custom_get_request(sess, url, relpath,
                               fetch_file_reader, &frc,
                               get_wc_prop, cb_baton,
- TRUE, pool) );
+ TRUE, NULL, pool) );
 
   /* close the handler, since the file reading completed successfully. */
   SVN_ERR( (*frc.handler)(NULL, frc.handler_baton) );
@@ -831,7 +842,9 @@
                                   get_file_reader, &fwc,
                                   ras->callbacks->get_wc_prop,
                                   ras->callback_baton,
- FALSE, pool) );
+ FALSE,
+ session->operation_name,
+ pool) );
 
       if (fwc.do_checksum)
         {
@@ -903,11 +916,23 @@
 
   if (dirents)
     {
+ apr_hash_t *extra_headers = NULL;
+
+ if (session->operation_name != NULL)
+ {
+ extra_headers = apr_hash_make(pool);
+ apr_hash_set(extra_headers, SVN_DAV_OPERATION_NAME_HEADER,
+ APR_HASH_KEY_STRING,
+ session->operation_name);
+ session->operation_name = NULL;
+ }
+
       /* Just like Nautilus, Cadaver, or any other browser, we do a
          PROPFIND on the directory of depth 1. */
       SVN_ERR( svn_ra_dav__get_props(&resources, ras->sess,
                                      final_url, NE_DEPTH_ONE,
- NULL, NULL /* all props */, pool) );
+ NULL, NULL /* all props */,
+ extra_headers, pool) );
       
       /* Count the number of path components in final_url. */
       final_url_n_components = svn_path_component_count(final_url);
@@ -1084,11 +1109,21 @@
   const char *body;
   const char *vcc_url;
   svn_error_t *err;
+ apr_hash_t *extra_headers = NULL;
 
   /* Run the 'dated-rev-report' on the VCC url, which is always
      guaranteed to exist. */
   SVN_ERR (svn_ra_dav__get_vcc(&vcc_url, ras->sess, ras->root.path, pool));
   
+ /* If available, send a custom header describing the operation. */
+ if (session->operation_name != NULL)
+ {
+ extra_headers = apr_hash_make(pool);
+ apr_hash_set(extra_headers, SVN_DAV_OPERATION_NAME_HEADER,
+ APR_HASH_KEY_STRING,
+ session->operation_name);
+ session->operation_name = NULL;
+ }
 
   body = apr_psprintf(pool,
                       "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
@@ -1104,7 +1139,9 @@
                                           drev_report_elements,
                                           drev_validate_element,
                                           drev_start_element, drev_end_element,
- revision, NULL, NULL, FALSE, pool);
+ revision, extra_headers,
+ NULL, FALSE, pool);
+
   if (err && err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)
     return svn_error_quick_wrap(err, _("Server does not support date-based "
                                        "operations"));
@@ -1195,7 +1232,18 @@
   const char *final_bc_url;
   int i;
   int status_code = 0;
+ apr_hash_t *extra_headers = NULL;
 
+ /* If available, send a custom header describing the operation. */
+ if (session->operation_name != NULL)
+ {
+ extra_headers = apr_hash_make(pool);
+ apr_hash_set(extra_headers, SVN_DAV_OPERATION_NAME_HEADER,
+ APR_HASH_KEY_STRING,
+ session->operation_name);
+ session->operation_name = NULL;
+ }
+
   *locations = apr_hash_make (pool);
 
   request_body = svn_stringbuf_create("", pool);
@@ -1245,7 +1293,7 @@
   err = svn_ra_dav__parsed_request(ras->sess, "REPORT", final_bc_url,
                                    request_body->data, NULL, NULL,
                                    gloc_start_element, NULL, NULL,
- &request_baton, NULL, &status_code,
+ &request_baton, extra_headers, &status_code,
                                    FALSE, pool);
 
   /* Map status 501: Method Not Implemented to our not implemented error.
@@ -1544,7 +1592,18 @@
   svn_error_t *err;
   int status_code = 0;
   get_locks_baton_t baton;
+ apr_hash_t *extra_headers = NULL;
 
+ /* If available, send a custom header describing the operation. */
+ if (session->operation_name != NULL)
+ {
+ extra_headers = apr_hash_make(pool);
+ apr_hash_set(extra_headers, SVN_DAV_OPERATION_NAME_HEADER,
+ APR_HASH_KEY_STRING,
+ session->operation_name);
+ session->operation_name = NULL;
+ }
+
   baton.lock_hash = apr_hash_make (pool);
   baton.pool = pool;
   baton.scratchpool = svn_pool_create(pool);
@@ -1571,7 +1630,7 @@
                                    getlocks_cdata_handler,
                                    getlocks_end_element,
                                    &baton,
- NULL, /* extra headers */
+ extra_headers,
                                    &status_code,
                                    FALSE,
                                    pool);
@@ -1610,6 +1669,7 @@
   svn_ra_dav_resource_t *baseline;
   svn_error_t *err;
   apr_hash_t *prop_changes = NULL;
+ apr_hash_t *extra_headers = NULL;
   apr_array_header_t *prop_deletes = NULL;
   static const ne_propname wanted_props[] =
     {
@@ -1657,8 +1717,18 @@
       (*((const char **) apr_array_push(prop_deletes))) = name;
     }
 
+ /* If available, send a custom header describing the operation. */
+ if (session->operation_name != NULL)
+ {
+ extra_headers = apr_hash_make(pool);
+ apr_hash_set(extra_headers, SVN_DAV_OPERATION_NAME_HEADER,
+ APR_HASH_KEY_STRING,
+ session->operation_name);
+ session->operation_name = NULL;
+ }
+
   err = svn_ra_dav__do_proppatch(ras, baseline->url, prop_changes,
- prop_deletes, NULL, pool);
+ prop_deletes, extra_headers, pool);
   if (err)
     return
       svn_error_create
@@ -2050,6 +2120,7 @@
                                         bc_url,
                                         NE_DEPTH_ONE,
                                         NULL, NULL /* allprops */,
+ NULL,
                                         TOP_DIR(rb).pool) );
           
           /* re-index the results into a more usable hash.
@@ -2767,6 +2838,7 @@
   svn_error_t *err;
   const char *vcc;
   int http_status;
+ apr_hash_t *extra_headers = NULL;
 
 #define SVN_RA_DAV__REPORT_TAIL "</S:update-report>" DEBUG_CR
   /* write the final closing gunk to our request body. */
@@ -2792,6 +2864,16 @@
       return err;
     }
 
+ /* If available, send a custom header describing the operation. */
+ if (rb->session->operation_name != NULL)
+ {
+ extra_headers = apr_hash_make(pool);
+ apr_hash_set(extra_headers, SVN_DAV_OPERATION_NAME_HEADER,
+ APR_HASH_KEY_STRING,
+ rb->session->operation_name);
+ rb->session->operation_name = NULL;
+ }
+
   /* dispatch the REPORT. */
   err = svn_ra_dav__parsed_request(rb->ras->sess, "REPORT", vcc,
                                    NULL, rb->tmpfile, NULL,
@@ -2799,7 +2881,7 @@
                                    cdata_handler,
                                    end_element,
                                    rb,
- NULL, &http_status,
+ extra_headers, &http_status,
                                    rb->spool_response, rb->ras->pool);
 
   /* we're done with the file */
@@ -2895,6 +2977,7 @@
 
   rb = apr_pcalloc(pool, sizeof(*rb));
   rb->ras = ras;
+ rb->session = session;
   rb->scratch_pool = svn_pool_create(pool);
   rb->editor = editor;
   rb->edit_baton = edit_baton;

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org
Received on Sun Jun 5 21:26:54 2005

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.