Port r21075 from trunk to the 1.4.x-r21075 backport branch.

The only change between this and the original version committed to trunk
is that the portions that were applied to mod_dav_svn/reports/replay.c on
trunk have been applied to mod_dav_svn/replay.c on the branch, with some
minor tweaks to account for different names of some structures.


git-svn-id: https://svn.apache.org/repos/asf/subversion/branches/1.4.x-r21075@861165 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/subversion/include/svn_ra_svn.h b/subversion/include/svn_ra_svn.h
index 8c5890b..e39fd1f 100644
--- a/subversion/include/svn_ra_svn.h
+++ b/subversion/include/svn_ra_svn.h
@@ -346,7 +346,17 @@
 
 /** Receive edit commands over the network and use them to drive @a editor
  * with @a edit_baton.  On return, @a *aborted will be set if the edit was
- * aborted.
+ * aborted.  The drive can be terminated with a finish-replay command only
+ * if @a for_replay is true.
+ */
+svn_error_t *svn_ra_svn_drive_editor2(svn_ra_svn_conn_t *conn,
+                                      apr_pool_t *pool,
+                                      const svn_delta_editor_t *editor,
+                                      void *edit_baton,
+                                      svn_boolean_t *aborted,
+                                      svn_boolean_t for_replay);
+
+/** Like svn_ra_svn_drive_editor2, but with @a for_replay always FALSE.
  */
 svn_error_t *svn_ra_svn_drive_editor(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
                                      const svn_delta_editor_t *editor,
diff --git a/subversion/libsvn_ra_dav/replay.c b/subversion/libsvn_ra_dav/replay.c
index dde0458..841f784 100644
--- a/subversion/libsvn_ra_dav/replay.c
+++ b/subversion/libsvn_ra_dav/replay.c
@@ -389,7 +389,7 @@
       if (rb->dirs->nelts)
         svn_pool_destroy(APR_ARRAY_IDX(rb->dirs, 0, dir_item_t).pool);
 
-      rb->err = rb->editor->close_edit(rb->edit_baton, rb->pool);
+      rb->err = SVN_NO_ERROR;
       break;
 
     case ELEM_apply_textdelta:
diff --git a/subversion/libsvn_ra_local/ra_plugin.c b/subversion/libsvn_ra_local/ra_plugin.c
index c27d380..6dbd492 100644
--- a/subversion/libsvn_ra_local/ra_plugin.c
+++ b/subversion/libsvn_ra_local/ra_plugin.c
@@ -1266,8 +1266,6 @@
                             send_deltas, editor, edit_baton, NULL, NULL,
                             pool));
 
-  SVN_ERR(editor->close_edit(edit_baton, pool));
-
   return SVN_NO_ERROR;
 }
 
diff --git a/subversion/libsvn_ra_serf/replay.c b/subversion/libsvn_ra_serf/replay.c
index 6a56905..91b1660 100644
--- a/subversion/libsvn_ra_serf/replay.c
+++ b/subversion/libsvn_ra_serf/replay.c
@@ -406,7 +406,6 @@
   if (state == REPORT &&
       strcmp(name.name, "editor-report") == 0)
     {
-      SVN_ERR(ctx->editor->close_edit(ctx->editor_baton, parser->state->pool));
       svn_ra_serf__xml_pop_state(parser);
     }
   else if (state == OPEN_DIR && strcmp(name.name, "open-directory") == 0)
diff --git a/subversion/libsvn_ra_svn/client.c b/subversion/libsvn_ra_svn/client.c
index 4dc734a..75f9c46 100644
--- a/subversion/libsvn_ra_svn/client.c
+++ b/subversion/libsvn_ra_svn/client.c
@@ -1964,8 +1964,8 @@
                                  _("Server doesn't support the replay "
                                    "command")));
 
-  SVN_ERR(svn_ra_svn_drive_editor(sess->conn, pool, editor, edit_baton,
-                                  NULL));
+  SVN_ERR(svn_ra_svn_drive_editor2(sess->conn, pool, editor, edit_baton,
+                                   NULL, TRUE));
 
   SVN_ERR(svn_ra_svn_read_cmd_response(sess->conn, pool, ""));
 
diff --git a/subversion/libsvn_ra_svn/editor.c b/subversion/libsvn_ra_svn/editor.c
index b6f2443..1b4cf88 100644
--- a/subversion/libsvn_ra_svn/editor.c
+++ b/subversion/libsvn_ra_svn/editor.c
@@ -67,6 +67,7 @@
   apr_hash_t *tokens;
   svn_boolean_t *aborted;
   apr_pool_t *pool;
