Hi, the attached patch fixes both issue 4856 and the (different) bug we were seeing in one of our own repositories. The problem boils down to the fact that with `svn log --xml --verbose` a log-item is opened every time the PATH_CHANGE_RECEIVER callback is used. But if the revision is later declared "unimportant", the corresponding log-item closure is never sent via the REVISION_RECEIVER callback.
The patch fixes that by always using an intermediate callback routine and recording the call of inner() in a new flag in interesting_merge_baton_t. This flag is then used to decide if the REVISION_RECEIVER callback has to be called to close the log-item. With yet another flag in interesting_merge_baton_t record_inner_call() could be merged into interesting_merge() if you prefer that. Franz.
Index: subversion-1.14.x/subversion/libsvn_ra_serf/log.c =================================================================== --- subversion-1.14.x/subversion/libsvn_ra_serf/log.c (revision 1921239) +++ subversion-1.14.x/subversion/libsvn_ra_serf/log.c (working copy) @@ -299,6 +299,7 @@ "subtractive-merge", FALSE); + SVN_ERR_ASSERT(attrs != NULL); rev_str = svn_hash_gets(attrs, "revision"); if (rev_str) { Index: subversion-1.14.x/subversion/libsvn_repos/log.c =================================================================== --- subversion-1.14.x/subversion/libsvn_repos/log.c (revision 1921239) +++ subversion-1.14.x/subversion/libsvn_repos/log.c (working copy) @@ -1216,6 +1216,9 @@ /* Set to TRUE if we found it. */ svn_boolean_t found_rev_of_interest; + /* Set to TRUE if we called PATH_CHANGE_RECEIVER via INNER. */ + svn_boolean_t inner_got_used; + /* We need to invoke this user-provided callback if not NULL. */ svn_repos_path_change_receiver_t inner; void *inner_baton; @@ -1236,7 +1239,10 @@ apr_hash_index_t *hi; if (b->inner) - SVN_ERR(b->inner(b->inner_baton, change, scratch_pool)); + { + SVN_ERR(b->inner(b->inner_baton, change, scratch_pool)); + b->inner_got_used = TRUE; + } if (b->found_rev_of_interest) return SVN_NO_ERROR; @@ -1270,6 +1276,22 @@ return SVN_NO_ERROR; } +static svn_error_t * +record_inner_call(void *baton, + svn_repos_path_change_t *change, + apr_pool_t *scratch_pool) +{ + interesting_merge_baton_t *b = baton; + + if (b->inner) + { + SVN_ERR(b->inner(b->inner_baton, change, scratch_pool)); + b->inner_got_used = TRUE; + } + + return SVN_NO_ERROR; +} + /* Send a log message for REV to the CALLBACKS. FS is used with REV to fetch the interesting history information, @@ -1310,6 +1332,7 @@ { svn_repos_log_entry_t log_entry = { 0 }; log_callbacks_t my_callbacks = *callbacks; + apr_pool_t *scratch_pool; interesting_merge_baton_t baton; @@ -1323,6 +1346,7 @@ && apr_hash_count(log_target_history_as_mergeinfo)) { baton.found_rev_of_interest = FALSE; + baton.inner_got_used = FALSE; baton.rev = rev; baton.log_target_history_as_mergeinfo = log_target_history_as_mergeinfo; baton.inner = callbacks->path_change_receiver; @@ -1335,6 +1359,12 @@ else { baton.found_rev_of_interest = TRUE; + baton.inner_got_used = FALSE; + baton.inner = callbacks->path_change_receiver; + baton.inner_baton = callbacks->path_change_receiver_baton; + my_callbacks.path_change_receiver = record_inner_call; + my_callbacks.path_change_receiver_baton = &baton; + callbacks = &my_callbacks; } SVN_ERR(fill_log_entry(&log_entry, rev, fs, revprops, callbacks, pool)); @@ -1345,15 +1375,14 @@ revision. */ if (baton.found_rev_of_interest) { - apr_pool_t *scratch_pool; - /* Is REV a merged revision we've already sent? */ if (nested_merges && handling_merged_revision) { if (svn_bit_array__get(nested_merges, rev)) { - /* We already sent REV. */ - return SVN_NO_ERROR; + if (!baton.inner_got_used) + /* We already sent REV and no log was streamed yet. */ + return SVN_NO_ERROR; } else { @@ -1371,7 +1400,20 @@ &log_entry, scratch_pool)); svn_pool_destroy(scratch_pool); } + else if (baton.inner_got_used) + { + svn_repos_log_entry_t empty_log_entry = { 0 }; + /* Send the empty revision. */ + empty_log_entry.revision = SVN_INVALID_REVNUM; + /* Pass a scratch pool to ensure no temporary state stored + by the receiver callback persists. */ + scratch_pool = svn_pool_create(pool); + SVN_ERR(callbacks->revision_receiver(callbacks->revision_receiver_baton, + &empty_log_entry, scratch_pool)); + svn_pool_destroy(scratch_pool); + } + return SVN_NO_ERROR; } @@ -1719,8 +1761,8 @@ int limit, svn_boolean_t strict_node_history, svn_boolean_t include_merged_revisions, + svn_boolean_t subtractive_merge, svn_boolean_t handling_merged_revisions, - svn_boolean_t subtractive_merge, svn_boolean_t ignore_missing_locations, const apr_array_header_t *revprops, svn_boolean_t descending_order,