Re: [PATCH] Fix missing log-item close tag in on-the-wire XML
Franz Sirl <[email protected]> Tue, 15 Oct 2024 18:37:35 +0200
| Newsgroups | gmane.comp.version-control.subversion.user |
|---|---|
| Message-ID | <[email protected]> |
Am 2024-10-15 um 12:23 schrieb Daniel Sahlberg: > Den mån 14 okt. 2024 kl 13:07 skrev Franz Sirl <Franz.Sirl- > [email protected] <mailto:[email protected]>>: > > 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. > > > Thank you for the patch! > > In the discussion about issue 4177, Daniel Shahaf mentioned that there > was an inconsistency about the number of times the receiver callback was > called, so it seems likely you have found that issue. > > I'm going to test this further when time permits, hopefully this week. Hello Daniel, in the meantime I noticed that the previous patch streams out a lot of duplicated revisions. I've come up with v2 of the patch, which probably has a bit of unnecessary code because I'm not so sure about when and how this code recurses. The patch avoids the duplicates, but it also triggers a strange behavior with --limit. If I add for example a `--limit 1` to the log command, the XML generated by file: and http: start to differ a lot on the repository I used for testing. I don't know what to make of that... BTW, the SVN_ERR_ASSERT() in subversion/libsvn_ra_serf/log.c was just helpful to get a better error report. It's not necessary for the functionality of the patch. Hope this helps, Franz
subversion-1.14.4-missing-log-item-closure-v2.patch
(text/plain, 6.8 KB)
Index: subversion-1.14.x/subversion/libsvn_ra_serf/log.c
===================================================================
--- subversion-1.14.x/subversion/libsvn_ra_serf/log.c (revision 1921329)
+++ 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 1921329)
+++ subversion-1.14.x/subversion/libsvn_repos/log.c (working copy)
@@ -1216,6 +1216,15 @@
/* 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;
+
+ /* Set to TRUE if we need the simple mode of interesting_merge() */
+ svn_boolean_t simple_mode;
+
+ svn_boolean_t handling_merged_revision;
+ svn_bit_array__t *nested_merges;
+
/* We need to invoke this user-provided callback if not NULL. */
svn_repos_path_change_receiver_t inner;
void *inner_baton;
@@ -1235,36 +1244,40 @@
interesting_merge_baton_t *b = baton;
apr_hash_index_t *hi;
- if (b->inner)
- SVN_ERR(b->inner(b->inner_baton, change, scratch_pool));
-
- if (b->found_rev_of_interest)
- return SVN_NO_ERROR;
-
- /* Look at each path on the log target's mergeinfo. */
- for (hi = apr_hash_first(scratch_pool, b->log_target_history_as_mergeinfo);
- hi;
- hi = apr_hash_next(hi))
+ if (!(b->found_rev_of_interest || b->simple_mode))
{
- const char *mergeinfo_path = apr_hash_this_key(hi);
- svn_rangelist_t *rangelist = apr_hash_this_val(hi);
- /* Check whether CHANGED_PATH at revision REV is a child of
- a (path, revision) tuple in LOG_TARGET_HISTORY_AS_MERGEINFO. */
- if (svn_fspath__skip_ancestor(mergeinfo_path, change->path.data))
+ /* Look at each path on the log target's mergeinfo. */
+ for (hi = apr_hash_first(scratch_pool, b->log_target_history_as_mergeinfo);
+ hi;
+ hi = apr_hash_next(hi))
{
- int i;
+ const char *mergeinfo_path = apr_hash_this_key(hi);
+ svn_rangelist_t *rangelist = apr_hash_this_val(hi);
- for (i = 0; i < rangelist->nelts; i++)
+ /* Check whether CHANGED_PATH at revision REV is a child of
+ a (path, revision) tuple in LOG_TARGET_HISTORY_AS_MERGEINFO. */
+ if (svn_fspath__skip_ancestor(mergeinfo_path, change->path.data))
{
- svn_merge_range_t *range
- = APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *);
- if (b->rev > range->start && b->rev <= range->end)
- return SVN_NO_ERROR;
+ int i;
+
+ for (i = 0; i < rangelist->nelts; i++)
+ {
+ svn_merge_range_t *range
+ = APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *);
+ if (b->rev > range->start && b->rev <= range->end)
+ return SVN_NO_ERROR;
+ }
}
}
}
+ if (b->inner && !(b->nested_merges && b->handling_merged_revision && svn_bit_array__get(b->nested_merges, b->rev)))
+ {
+ SVN_ERR(b->inner(b->inner_baton, change, scratch_pool));
+ b->inner_got_used = TRUE;
+ }
+
b->found_rev_of_interest = TRUE;
return SVN_NO_ERROR;
@@ -1310,9 +1323,19 @@
{
svn_repos_log_entry_t log_entry = { 0 };
log_callbacks_t my_callbacks = *callbacks;
+ apr_pool_t *scratch_pool;
interesting_merge_baton_t baton;
+ baton.inner_got_used = FALSE;
+ baton.inner = callbacks->path_change_receiver;
+ baton.inner_baton = callbacks->path_change_receiver_baton;
+ baton.handling_merged_revision = handling_merged_revision;
+ baton.nested_merges = nested_merges;
+ my_callbacks.path_change_receiver = interesting_merge;
+ my_callbacks.path_change_receiver_baton = &baton;
+ callbacks = &my_callbacks;
+
/* Is REV a merged revision that is already part of
LOG_TARGET_HISTORY_AS_MERGEINFO? If so then there is no
need to send it, since it already was (or will be) sent.
@@ -1323,18 +1346,14 @@
&& apr_hash_count(log_target_history_as_mergeinfo))
{
baton.found_rev_of_interest = FALSE;
+ baton.simple_mode = FALSE;
baton.rev = rev;
baton.log_target_history_as_mergeinfo = log_target_history_as_mergeinfo;
- baton.inner = callbacks->path_change_receiver;
- baton.inner_baton = callbacks->path_change_receiver_baton;
-
- my_callbacks.path_change_receiver = interesting_merge;
- my_callbacks.path_change_receiver_baton = &baton;
- callbacks = &my_callbacks;
}
else
{
baton.found_rev_of_interest = TRUE;
+ baton.simple_mode = TRUE;
}
SVN_ERR(fill_log_entry(&log_entry, rev, fs, revprops, callbacks, pool));
@@ -1345,15 +1364,15 @@
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;
+ log_entry.revision = SVN_INVALID_REVNUM;
}
else
{
@@ -1371,7 +1390,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 +1751,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,