+  svn_boolean_t for_replay;
 } ra_svn_driver_state_t;
 
 typedef struct {
@@ -704,6 +705,19 @@
   return svn_ra_svn_write_cmd_response(conn, pool, "");
 }
 
+static svn_error_t *ra_svn_handle_finish_replay(svn_ra_svn_conn_t *conn,
+                                                apr_pool_t *pool,
+                                                apr_array_header_t *params,
+                                                void *baton)
+{
+  ra_svn_driver_state_t *ds = baton;
+  if (!ds->for_replay)
+    return svn_error_createf
+      (SVN_ERR_RA_SVN_UNKNOWN_CMD, NULL,
+       _("Command 'finish-replay' invalid outside of replays"));
+  return SVN_NO_ERROR;
+}
+
 static const svn_ra_svn_cmd_entry_t ra_svn_edit_commands[] = {
   { "target-rev",       ra_svn_handle_target_rev },
   { "open-root",        ra_svn_handle_open_root },
@@ -719,23 +733,41 @@
   { "close-file",       ra_svn_handle_close_file },
   { "close-edit",       ra_svn_handle_close_edit, TRUE },
   { "abort-edit",       ra_svn_handle_abort_edit, TRUE },
+  { "finish-replay",    ra_svn_handle_finish_replay, TRUE },
   { NULL }
 };
 
-svn_error_t *svn_ra_svn_drive_editor(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
-                                     const svn_delta_editor_t *editor,
-                                     void *edit_baton,
-                                     svn_boolean_t *aborted)
+svn_error_t *svn_ra_svn_drive_editor2(svn_ra_svn_conn_t *conn,
+                                      apr_pool_t *pool,
+                                      const svn_delta_editor_t *editor,
+                                      void *edit_baton,
+                                      svn_boolean_t *aborted,
+                                      svn_boolean_t for_replay)
 {
   ra_svn_driver_state_t state;
 
   if (svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EDIT_PIPELINE))
-    return svn_ra_svn__drive_editorp(conn, pool, editor, edit_baton, aborted);
+    return svn_ra_svn__drive_editorp(conn, pool, editor, edit_baton, aborted,
+                                     for_replay);
 
   state.editor = editor;
   state.edit_baton = edit_baton;
   state.tokens = apr_hash_make(pool);
   state.aborted = aborted;
   state.pool = pool;
+  state.for_replay = for_replay;
   return svn_ra_svn_handle_commands(conn, pool, ra_svn_edit_commands, &state);
 }
+
+svn_error_t *svn_ra_svn_drive_editor(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+                                     const svn_delta_editor_t *editor,
+                                     void *edit_baton,
+                                     svn_boolean_t *aborted)
+{
+  return svn_ra_svn_drive_editor2(conn,
+                                  pool,
+                                  editor,
+                                  edit_baton,
+                                  aborted,
+                                  FALSE);
+}
diff --git a/subversion/libsvn_ra_svn/editorp.c b/subversion/libsvn_ra_svn/editorp.c
index 5b156fe..2648ba0 100644
--- a/subversion/libsvn_ra_svn/editorp.c
+++ b/subversion/libsvn_ra_svn/editorp.c
@@ -71,6 +71,7 @@
   apr_pool_t *pool;
   apr_pool_t *file_pool;
   int file_refs;
