Author: svn-role
Date: Tue Apr 21 19:02:38 2026
New Revision: 1933215
Log:
Merge r1931549 from trunk:
* r1931549
Use bytewise content comparison in the "is the file modified?" working copy
checks if we have the pristine file content.
Justification:
Avoid changing the file comparison characteristics for working copies with
pristine contents.
Notes:
Discussed in
https://lists.apache.org/thread/128rgf7ozn72ljfzn7o18gfhb9l43y26
Votes:
+1: kotkov, rinrab, ivan
Modified:
subversion/branches/1.15.x/ (props changed)
subversion/branches/1.15.x/STATUS
subversion/branches/1.15.x/subversion/libsvn_wc/questions.c
Modified: subversion/branches/1.15.x/STATUS
==============================================================================
--- subversion/branches/1.15.x/STATUS Tue Apr 21 19:00:00 2026
(r1933214)
+++ subversion/branches/1.15.x/STATUS Tue Apr 21 19:02:38 2026
(r1933215)
@@ -57,14 +57,3 @@ Veto-blocked changes:
Approved changes:
=================
-
- * r1931549
- Use bytewise content comparison in the "is the file modified?" working copy
- checks if we have the pristine file content.
- Justification:
- Avoid changing the file comparison characteristics for working copies with
- pristine contents.
- Notes:
- Discussed in
https://lists.apache.org/thread/128rgf7ozn72ljfzn7o18gfhb9l43y26
- Votes:
- +1: kotkov, rinrab, ivan
Modified: subversion/branches/1.15.x/subversion/libsvn_wc/questions.c
==============================================================================
--- subversion/branches/1.15.x/subversion/libsvn_wc/questions.c Tue Apr 21
19:00:00 2026 (r1933214)
+++ subversion/branches/1.15.x/subversion/libsvn_wc/questions.c Tue Apr 21
19:02:38 2026 (r1933215)
@@ -77,14 +77,99 @@
mark it for removal?
*/
+static svn_error_t *
+compare_exact(svn_boolean_t *modified_p,
+ svn_stream_t *stream,
+ const char *eol_str,
+ apr_hash_t *keywords,
+ const svn_checksum_t *pristine_checksum,
+ svn_stream_t *pristine_stream,
+ const char *pristine_eol_str,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t same;
+
+ /* For exact comparison, we check that contents remain the same when
+ retranslated according to file properties. */
+
+ if (pristine_stream)
+ {
+ /* We have pristine contents: wrap a stream to translate it into working
+ copy form, and check that contents remain the same. */
+
+ pristine_stream = svn_subst_stream_translated(pristine_stream,
+ eol_str, FALSE,
+ keywords, TRUE,
+ scratch_pool);
+
+ SVN_ERR(svn_stream_contents_same2(&same, pristine_stream, stream,
+ scratch_pool));
+ }
+ else
+ {
+ svn_checksum_t *working_checksum;
+ svn_checksum_t *detranslated_checksum;
+ svn_checksum_t *retranslated_checksum;
+
+ /* We don't have pristine contents. To make the comparison work without
+ it, let's check for two things:
+
+ 1) That the checksum of the detranslated contents matches the recorded
+ pristine checksum, as in the case of a non-exact comparison, ...
+
+ 2) ...and additionally, that the contents of the working file does not
+ change when retranslated according to its properties.
+
+ Technically we're going to do that with a single read of the file
+ contents, while checksumming it's original, detranslated and
+ retranslated versions.
+ */
+
+ stream = svn_stream_checksummed2(stream,
+ &working_checksum, NULL,
+ pristine_checksum->kind, TRUE,
+ scratch_pool);
+
+ stream = svn_subst_stream_translated(stream,
+ pristine_eol_str, TRUE,
+ keywords, FALSE,
+ scratch_pool);
+ stream = svn_stream_checksummed2(stream,
+ &detranslated_checksum, NULL,
+ pristine_checksum->kind, TRUE,
+ scratch_pool);
+
+ stream = svn_subst_stream_translated(stream, eol_str, FALSE,
+ keywords, TRUE,
+ scratch_pool);
+ stream = svn_stream_checksummed2(stream,
+ &retranslated_checksum, NULL,
+ pristine_checksum->kind, TRUE,
+ scratch_pool);
+
+ SVN_ERR(svn_stream_copy3(stream, svn_stream_empty(scratch_pool),
+ NULL, NULL, scratch_pool));
+
+ same = svn_checksum_match(detranslated_checksum, pristine_checksum) &&
+ svn_checksum_match(working_checksum, retranslated_checksum);
+ }
+
+ *modified_p = !same;
+ return SVN_NO_ERROR;
+}
/* Set *MODIFIED_P to TRUE if (after translation) VERSIONED_FILE_ABSPATH
- * (of VERSIONED_FILE_SIZE bytes) differs from pristine file with checksum
- * PRISTINE_CHECKSUM, else to FALSE if not.
+ * (of VERSIONED_FILE_SIZE bytes) differs from PRISTINE_STREAM (of
+ * PRISTINE_SIZE bytes with PRISTINE_CHECKSUM), else to FALSE if not.
+ *
+ * If PRISTINE_STREAM is NULL, perform checksum-based content comparison.
+ * If PRISTINE_STREAM is not NULL, perform bytewise content comparison
+ * against the provided stream. PRISTINE_STREAM will be closed before
+ * a successful return.
*
* If EXACT_COMPARISON is FALSE, translate VERSIONED_FILE_ABSPATH's EOL
* style and keywords to repository-normal form according to its properties,
- * calculate checksum and compare the result with PRISTINE_CHECKSUM.
+ * and compare the result with pristine contents.
* If EXACT_COMPARISON is TRUE, also check that VERSIONED_FILE_ABSPATH
* contents remains the same when retranslated according to its properties.
*
@@ -94,6 +179,7 @@
* PROPS_MOD should be TRUE if the file's properties have been changed,
* otherwise FALSE.
*
+ *
* DB is a wc_db; use SCRATCH_POOL for temporary allocation.
*/
static svn_error_t *
@@ -101,6 +187,8 @@ compare_and_verify(svn_boolean_t *modifi
svn_wc__db_t *db,
const char *versioned_file_abspath,
svn_filesize_t versioned_file_size,
+ svn_stream_t *pristine_stream,
+ svn_filesize_t pristine_size,
const svn_checksum_t *pristine_checksum,
svn_boolean_t has_props,
svn_boolean_t props_mod,
@@ -113,8 +201,7 @@ compare_and_verify(svn_boolean_t *modifi
svn_boolean_t special = FALSE;
svn_boolean_t need_translation;
svn_stream_t *v_stream; /* versioned_file */
- svn_checksum_t *v_checksum;
- svn_error_t *err;
+ svn_boolean_t same;
SVN_ERR_ASSERT(svn_dirent_is_absolute(versioned_file_abspath));
@@ -140,20 +227,15 @@ compare_and_verify(svn_boolean_t *modifi
else
need_translation = FALSE;
- if (! need_translation)
+ /* Easy out check: different sizes with no translation mean the
+ * file was modified. */
+ if (!need_translation && versioned_file_size != pristine_size)
{
- svn_filesize_t pristine_size;
-
- SVN_ERR(svn_wc__db_pristine_read(NULL, &pristine_size, db,
- versioned_file_abspath,
pristine_checksum,
- scratch_pool, scratch_pool));
-
- if (versioned_file_size != pristine_size)
- {
- *modified_p = TRUE;
+ if (pristine_stream)
+ SVN_ERR(svn_stream_close(pristine_stream));
- return SVN_NO_ERROR;
- }
+ *modified_p = TRUE;
+ return SVN_NO_ERROR;
}
/* ### Other checks possible? */
@@ -169,13 +251,8 @@ compare_and_verify(svn_boolean_t *modifi
/* We don't use APR-level buffering because the comparison function
* will do its own buffering. */
apr_file_t *file;
- err = svn_io_file_open(&file, versioned_file_abspath, APR_READ,
- APR_OS_DEFAULT, scratch_pool);
- /* Convert EACCESS on working copy path to WC specific error code. */
- if (err && APR_STATUS_IS_EACCES(err->apr_err))
- return svn_error_create(SVN_ERR_WC_PATH_ACCESS_DENIED, err, NULL);
- else
- SVN_ERR(err);
+ SVN_ERR(svn_io_file_open(&file, versioned_file_abspath, APR_READ,
+ APR_OS_DEFAULT, scratch_pool));
v_stream = svn_stream_from_aprfile2(file, FALSE, scratch_pool);
if (need_translation)
@@ -188,79 +265,41 @@ compare_and_verify(svn_boolean_t *modifi
pristine_eol_str = eol_str;
if (exact_comparison)
- {
- svn_checksum_t *working_checksum;
- svn_checksum_t *detranslated_checksum;
- svn_checksum_t *retranslated_checksum;
-
- v_stream = svn_stream_checksummed2(v_stream,
- &working_checksum, NULL,
- pristine_checksum->kind, TRUE,
- scratch_pool);
-
- v_stream = svn_subst_stream_translated(v_stream,
- pristine_eol_str, TRUE,
- keywords, FALSE,
- scratch_pool);
- v_stream = svn_stream_checksummed2(v_stream,
- &detranslated_checksum, NULL,
- pristine_checksum->kind, TRUE,
- scratch_pool);
-
- v_stream = svn_subst_stream_translated(v_stream, eol_str, FALSE,
- keywords, TRUE,
- scratch_pool);
- v_stream = svn_stream_checksummed2(v_stream,
- &retranslated_checksum, NULL,
- pristine_checksum->kind, TRUE,
+ return svn_error_trace(compare_exact(modified_p,
+ v_stream, eol_str, keywords,
+ pristine_checksum,
+ pristine_stream,
+ pristine_eol_str,
+ scratch_pool));
+
+ /* Wrap file stream to detranslate into normal form,
+ * "repairing" the EOL style if it is inconsistent. */
+ v_stream = svn_subst_stream_translated(v_stream,
+ pristine_eol_str,
+ TRUE /* repair */,
+ keywords,
+ FALSE /* expand */,
scratch_pool);
-
- err = svn_stream_copy3(v_stream, svn_stream_empty(scratch_pool),
- NULL, NULL, scratch_pool);
- /* Convert EACCESS on working copy path to WC specific error
code. */
- if (err && APR_STATUS_IS_EACCES(err->apr_err))
- return svn_error_create(SVN_ERR_WC_PATH_ACCESS_DENIED, err,
NULL);
- else
- SVN_ERR(err);
-
- if (svn_checksum_match(detranslated_checksum, pristine_checksum)
&&
- svn_checksum_match(working_checksum, retranslated_checksum))
- {
- *modified_p = FALSE;
- }
- else
- {
- *modified_p = TRUE;
- }
-
- return SVN_NO_ERROR;
- }
- else
- {
- /* Wrap file stream to detranslate into normal form,
- * "repairing" the EOL style if it is inconsistent. */
- v_stream = svn_subst_stream_translated(v_stream,
- pristine_eol_str,
- TRUE /* repair */,
- keywords,
- FALSE /* expand */,
- scratch_pool);
- }
}
}
- /* Get checksum of detranslated (normalized) content. */
- err = svn_stream_contents_checksum(&v_checksum, v_stream,
- pristine_checksum->kind,
- scratch_pool, scratch_pool);
- /* Convert EACCESS on working copy path to WC specific error code. */
- if (err && APR_STATUS_IS_EACCES(err->apr_err))
- return svn_error_create(SVN_ERR_WC_PATH_ACCESS_DENIED, err, NULL);
+ if (pristine_stream)
+ {
+ SVN_ERR(svn_stream_contents_same2(&same, pristine_stream, v_stream,
+ scratch_pool));
+ }
else
- SVN_ERR(err);
+ {
+ svn_checksum_t *v_checksum;
- *modified_p = (! svn_checksum_match(v_checksum, pristine_checksum));
+ SVN_ERR(svn_stream_contents_checksum(&v_checksum, v_stream,
+ pristine_checksum->kind,
+ scratch_pool,
+ scratch_pool));
+ same = svn_checksum_match(v_checksum, pristine_checksum);
+ }
+ *modified_p = !same;
return SVN_NO_ERROR;
}
@@ -279,6 +318,9 @@ svn_wc__internal_file_modified_p(svn_boo
svn_boolean_t has_props;
svn_boolean_t props_mod;
const svn_io_dirent2_t *dirent;
+ svn_stream_t *pristine_stream;
+ svn_filesize_t pristine_size;
+ svn_error_t *err;
/* Read the relevant info */
SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL,
@@ -358,12 +400,23 @@ svn_wc__internal_file_modified_p(svn_boo
}
compare_them:
- /* Check all bytes, and verify checksum if requested. */
- SVN_ERR(compare_and_verify(modified_p, db,
- local_abspath, dirent->filesize,
- checksum, has_props, props_mod,
- exact_comparison,
- scratch_pool));
+ SVN_ERR(svn_wc__db_pristine_read(&pristine_stream, &pristine_size,
+ db, local_abspath, checksum,
+ scratch_pool, scratch_pool));
+
+ err = compare_and_verify(modified_p, db,
+ local_abspath, dirent->filesize,
+ pristine_stream, pristine_size,
+ checksum, has_props, props_mod,
+ exact_comparison,
+ scratch_pool);
+
+ /* At this point we already opened the pristine file, so we know that
+ the access denied applies to the working copy path. */
+ if (err && APR_STATUS_IS_EACCES(err->apr_err))
+ return svn_error_create(SVN_ERR_WC_PATH_ACCESS_DENIED, err, NULL);
+ else
+ SVN_ERR(err);
if (!*modified_p)
{