+  svn_boolean_t for_replay;
 } ra_svn_driver_state_t;
 
 /* Works for both directories and files; however, the pool handling is
@@ -788,6 +789,21 @@
   return svn_ra_svn_write_cmd_response(conn, pool, "");
 }
 
+static svn_error_t *ra_svn_handle_finish_replay(svn_ra_svn_conn_t *conn,
+                                                apr_pool_t *pool,
+                                                apr_array_header_t *params,
+                                                ra_svn_driver_state_t *ds)
+{
+  if (!ds->for_replay)
+    return svn_error_createf
+      (SVN_ERR_RA_SVN_UNKNOWN_CMD, NULL,
+       _("Command 'finish-replay' invalid outside of replays"));
+  ds->done = TRUE;
+  if (ds->aborted)
+    *ds->aborted = FALSE;
+  return SVN_NO_ERROR;
+}
+
 static const struct {
   const char *cmd;
   svn_error_t *(*handler)(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
@@ -812,6 +828,7 @@
   { "absent-file",      ra_svn_handle_absent_file },
   { "close-edit",       ra_svn_handle_close_edit },
   { "abort-edit",       ra_svn_handle_abort_edit },
+  { "finish-replay",    ra_svn_handle_finish_replay },
   { NULL }
 };
 
@@ -837,7 +854,8 @@
                                        apr_pool_t *pool,
                                        const svn_delta_editor_t *editor,
                                        void *edit_baton,
-                                       svn_boolean_t *aborted)
+                                       svn_boolean_t *aborted,
+                                       svn_boolean_t for_replay)
 {
   ra_svn_driver_state_t state;
   apr_pool_t *subpool = svn_pool_create(pool);
@@ -854,6 +872,7 @@
   state.pool = pool;
   state.file_pool = svn_pool_create(pool);
   state.file_refs = 0;
+  state.for_replay = for_replay;
 
   while (!state.done)
     {
diff --git a/subversion/libsvn_ra_svn/protocol b/subversion/libsvn_ra_svn/protocol
index dc6880c..ce123f9 100644
--- a/subversion/libsvn_ra_svn/protocol
+++ b/subversion/libsvn_ra_svn/protocol
@@ -461,6 +461,10 @@
     params:   ( )
     response: ( )
 
+  finish-replay
+    params:   ( )
+    Only delivered from server to client, at the end of a replay.
+
 3.1.3. Report Command Set
 
 To reduce round-trip delays, report commands do not return responses.
diff --git a/subversion/libsvn_ra_svn/ra_svn.h b/subversion/libsvn_ra_svn/ra_svn.h
index 6ff4991..b3a8d8f 100644
--- a/subversion/libsvn_ra_svn/ra_svn.h
+++ b/subversion/libsvn_ra_svn/ra_svn.h
@@ -81,7 +81,8 @@
                                        apr_pool_t *pool,
                                        const svn_delta_editor_t *editor,
                                        void *edit_baton,
-                                       svn_boolean_t *aborted);
+                                       svn_boolean_t *aborted,
+                                       svn_boolean_t for_replay);
 
 /* CRAM-MD5 client implementation. */
 svn_error_t *svn_ra_svn__cram_client(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
diff --git a/subversion/mod_dav_svn/replay.c b/subversion/mod_dav_svn/replay.c
index a959417..b195e57 100644
--- a/subversion/mod_dav_svn/replay.c
+++ b/subversion/mod_dav_svn/replay.c
@@ -61,6 +61,16 @@
 }
 
 static svn_error_t *
+end_report(dav_svn_edit_baton_t *eb)
+{
+  SVN_ERR(dav_svn__send_xml(eb->bb, eb->output,
+                            "</S:editor-report>" DEBUG_CR));
+
+  return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
 maybe_close_textdelta(dav_svn_edit_baton_t *eb)
 {
   if (eb->sending_textdelta)
@@ -355,11 +365,6 @@
 static svn_error_t *close_edit(void *edit_baton,
                                apr_pool_t *pool)
 {
-  dav_svn_edit_baton_t *eb = edit_baton;
-
-  SVN_ERR(dav_svn__send_xml(eb->bb, eb->output,
-                            "</S:editor-report>" DEBUG_CR));
-
   return SVN_NO_ERROR;
 }
 
@@ -503,7 +508,7 @@
                                "Problem replaying revision",
                                resource->pool);
 
-  if ((err = editor->close_edit(edit_baton, resource->pool)))
+  if ((err = end_report(edit_baton)))
     return dav_svn_convert_err(err, HTTP_INTERNAL_SERVER_ERROR,
                                "Problem closing editor drive",
                                resource->pool);
diff --git a/subversion/svnserve/serve.c b/subversion/svnserve/serve.c
index 0647b13..c99a9cb 100644
--- a/subversion/svnserve/serve.c
+++ b/subversion/svnserve/serve.c
@@ -1976,13 +1976,12 @@
     err = svn_repos_replay2(root, b->fs_path->data, low_water_mark,
                             send_deltas, editor, edit_baton,
                             authz_check_access_cb_func(b), b, pool);
-  if (! err)
-    SVN_CMD_ERR(editor->close_edit(edit_baton, pool));
 
   if (err)
     svn_error_clear(editor->abort_edit(edit_baton, pool));
   SVN_CMD_ERR(err);
 
+  SVN_ERR(svn_ra_svn_write_cmd(conn, pool, "finish-replay", ""));
   SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, ""));
 
   return SVN_NO_ERROR;
diff --git a/subversion/svnsync/main.c b/subversion/svnsync/main.c
index 5718676..a75c87e 100644
--- a/subversion/svnsync/main.c
+++ b/subversion/svnsync/main.c
@@ -1075,6 +1075,8 @@
       SVN_ERR(svn_ra_replay(from_session, current, 0, TRUE,
                             cancel_editor, cancel_baton, subpool));
 
+      SVN_ERR(cancel_editor->close_edit(cancel_baton, subpool));
+
       /* Sanity check that we actually committed the revision we meant to. */
       if (baton->committed_rev != current)
         return svn_error_createf