svn commit: r1935862 - in subversion/branches/svnsync-atomic-author/subversion: include include/private libsvn_ra_local libsvn_ra_serf libsvn_ra_svn libsvn_repos mod_dav_svn svnserve svnsync tests/cmdline tests/libsvn_repos
[email protected] Fri, 03 Jul 2026 20:59:45 -0000
| Newsgroups | gmane.comp.version-control.subversion.svn |
|---|---|
| Message-ID | <178311238575.35585.18388286149193632028@svn03-he-fi> |
Author: jpeck
Date: Fri Jul 3 20:59:45 2026
New Revision: 1935862
Log:
On the 'svnsync-atomic-author' branch:
Let svnsync set svn:author and svn:date in the commit itself when the
destination supports it, instead of patching them up with a post-commit
revprop change. This makes mirroring a revision atomic for ra_local
destinations and removes the window where a mirrored revision briefly
carries the wrong author and date.
See discussion: https://lists.apache.org/thread/qnxqd4whjp62rk2pgndt8xyp40oqnom2
Introduce SVN_RA_CAPABILITY_COMMIT_PRESERVES_AUTHOR_DATE, advertised by
ra_local and wired through the standard capability negotiation for svn://
and http:// so future servers can opt in without client changes. Rev the
repos-layer commit APIs to carry transaction flags (SVN_FS_TXN_CLIENT_DATE).
* subversion/include/svn_ra.h
(SVN_RA_CAPABILITY_COMMIT_PRESERVES_AUTHOR_DATE): New capability.
(svn_ra_get_commit_editor3): Allow svn:author/svn:date in REVPROP_TABLE
when the session advertises the new capability.
* subversion/include/svn_ra_svn.h
(SVN_RA_SVN_CAP_COMMIT_PRESERVES_AUTHOR_DATE): New wire capability.
* subversion/include/svn_dav.h
(SVN_DAV_NS_DAV_SVN_COMMIT_PRESERVES_AUTHOR_DATE): New OPTIONS header token.
* subversion/include/svn_repos.h
(svn_repos_get_commit_editor6, svn_repos_fs_begin_txn_for_commit3): New,
taking TXN_FLAGS.
(svn_repos_get_commit_editor5, svn_repos_fs_begin_txn_for_commit2):
Deprecate.
* subversion/include/private/svn_repos_private.h
(svn_repos__get_commit_ev2): Take TXN_FLAGS.
* subversion/libsvn_repos/commit.c
(edit_baton): Add TXN_FLAGS.
(open_root): Pass them to svn_repos_fs_begin_txn_for_commit3().
(svn_repos_get_commit_editor6, svn_repos__get_commit_ev2): Accept and
forward TXN_FLAGS.
* subversion/libsvn_repos/fs-wrap.c
(svn_repos_fs_begin_txn_for_commit3): New revision of ..._commit2(),
OR-ing TXN_FLAGS into SVN_FS_TXN_CHECK_LOCKS.
(svn_repos_fs_begin_txn_for_commit): Move to deprecated.c.
* subversion/libsvn_repos/deprecated.c
(svn_repos_get_commit_editor5, svn_repos_fs_begin_txn_for_commit2,
svn_repos_fs_begin_txn_for_commit): Implement as wrappers.
* subversion/libsvn_ra_local/ra_plugin.c
(prepare_commit_revprops): New helper: default svn:author to the session
username only when absent, and request SVN_FS_TXN_CLIENT_DATE iff the
* is committed; otherwise it will be overwritten with the commit
*
* @since New in 1.16.
svn_repos_t *repos,
t32_t txn_flags,
apr_pool_t *pool);
/** Like svn_repos_fs_begin_txn_for_commit3(), but with @a flags
/** Like svn_repos_fs_begin_txn_for_commit3(), but with @a txn_flags
* set to #SVN_FS_TXN_CHECK_LOCKS only.
*
* @note @a revprop_table may contain an #SVN_PROP_REVISION_DATE property,
Read 1 file
● Update(subversion\libsvn_repos\fs-wrap.c)
Added 1 line, removed 1 line
svn_repos_t *repos,
svn_revnum_t rev,
_t *pool)
{
apr_array_header_t *revprops;
● Update(subversion\libsvn_repos\fs-wrap.c)
Added 1 line, removed 1 line
/* Begin the transaction, ask for the fs to do on-the-fly lock checks.
We fetch its name, too, so the start-commit hook can use it. */
SVN_ERR(svn_fs_begin_txn2(&txn, repos->fs, rev,
SVN_FS_TXN_CHECK_LOCKS | flags, pool));
SVN_FS_TXN_CHECK_LOCKS | txn_flags, poo
err = svn_fs_txn_name(&txn_name, txn, pool);
if (err)
return svn_error_compose_create(err, svn_fs_abort_txn(txn, pool));
Fix 5c — state commit_callback nullability on editor6 directly (replacing the stale pre-1.6 parenthetical):
● Update(subversion\include\svn_repos.h)
Added 1 line, removed 2 lines
* returns an error, that error will be returned from @c close_edit,
* otherwise if there was a post-commit hook failure, then that error
* will be returned with code SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED.
* (Note that prior to Subversion 1.6, @a commit_callback cannot be @c NULL;
* if you don't need a callback, pass a dummy function.)
* @a commit_callback may be @c NULL.
*
* Calling @a (*editor)->abort_edit aborts the commit, and will also
* abort the commit transaction unless @a txn was supplied (not @c
You've hit your session limit · resets 9:50pm (Europe/London)
/upgrade or /usage-credits to finish what you’re working on.
✻ Baked for 5m 21s
❯ Can you put together a brief commit message for this change
Here's a commit message in the Apache Subversion log-message convention (summary paragraph plus per-file annotations), ready for svn commit -F:
On the 'svnsync-atomic-author' branch:
Let svnsync set svn:author and svn:date in the commit itself when the
destination supports it, instead of patching them up with a post-commit
revprop change. This makes mirroring a revision atomic for ra_local
destinations and removes the window where a mirrored revision briefly
ent changes. Rev the
repos-layer commit APIs to carry transaction flags (SVN_FS_TXN_CLIENT_DATE).
* subversion/include/svn_ra.h
(SVN_RA_CAPABILITY_COMMIT_PRESERVES_AUTHOR_DATE): New capability.
(svn_ra_get_commit_editor3): Allow svn:author/svn:date in REVPROP_TABLE
when the session advertises the new capability.
* subversion/include/svn_ra_svn.h
(SVN_RA_SVN_CAP_COMMIT_PRESERVES_AUTHOR_DATE): New wire capability.
* subversion/include/svn_dav.h
(SVN_DAV_NS_DAV_SVN_COMMIT_PRESERVES_AUTHOR_DATE): New OPTIONS header token.
* subversion/include/svn_repos.h
(svn_repos_get_commit_editor6, svn_repos_fs_begin_txn_for_commit3): New,
taking TXN_FLAGS.
(svn_repos_get_commit_editor5, svn_repos_fs_begin_txn_for_commit2):
* subversion/libsvn_repos/commit.c
(edit_baton): Add TXN_FLAGS.
(open_root): Pass them to svn_repos_fs_begin_txn_for_commit3().
(svn_repos_get_commit_editor6, svn_repos__get_commit_ev2): Accept and
forward TXN_FLAGS.
* subversion/libsvn_repos/fs-wrap.c
(svn_repos_fs_begin_txn_for_commit3): New revision of ..._commit2(),
OR-ing TXN_FLAGS into SVN_FS_TXN_CHECK_LOCKS.
(svn_repos_fs_begin_txn_for_commit): Move to deprecated.c.
* subversion/libsvn_repos/deprecated.c
(svn_repos_get_commit_editor5, svn_repos_fs_begin_txn_for_commit2,
svn_repos_fs_begin_txn_for_commit): Implement as wrappers.
* subversion/libsvn_ra_local/ra_plugin.c
(svn_ra_local__get_commit_editor, svn_ra_local__get_commit_ev2): Use it.
(svn_ra_local__has_capability): Advertise the new capability.
* subversion/libsvn_ra_svn/client.c
(ra_svn_has_capability): Map the new capability to its wire word.
* subversion/libsvn_ra_serf/options.c
(capabilities_headers_iterator_callback, options_response_handler):
Parse and pre-seed the new capability.
* subversion/mod_dav_svn/activity.c (dav_svn__create_txn),
subversion/mod_dav_svn/lock.c (append_locks),
subversion/svnserve/serve.c (commit):
Track the API revs; no functional change.
* subversion/svnsync/svnsync.c
once per sync how
revprops split between the commit and a follow-up revprop update.
(replay_rev_started, replay_rev_finished, do_synchronize): Use that
decision; skip the follow-up pass entirely when the commit carries
author/date.
* subversion/tests/libsvn_repos/repos-test.c
(commit_add_file, commit_editor_preserve_revprops): New test for both
SVN_FS_TXN_CLIENT_DATE polarities via the commit editor.
* subversion/tests/cmdline/svnsync_tests.py
(sync_to_local_preserves_author_date): New test: an ra_local sync
preserves author/date with no post-commit revprop copy.
Modified:
subversion/branches/svnsync-atomic-author/subversion/include/private/svn_repos_private.h
subversion/branches/svnsync-atomic-author/subversion/include/svn_dav.h
subversion/branches/svnsync-atomic-author/subversion/include/svn_ra.h
subversion/branches/svnsync-atomic-author/subversion/include/svn_ra_svn.h
subversion/branches/svnsync-atomic-author/subversion/include/svn_repos.h
subversion/branches/svnsync-atomic-author/subversion/libsvn_ra_local/ra_plugin.c
subversion/branches/svnsync-atomic-author/subversion/libsvn_ra_serf/options.c
subversion/branches/svnsync-atomic-author/subversion/libsvn_ra_svn/client.c
subversion/branches/svnsync-atomic-author/subversion/libsvn_repos/commit.c
subversion/branches/svnsync-atomic-author/subversion/libsvn_repos/deprecated.c
subversion/branches/svnsync-atomic-author/subversion/libsvn_repos/fs-wrap.c
subversion/branches/svnsync-atomic-author/subversion/mod_dav_svn/activity.c
subversion/branches/svnsync-atomic-author/subversion/mod_dav_svn/lock.c
subversion/branches/svnsync-atomic-author/subversion/svnserve/serve.c
subversion/branches/svnsync-atomic-author/subversion/svnsync/svnsync.c
subversion/branches/svnsync-atomic-author/subversion/tests/cmdline/svnsync_tests.py
subversion/branches/svnsync-atomic-author/subversion/tests/libsvn_repos/repos-test.c
Modified: subversion/branches/svnsync-atomic-author/subversion/include/private/svn_repos_private.h
==============================================================================
--- subversion/branches/svnsync-atomic-author/subversion/include/private/svn_repos_private.h Fri Jul 3 20:20:55 2026 (r1935861)
+++ subversion/branches/svnsync-atomic-author/subversion/include/private/svn_repos_private.h Fri Jul 3 20:59:45 2026 (r1935862)
@@ -131,7 +131,13 @@ svn_repos__fs_type(const char **fs_type,
apr_pool_t *pool);
-/* Create a commit editor for REPOS, based on REVISION. */
+/* Create a commit editor for REPOS, based on REVISION.
+ *
+ * TXN_FLAGS are additional transaction flags (e.g. SVN_FS_TXN_CLIENT_DATE)
+ * OR-ed with SVN_FS_TXN_CHECK_LOCKS, which is always applied. When
+ * SVN_FS_TXN_CLIENT_DATE is set, a caller-supplied SVN_PROP_REVISION_DATE
+ * in REVPROPS is preserved instead of being overwritten at commit time.
+ */
svn_error_t *
svn_repos__get_commit_ev2(svn_editor_t **editor,
svn_repos_t *repos,
@@ -139,6 +145,7 @@ svn_repos__get_commit_ev2(svn_editor_t *
const char *authz_repos_name,
const char *authz_user,
apr_hash_t *revprops,
+ apr_uint32_t txn_flags,
svn_commit_callback2_t commit_cb,
void *commit_baton,
svn_cancel_func_t cancel_func,
Modified: subversion/branches/svnsync-atomic-author/subversion/include/svn_dav.h
==============================================================================
--- subversion/branches/svnsync-atomic-author/subversion/include/svn_dav.h Fri Jul 3 20:20:55 2026 (r1935861)
+++ subversion/branches/svnsync-atomic-author/subversion/include/svn_dav.h Fri Jul 3 20:59:45 2026 (r1935862)
@@ -422,6 +422,16 @@ extern "C" {
#define SVN_DAV_NS_DAV_SVN_PUT_RESULT_CHECKSUM\
SVN_DAV_PROP_NS_DAV "svn/put-result-checksum"
+/** Presence of this in a DAV header in an OPTIONS response indicates
+ * that the transmitter (in this case, the server) preserves a
+ * client-supplied svn:author and svn:date in a commit's revision
+ * properties instead of overwriting them at commit time.
+ *
+ * @since New in 1.16.
+ */
+#define SVN_DAV_NS_DAV_SVN_COMMIT_PRESERVES_AUTHOR_DATE\
+ SVN_DAV_PROP_NS_DAV "svn/commit-preserves-author-date"
+
/** @} */
/** @} */
Modified: subversion/branches/svnsync-atomic-author/subversion/include/svn_ra.h
==============================================================================
--- subversion/branches/svnsync-atomic-author/subversion/include/svn_ra.h Fri Jul 3 20:20:55 2026 (r1935861)
+++ subversion/branches/svnsync-atomic-author/subversion/include/svn_ra.h Fri Jul 3 20:59:45 2026 (r1935862)
@@ -983,7 +983,10 @@ svn_ra_rev_prop(svn_ra_session_t *sessio
* names to @c svn_string_t property values. The commit log message
* is expected to be in the @c SVN_PROP_REVISION_LOG element. @a
* revprop_table can not contain either of @c SVN_PROP_REVISION_DATE
- * or @c SVN_PROP_REVISION_AUTHOR.
+ * or @c SVN_PROP_REVISION_AUTHOR, unless @a session advertises
+ * #SVN_RA_CAPABILITY_COMMIT_PRESERVES_AUTHOR_DATE. If it does, both
+ * properties may be supplied and will be preserved verbatim as the
+ * new revision's svn:date and svn:author (since 1.16).
*
* Before @c close_edit returns, but after the commit has succeeded,
* it will invoke @a commit_callback (if non-NULL) with filled-in
@@ -2288,6 +2291,21 @@ svn_ra_has_capability(svn_ra_session_t *
*/
#define SVN_RA_CAPABILITY_LIST "list"
+/**
+ * The capability of a destination to preserve a client-supplied
+ * svn:author and svn:date passed in a commit's revision properties,
+ * rather than overwriting them with the committing user and the commit
+ * time. This is useful for tools like svnsync that need to preserve
+ * the original author and date from the source repository.
+ *
+ * @note This is distinct from #SVN_RA_CAPABILITY_COMMIT_REVPROPS, which
+ * only indicates that arbitrary revision properties may be carried in a
+ * commit; svn:author and svn:date are still overwritten in that case.
+ *
+ * @since New in 1.16.
+ */
+#define SVN_RA_CAPABILITY_COMMIT_PRESERVES_AUTHOR_DATE \
+ "commit-preserves-author-date"
/* *** PLEASE READ THIS IF YOU ADD A NEW CAPABILITY ***
*
Modified: subversion/branches/svnsync-atomic-author/subversion/include/svn_ra_svn.h
==============================================================================
--- subversion/branches/svnsync-atomic-author/subversion/include/svn_ra_svn.h Fri Jul 3 20:20:55 2026 (r1935861)
+++ subversion/branches/svnsync-atomic-author/subversion/include/svn_ra_svn.h Fri Jul 3 20:59:45 2026 (r1935862)
@@ -71,6 +71,9 @@ extern "C" {
#define SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE "file-revs-reverse"
/* maps to SVN_RA_CAPABILITY_LIST */
#define SVN_RA_SVN_CAP_LIST "list"
+/* maps to SVN_RA_CAPABILITY_COMMIT_PRESERVES_AUTHOR_DATE */
+#define SVN_RA_SVN_CAP_COMMIT_PRESERVES_AUTHOR_DATE \
+ "commit-preserves-author-date"
/** ra_svn passes @c svn_dirent_t fields over the wire as a list of
Modified: subversion/branches/svnsync-atomic-author/subversion/include/svn_repos.h
==============================================================================
--- subversion/branches/svnsync-atomic-author/subversion/include/svn_repos.h Fri Jul 3 20:20:55 2026 (r1935861)
+++ subversion/branches/svnsync-atomic-author/subversion/include/svn_repos.h Fri Jul 3 20:59:45 2026 (r1935862)
@@ -1540,8 +1540,12 @@ svn_repos_replay(svn_fs_root_t *root,
* of the commit transaction, including author and log message if
* present.
*
- * @note #SVN_PROP_REVISION_DATE may be present in @a revprop_table, but
- * it will be overwritten when the transaction is committed.
+ * @a txn_flags is passed to svn_repos_fs_begin_txn_for_commit3() when
+ * creating a new transaction.
+ *
+ * @note If #SVN_FS_TXN_CLIENT_DATE is included in @a txn_flags, the
+ * #SVN_PROP_REVISION_DATE in @a revprop_table will be preserved as
+ * the commit date; otherwise it will be overwritten with the commit time.
*
* Iff @a authz_callback is provided, check read/write authorizations
* on paths accessed by editor operations. An operation which fails
@@ -1550,6 +1554,9 @@ svn_repos_replay(svn_fs_root_t *root,
*
* Calling @a (*editor)->close_edit completes the commit.
*
+ * @a commit_callback may be @c NULL if the caller does not need to be
+ * notified of the commit result.
+ *
* If @a commit_callback is non-NULL, then before @c close_edit returns (but
* after the commit has succeeded) @c close_edit will invoke
* @a commit_callback with a filled-in #svn_commit_info_t *, @a commit_baton,
@@ -1566,7 +1573,7 @@ svn_repos_replay(svn_fs_root_t *root,
* NULL). Callers who supply their own transactions are responsible
* for cleaning them up (either by committing them, or aborting them).
*
- * @since New in 1.5. Since 1.6, @a commit_callback can be @c NULL.
+ * @since New in 1.16.
*
* @note Yes, @a repos_url_decoded is a <em>decoded</em> URL. We realize
* that's sorta wonky. Sorry about that.
@@ -1576,6 +1583,32 @@ svn_repos_replay(svn_fs_root_t *root,
* methods is a full, URI-encoded URL, not a relative path.
*/
svn_error_t *
+svn_repos_get_commit_editor6(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_repos_t *repos,
+ svn_fs_txn_t *txn,
+ const char *repos_url_decoded,
+ const char *base_path,
+ apr_hash_t *revprop_table,
+ apr_uint32_t txn_flags,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ svn_repos_authz_callback_t authz_callback,
+ void *authz_baton,
+ apr_pool_t *pool);
+
+/**
+ * Similar to svn_repos_get_commit_editor6(), but with @a txn_flags
+ * always set to 0.
+ *
+ * @note #SVN_PROP_REVISION_DATE may be present in @a revprop_table, but
+ * it will be overwritten when the transaction is committed.
+ *
+ * @since New in 1.5. Since 1.6, @a commit_callback can be @c NULL.
+ * @deprecated Provided for backward compatibility with the 1.15 API.
+ */
+SVN_DEPRECATED
+svn_error_t *
svn_repos_get_commit_editor5(const svn_delta_editor_t **editor,
void **edit_baton,
svn_repos_t *repos,
@@ -2510,16 +2543,42 @@ svn_repos_fs_commit_txn(const char **con
* repository object which contains the filesystem. @a rev, @a
* *txn_p, and @a pool are as in svn_fs_begin_txn().
*
+ * @a txn_flags are additional transaction flags OR-ed with
+ * #SVN_FS_TXN_CHECK_LOCKS, which is always applied; passing 0 therefore
+ * yields the same lock-checking behavior as the older
+ * svn_repos_fs_begin_txn_for_commit2().
+ *
* Before a txn is created, the repository's start-commit hooks are
* run; if any of them fail, no txn is created, @a *txn_p is unaffected,
* and #SVN_ERR_REPOS_HOOK_FAILURE is returned.
*
* @note @a revprop_table may contain an #SVN_PROP_REVISION_DATE property,
+ * which will be set on the transaction. If #SVN_FS_TXN_CLIENT_DATE is
+ * included in @a txn_flags, this date will be preserved when the transaction
+ * is committed; otherwise it will be overwritten with the commit time.
+ *
+ * @since New in 1.16.
+ */
+svn_error_t *
+svn_repos_fs_begin_txn_for_commit3(svn_fs_txn_t **txn_p,
+ svn_repos_t *repos,
+ svn_revnum_t rev,
+ apr_hash_t *revprop_table,
+ apr_uint32_t txn_flags,
+ apr_pool_t *pool);
+
+
+/** Like svn_repos_fs_begin_txn_for_commit3(), but with @a txn_flags
+ * set to #SVN_FS_TXN_CHECK_LOCKS only.
+ *
+ * @note @a revprop_table may contain an #SVN_PROP_REVISION_DATE property,
* which will be set on the transaction, but that will be overwritten
* when the transaction is committed.
*
* @since New in 1.5.
+ * @deprecated Provided for backward compatibility with the 1.15 API.
*/
+SVN_DEPRECATED
svn_error_t *
svn_repos_fs_begin_txn_for_commit2(svn_fs_txn_t **txn_p,
svn_repos_t *repos,
Modified: subversion/branches/svnsync-atomic-author/subversion/libsvn_ra_local/ra_plugin.c
==============================================================================
--- subversion/branches/svnsync-atomic-author/subversion/libsvn_ra_local/ra_plugin.c Fri Jul 3 20:20:55 2026 (r1935861)
+++ subversion/branches/svnsync-atomic-author/subversion/libsvn_ra_local/ra_plugin.c Fri Jul 3 20:59:45 2026 (r1935862)
@@ -861,6 +861,28 @@ remap_commit_callback(svn_commit_callbac
}
}
+/* Prepare REVPROPS, a mutable revprop hash owned by the caller, for an
+ ra_local commit and return the transaction flags it requires.
+
+ When the caller did not supply an svn:author, default it to USERNAME.
+ Return SVN_FS_TXN_CLIENT_DATE iff the caller supplied an svn:date, so the
+ filesystem preserves that date instead of stamping the commit time.
+
+ This lets tools like svnsync carry the original author and date through
+ the commit itself. Any allocations are made in POOL. */
+static apr_uint32_t
+prepare_commit_revprops(apr_hash_t *revprops,
+ const char *username,
+ apr_pool_t *pool)
+{
+ if (! svn_hash_gets(revprops, SVN_PROP_REVISION_AUTHOR))
+ svn_hash_sets(revprops, SVN_PROP_REVISION_AUTHOR,
+ svn_string_create(username, pool));
+
+ return svn_hash_gets(revprops, SVN_PROP_REVISION_DATE)
+ ? SVN_FS_TXN_CLIENT_DATE : 0;
+}
+
static svn_error_t *
svn_ra_local__get_commit_editor(svn_ra_session_t *session,
const svn_delta_editor_t **editor,
@@ -874,6 +896,7 @@ svn_ra_local__get_commit_editor(svn_ra_s
{
svn_ra_local__session_baton_t *sess = session->priv;
struct deltify_etc_baton *deb = apr_palloc(pool, sizeof(*deb));
+ apr_uint32_t txn_flags;
/* Set repos_root_url in commit info */
remap_commit_callback(&callback, &callback_baton, session,
@@ -896,20 +919,20 @@ svn_ra_local__get_commit_editor(svn_ra_s
SVN_ERR(apply_lock_tokens(sess->fs, sess->fs_path->data, lock_tokens,
session->pool, pool));
- /* Copy the revprops table so we can add the username. */
+ /* Copy the revprops table so we can add/modify properties. */
revprop_table = apr_hash_copy(pool, revprop_table);
- svn_hash_sets(revprop_table, SVN_PROP_REVISION_AUTHOR,
- svn_string_create(sess->username, pool));
+ txn_flags = prepare_commit_revprops(revprop_table, sess->username, pool);
+
svn_hash_sets(revprop_table, SVN_PROP_TXN_CLIENT_COMPAT_VERSION,
svn_string_create(SVN_VER_NUMBER, pool));
svn_hash_sets(revprop_table, SVN_PROP_TXN_USER_AGENT,
svn_string_create(sess->useragent, pool));
/* Get the repos commit-editor */
- return svn_repos_get_commit_editor5
+ return svn_repos_get_commit_editor6
(editor, edit_baton, sess->repos, NULL,
svn_path_uri_decode(sess->repos_url, pool), sess->fs_path->data,
- revprop_table, deltify_etc, deb, NULL, NULL, pool);
+ revprop_table, txn_flags, deltify_etc, deb, NULL, NULL, pool);
}
@@ -1674,6 +1697,7 @@ svn_ra_local__has_capability(svn_ra_sess
|| strcmp(capability, SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS) == 0
|| strcmp(capability, SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE) == 0
|| strcmp(capability, SVN_RA_CAPABILITY_LIST) == 0
+ || strcmp(capability, SVN_RA_CAPABILITY_COMMIT_PRESERVES_AUTHOR_DATE) == 0
)
{
*has = TRUE;
@@ -1777,6 +1801,7 @@ svn_ra_local__get_commit_ev2(svn_editor_
{
svn_ra_local__session_baton_t *sess = session->priv;
struct deltify_etc_baton *deb = apr_palloc(result_pool, sizeof(*deb));
+ apr_uint32_t txn_flags;
remap_commit_callback(&commit_cb, &commit_baton, session,
commit_cb, commit_baton, result_pool);
@@ -1803,15 +1828,14 @@ svn_ra_local__get_commit_ev2(svn_editor_
SVN_ERR(apply_lock_tokens(sess->fs, sess->fs_path->data, lock_tokens,
session->pool, scratch_pool));
- /* Copy the REVPROPS and insert the author/username. */
+ /* Copy the REVPROPS so we can add/modify properties. */
revprops = apr_hash_copy(scratch_pool, revprops);
- svn_hash_sets(revprops, SVN_PROP_REVISION_AUTHOR,
- svn_string_create(sess->username, scratch_pool));
+ txn_flags = prepare_commit_revprops(revprops, sess->username, scratch_pool);
return svn_error_trace(svn_repos__get_commit_ev2(
editor, sess->repos, NULL /* authz */,
NULL /* authz_repos_name */, NULL /* authz_user */,
- revprops,
+ revprops, txn_flags,
deltify_etc, deb, cancel_func, cancel_baton,
result_pool, scratch_pool));
}
Modified: subversion/branches/svnsync-atomic-author/subversion/libsvn_ra_serf/options.c
==============================================================================
--- subversion/branches/svnsync-atomic-author/subversion/libsvn_ra_serf/options.c Fri Jul 3 20:20:55 2026 (r1935861)
+++ subversion/branches/svnsync-atomic-author/subversion/libsvn_ra_serf/options.c Fri Jul 3 20:59:45 2026 (r1935862)
@@ -251,6 +251,13 @@ capabilities_headers_iterator_callback(v
{
session->supports_put_result_checksum = TRUE;
}
+ if (svn_cstring_match_list(
+ SVN_DAV_NS_DAV_SVN_COMMIT_PRESERVES_AUTHOR_DATE, vals))
+ {
+ svn_hash_sets(session->capabilities,
+ SVN_RA_CAPABILITY_COMMIT_PRESERVES_AUTHOR_DATE,
+ capability_yes);
+ }
}
/* SVN-specific headers -- if present, server supports HTTP protocol v2 */
@@ -396,6 +403,9 @@ options_response_handler(serf_request_t
capability_no);
svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_LIST,
capability_no);
+ svn_hash_sets(session->capabilities,
+ SVN_RA_CAPABILITY_COMMIT_PRESERVES_AUTHOR_DATE,
+ capability_no);
/* Then see which ones we can discover. */
serf_bucket_headers_do(hdrs, capabilities_headers_iterator_callback,
Modified: subversion/branches/svnsync-atomic-author/subversion/libsvn_ra_svn/client.c
==============================================================================
--- subversion/branches/svnsync-atomic-author/subversion/libsvn_ra_svn/client.c Fri Jul 3 20:20:55 2026 (r1935861)
+++ subversion/branches/svnsync-atomic-author/subversion/libsvn_ra_svn/client.c Fri Jul 3 20:59:45 2026 (r1935862)
@@ -3079,6 +3079,8 @@ ra_svn_has_capability(svn_ra_session_t *
{SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE,
SVN_RA_SVN_CAP_GET_FILE_REVS_REVERSE},
{SVN_RA_CAPABILITY_LIST, SVN_RA_SVN_CAP_LIST},
+ {SVN_RA_CAPABILITY_COMMIT_PRESERVES_AUTHOR_DATE,
+ SVN_RA_SVN_CAP_COMMIT_PRESERVES_AUTHOR_DATE},
{NULL, NULL} /* End of list marker */
};
Modified: subversion/branches/svnsync-atomic-author/subversion/libsvn_repos/commit.c
==============================================================================
--- subversion/branches/svnsync-atomic-author/subversion/libsvn_repos/commit.c Fri Jul 3 20:20:55 2026 (r1935861)
+++ subversion/branches/svnsync-atomic-author/subversion/libsvn_repos/commit.c Fri Jul 3 20:59:45 2026 (r1935862)
@@ -88,6 +88,10 @@ struct edit_baton
/* Does this set of interfaces 'own' the commit transaction? */
svn_boolean_t txn_owner;
+ /* Flags to pass to svn_repos_fs_begin_txn_for_commit3() when
+ creating the transaction (e.g., SVN_FS_TXN_CLIENT_DATE). */
+ apr_uint32_t txn_flags;
+
/* svn transaction associated with this edit (created in
open_root, or supplied by the public API caller). */
svn_fs_txn_t *txn;
@@ -434,10 +438,11 @@ open_root(void *edit_baton,
make our own. */
if (eb->txn_owner)
{
- SVN_ERR(svn_repos_fs_begin_txn_for_commit2(&(eb->txn),
+ SVN_ERR(svn_repos_fs_begin_txn_for_commit3(&(eb->txn),
eb->repos,
youngest,
eb->revprop_table,
+ eb->txn_flags,
eb->pool));
}
else /* Even if we aren't the owner of the transaction, we might
@@ -992,13 +997,14 @@ fetch_base_func(const char **filename,
/*** Public interfaces. ***/
svn_error_t *
-svn_repos_get_commit_editor5(const svn_delta_editor_t **editor,
+svn_repos_get_commit_editor6(const svn_delta_editor_t **editor,
void **edit_baton,
svn_repos_t *repos,
svn_fs_txn_t *txn,
const char *repos_url_decoded,
const char *base_path,
apr_hash_t *revprop_table,
+ apr_uint32_t txn_flags,
svn_commit_callback2_t commit_callback,
void *commit_baton,
svn_repos_authz_callback_t authz_callback,
@@ -1058,6 +1064,7 @@ svn_repos_get_commit_editor5(const svn_d
eb->fs = svn_repos_fs(repos);
eb->txn = txn;
eb->txn_owner = txn == NULL;
+ eb->txn_flags = txn_flags;
*edit_baton = eb;
*editor = e;
@@ -1377,6 +1384,7 @@ svn_repos__get_commit_ev2(svn_editor_t *
const char *authz_repos_name,
const char *authz_user,
apr_hash_t *revprops,
+ apr_uint32_t txn_flags,
svn_commit_callback2_t commit_cb,
void *commit_baton,
svn_cancel_func_t cancel_func,
@@ -1420,7 +1428,7 @@ svn_repos__get_commit_ev2(svn_editor_t *
eb->commit_baton = commit_baton;
SVN_ERR(svn_fs__editor_create(&eb->inner, &eb->txn_name,
- repos->fs, SVN_FS_TXN_CHECK_LOCKS,
+ repos->fs, SVN_FS_TXN_CHECK_LOCKS | txn_flags,
cancel_func, cancel_baton,
result_pool, scratch_pool));
Modified: subversion/branches/svnsync-atomic-author/subversion/libsvn_repos/deprecated.c
==============================================================================
--- subversion/branches/svnsync-atomic-author/subversion/libsvn_repos/deprecated.c Fri Jul 3 20:20:55 2026 (r1935861)
+++ subversion/branches/svnsync-atomic-author/subversion/libsvn_repos/deprecated.c Fri Jul 3 20:59:45 2026 (r1935862)
@@ -46,6 +46,27 @@
/*** From commit.c ***/
svn_error_t *
+svn_repos_get_commit_editor5(const svn_delta_editor_t **editor,
+ void **edit_baton,
+ svn_repos_t *repos,
+ svn_fs_txn_t *txn,
+ const char *repos_url_decoded,
+ const char *base_path,
+ apr_hash_t *revprop_table,
+ svn_commit_callback2_t commit_callback,
+ void *commit_baton,
+ svn_repos_authz_callback_t authz_callback,
+ void *authz_baton,
+ apr_pool_t *pool)
+{
+ return svn_repos_get_commit_editor6(editor, edit_baton, repos, txn,
+ repos_url_decoded, base_path,
+ revprop_table, 0,
+ commit_callback, commit_baton,
+ authz_callback, authz_baton, pool);
+}
+
+svn_error_t *
svn_repos_get_commit_editor4(const svn_delta_editor_t **editor,
void **edit_baton,
svn_repos_t *repos,
@@ -411,6 +432,36 @@ svn_repos_replay(svn_fs_root_t *root,
/*** From fs-wrap.c ***/
svn_error_t *
+svn_repos_fs_begin_txn_for_commit2(svn_fs_txn_t **txn_p,
+ svn_repos_t *repos,
+ svn_revnum_t rev,
+ apr_hash_t *revprop_table,
+ apr_pool_t *pool)
+{
+ return svn_repos_fs_begin_txn_for_commit3(txn_p, repos, rev, revprop_table,
+ 0, pool);
+}
+
+svn_error_t *
+svn_repos_fs_begin_txn_for_commit(svn_fs_txn_t **txn_p,
+ svn_repos_t *repos,
+ svn_revnum_t rev,
+ const char *author,
+ const char *log_msg,
+ apr_pool_t *pool)
+{
+ apr_hash_t *revprop_table = apr_hash_make(pool);
+ if (author)
+ svn_hash_sets(revprop_table, SVN_PROP_REVISION_AUTHOR,
+ svn_string_create(author, pool));
+ if (log_msg)
+ svn_hash_sets(revprop_table, SVN_PROP_REVISION_LOG,
+ svn_string_create(log_msg, pool));
+ return svn_repos_fs_begin_txn_for_commit2(txn_p, repos, rev, revprop_table,
+ pool);
+}
+
+svn_error_t *
svn_repos_fs_change_rev_prop3(svn_repos_t *repos,
svn_revnum_t rev,
const char *author,
Modified: subversion/branches/svnsync-atomic-author/subversion/libsvn_repos/fs-wrap.c
==============================================================================
--- subversion/branches/svnsync-atomic-author/subversion/libsvn_repos/fs-wrap.c Fri Jul 3 20:20:55 2026 (r1935861)
+++ subversion/branches/svnsync-atomic-author/subversion/libsvn_repos/fs-wrap.c Fri Jul 3 20:59:45 2026 (r1935862)
@@ -129,10 +129,11 @@ svn_repos_fs_commit_txn(const char **con
svn_error_t *
-svn_repos_fs_begin_txn_for_commit2(svn_fs_txn_t **txn_p,
+svn_repos_fs_begin_txn_for_commit3(svn_fs_txn_t **txn_p,
svn_repos_t *repos,
svn_revnum_t rev,
apr_hash_t *revprop_table,
+ apr_uint32_t txn_flags,
apr_pool_t *pool)
{
apr_array_header_t *revprops;
@@ -149,7 +150,7 @@ svn_repos_fs_begin_txn_for_commit2(svn_f
/* Begin the transaction, ask for the fs to do on-the-fly lock checks.
We fetch its name, too, so the start-commit hook can use it. */
SVN_ERR(svn_fs_begin_txn2(&txn, repos->fs, rev,
- SVN_FS_TXN_CHECK_LOCKS, pool));
+ SVN_FS_TXN_CHECK_LOCKS | txn_flags, pool));
err = svn_fs_txn_name(&txn_name, txn, pool);
if (err)
return svn_error_compose_create(err, svn_fs_abort_txn(txn, pool));
@@ -176,26 +177,6 @@ svn_repos_fs_begin_txn_for_commit2(svn_f
}
-svn_error_t *
-svn_repos_fs_begin_txn_for_commit(svn_fs_txn_t **txn_p,
- svn_repos_t *repos,
- svn_revnum_t rev,
- const char *author,
- const char *log_msg,
- apr_pool_t *pool)
-{
- apr_hash_t *revprop_table = apr_hash_make(pool);
- if (author)
- svn_hash_sets(revprop_table, SVN_PROP_REVISION_AUTHOR,
- svn_string_create(author, pool));
- if (log_msg)
- svn_hash_sets(revprop_table, SVN_PROP_REVISION_LOG,
- svn_string_create(log_msg, pool));
- return svn_repos_fs_begin_txn_for_commit2(txn_p, repos, rev, revprop_table,
- pool);
-}
-
-
/*** Property wrappers ***/
svn_error_t *
Modified: subversion/branches/svnsync-atomic-author/subversion/mod_dav_svn/activity.c
==============================================================================
--- subversion/branches/svnsync-atomic-author/subversion/mod_dav_svn/activity.c Fri Jul 3 20:20:55 2026 (r1935861)
+++ subversion/branches/svnsync-atomic-author/subversion/mod_dav_svn/activity.c Fri Jul 3 20:59:45 2026 (r1935862)
@@ -256,8 +256,9 @@ dav_svn__create_txn(dav_svn_repos *repos
repos->pool);
}
- serr = svn_repos_fs_begin_txn_for_commit2(&txn, repos->repos, rev,
- revprops, repos->pool);
+ serr = svn_repos_fs_begin_txn_for_commit3(&txn, repos->repos, rev,
+ revprops, 0 /* flags */,
+ repos->pool);
if (serr != NULL)
{
return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
Modified: subversion/branches/svnsync-atomic-author/subversion/mod_dav_svn/lock.c
==============================================================================
--- subversion/branches/svnsync-atomic-author/subversion/mod_dav_svn/lock.c Fri Jul 3 20:20:55 2026 (r1935861)
+++ subversion/branches/svnsync-atomic-author/subversion/mod_dav_svn/lock.c Fri Jul 3 20:59:45 2026 (r1935862)
@@ -729,8 +729,9 @@ append_locks(dav_lockdb *lockdb,
"Could not determine youngest revision",
resource->pool);
- if ((serr = svn_repos_fs_begin_txn_for_commit2(&txn, repos->repos, rev,
+ if ((serr = svn_repos_fs_begin_txn_for_commit3(&txn, repos->repos, rev,
revprop_table,
+ 0 /* flags */,
resource->pool)))
return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
"Could not begin a transaction",
Modified: subversion/branches/svnsync-atomic-author/subversion/svnserve/serve.c
==============================================================================
--- subversion/branches/svnsync-atomic-author/subversion/svnserve/serve.c Fri Jul 3 20:20:55 2026 (r1935861)
+++ subversion/branches/svnsync-atomic-author/subversion/svnserve/serve.c Fri Jul 3 20:59:45 2026 (r1935862)
@@ -1547,11 +1547,11 @@ commit(svn_ra_svn_conn_t *conn,
ccb.date = &date;
ccb.author = &author;
ccb.post_commit_err = &post_commit_err;
- /* ### Note that svn_repos_get_commit_editor5 actually wants a decoded URL. */
- SVN_CMD_ERR(svn_repos_get_commit_editor5
+ /* ### Note that svn_repos_get_commit_editor6 actually wants a decoded URL. */
+ SVN_CMD_ERR(svn_repos_get_commit_editor6
(&editor, &edit_baton, b->repository->repos, NULL,
svn_path_uri_decode(b->repository->repos_url, pool),
- b->repository->fs_path->data, revprop_table,
+ b->repository->fs_path->data, revprop_table, 0,
commit_done, &ccb,
authz_commit_cb, &ab, pool));
SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
Modified: subversion/branches/svnsync-atomic-author/subversion/svnsync/svnsync.c
==============================================================================
--- subversion/branches/svnsync-atomic-author/subversion/svnsync/svnsync.c Fri Jul 3 20:20:55 2026 (r1935861)
+++ subversion/branches/svnsync-atomic-author/subversion/svnsync/svnsync.c Fri Jul 3 20:59:45 2026 (r1935862)
@@ -494,6 +494,14 @@ check_if_session_is_at_repos_root(svn_ra
}
+/* Return TRUE iff KEY names a property in the svnsync property namespace. */
+static svn_boolean_t
+is_svnsync_prop(const char *key)
+{
+ return strncmp(key, SVNSYNC_PROP_PREFIX,
+ sizeof(SVNSYNC_PROP_PREFIX) - 1) == 0;
+}
+
/* Remove the properties in TARGET_PROPS but not in SOURCE_PROPS from
* revision REV of the repository associated with RA session SESSION.
*
@@ -519,8 +527,7 @@ remove_props_not_in_source(svn_ra_sessio
svn_pool_clear(subpool);
- if (rev == 0 && !strncmp(propname, SVNSYNC_PROP_PREFIX,
- sizeof(SVNSYNC_PROP_PREFIX) - 1))
+ if (rev == 0 && is_svnsync_prop(propname))
continue;
/* Delete property if the name can't be found in SOURCE_PROPS. */
@@ -611,8 +618,7 @@ write_revprops(int *filtered_count,
svn_pool_clear(subpool);
- if (strncmp(propname, SVNSYNC_PROP_PREFIX,
- sizeof(SVNSYNC_PROP_PREFIX) - 1) != 0)
+ if (! is_svnsync_prop(propname))
{
if (old_rev_props)
{
@@ -1027,8 +1033,17 @@ typedef struct replay_baton_t {
svn_ra_session_t *to_session;
svn_revnum_t current_revision;
subcommand_baton_t *sb;
- svn_boolean_t has_commit_revprops_capability;
svn_boolean_t has_atomic_revprops_capability;
+
+ /* How each revision's properties are split between the commit and a
+ follow-up revprop update; resolved once by resolve_revprop_handling(),
+ which see. Each filter returns TRUE for the properties to *exclude*
+ from its pass. POST_COMMIT_FILTER is NULL when no follow-up pass is
+ needed. Properties in the svnsync bookkeeping namespace (svn:sync-*)
+ are never mirrored by either pass. */
+ filter_func_t commit_filter;
+ filter_func_t post_commit_filter;
+
int normalized_rev_props_count;
int normalized_node_props_count;
const char *to_root;
@@ -1070,15 +1085,9 @@ make_replay_baton(replay_baton_t **baton
static svn_boolean_t
filter_exclude_date_author_sync(const char *key)
{
- if (strcmp(key, SVN_PROP_REVISION_AUTHOR) == 0)
- return TRUE;
- else if (strcmp(key, SVN_PROP_REVISION_DATE) == 0)
- return TRUE;
- else if (strncmp(key, SVNSYNC_PROP_PREFIX,
- sizeof(SVNSYNC_PROP_PREFIX) - 1) == 0)
- return TRUE;
-
- return FALSE;
+ return strcmp(key, SVN_PROP_REVISION_AUTHOR) == 0
+ || strcmp(key, SVN_PROP_REVISION_DATE) == 0
+ || is_svnsync_prop(key);
}
/* Return FALSE iff KEY is the name of an svn:date or svn:author or any svnsync
@@ -1091,7 +1100,6 @@ filter_include_date_author_sync(const ch
return ! filter_exclude_date_author_sync(key);
}
-
/* Return TRUE iff KEY is the name of the svn:log property.
* Implements filter_func_t. Use with filter_props() to only exclude svn:log.
*/
@@ -1113,6 +1121,45 @@ filter_include_log(const char *key)
return ! filter_exclude_log(key);
}
+/* Resolve, from the destination capabilities HAS_COMMIT_PRESERVES_AUTHOR_DATE
+ * and HAS_COMMIT_REVPROPS, how each revision's properties are divided between
+ * the commit itself and a follow-up revprop update, storing the decision on
+ * RB. RB->COMMIT_FILTER excludes properties from the commit.
+ * RB->POST_COMMIT_FILTER excludes properties from the follow-up update, or
+ * is NULL when no follow-up update is needed at all. The two filters are
+ * not exact complements: properties in the svnsync bookkeeping namespace
+ * (svn:sync-*) are excluded from both passes, since they describe the
+ * mirror relationship itself and must not be mirrored as payload. */
+static void
+resolve_revprop_handling(replay_baton_t *rb,
+ svn_boolean_t has_commit_preserves_author_date,
+ svn_boolean_t has_commit_revprops)
+{
+ if (has_commit_preserves_author_date)
+ {
+ /* The destination preserves a client-supplied svn:author/svn:date in
+ the commit (e.g. file:// via ra_local), so commit every property
+ except the svnsync bookkeeping ones and leave nothing for a
+ follow-up. */
+ rb->commit_filter = is_svnsync_prop;
+ rb->post_commit_filter = NULL;
+ }
+ else if (has_commit_revprops)
+ {
+ /* The destination accepts revprops in the commit but overwrites
+ svn:author/svn:date, so those are copied afterwards. */
+ rb->commit_filter = filter_exclude_date_author_sync;
+ rb->post_commit_filter = filter_include_date_author_sync;
+ }
+ else
+ {
+ /* The destination only carries svn:log through the commit; every other
+ property is copied afterwards. */
+ rb->commit_filter = filter_include_log;
+ rb->post_commit_filter = filter_exclude_log;
+ }
+}
+
#ifdef ENABLE_EV2_SHIMS
static svn_error_t *
fetch_base_func(const char **filename,
@@ -1281,18 +1328,12 @@ replay_rev_started(svn_revnum_t revision
svn_string_createf(pool, "%ld", revision),
pool));
- /* The actual copy is just a replay hooked up to a commit. Include
- all the revision properties from the source repositories, except
- 'svn:author' and 'svn:date', those are not guaranteed to get
- through the editor anyway.
- If we're syncing to an non-commit-revprops capable server, filter
- out all revprops except svn:log and add them later in
- revplay_rev_finished. */
- filtered = filter_props(&filtered_count, rev_props,
- (rb->has_commit_revprops_capability
- ? filter_exclude_date_author_sync
- : filter_include_log),
- pool);
+ /* The actual copy is just a replay hooked up to a commit. Send through
+ the commit editor whichever revision properties the destination can
+ carry in the commit itself; rb->commit_filter encodes that choice (see
+ resolve_revprop_handling()). Any properties it leaves behind are added
+ later in replay_rev_finished(). */
+ filtered = filter_props(&filtered_count, rev_props, rb->commit_filter, pool);
/* svn_ra_get_commit_editor3 requires the log message to be
set. It's possible that we didn't receive 'svn:log' here, so we
@@ -1353,9 +1394,8 @@ replay_rev_finished(svn_revnum_t revisio
{
apr_pool_t *subpool = svn_pool_create(pool);
replay_baton_t *rb = replay_baton;
- apr_hash_t *filtered, *existing_props;
- int filtered_count;
- int normalized_count;
+ apr_hash_t *existing_props;
+ int filtered_count = 0;
const svn_string_t *rev_str;
SVN_ERR(editor->close_edit(edit_baton, pool));
@@ -1371,24 +1411,28 @@ replay_rev_finished(svn_revnum_t revisio
subpool));
- /* Ok, we're done with the data, now we just need to copy the remaining
- 'svn:date' and 'svn:author' revprops and we're all set.
- If the server doesn't support revprops-in-a-commit, we still have to
- set all revision properties except svn:log. */
- filtered = filter_props(&filtered_count, rev_props,
- (rb->has_commit_revprops_capability
- ? filter_include_date_author_sync
- : filter_exclude_log),
- subpool);
+ /* Now copy whatever revision properties the commit could not carry.
+ rb->post_commit_filter excludes the properties already handled in
+ replay_rev_started(); it is NULL when no follow-up update is needed at
+ all. See resolve_revprop_handling(). */
+ if (rb->post_commit_filter)
+ {
+ apr_hash_t *filtered;
+ int normalized_count;
- /* If necessary, normalize encoding and line ending style, and add the number
- of EOL-normalized properties to the overall count in the replay baton. */
- SVN_ERR(svnsync_normalize_revprops(filtered, &normalized_count,
- rb->sb->source_prop_encoding, pool));
- rb->normalized_rev_props_count += normalized_count;
+ filtered = filter_props(&filtered_count, rev_props,
+ rb->post_commit_filter, subpool);
- SVN_ERR(write_revprops(&filtered_count, rb->to_session, revision, filtered,
- NULL, subpool));
+ /* If necessary, normalize encoding and line ending style, and add the
+ number of EOL-normalized properties to the overall count in the
+ replay baton. */
+ SVN_ERR(svnsync_normalize_revprops(filtered, &normalized_count,
+ rb->sb->source_prop_encoding, pool));
+ rb->normalized_rev_props_count += normalized_count;
+
+ SVN_ERR(write_revprops(&filtered_count, rb->to_session, revision,
+ filtered, NULL, subpool));
+ }
/* Remove all extra properties in TARGET. */
SVN_ERR(remove_props_not_in_source(rb->to_session, revision,
@@ -1415,8 +1459,11 @@ replay_rev_finished(svn_revnum_t revisio
? &rev_str : NULL,
NULL, subpool));
- /* Notify the user that we copied revision properties. */
- if (! rb->sb->quiet)
+ /* Notify the user that we copied revision properties, unless the commit
+ itself carried them (post_commit_filter is NULL), in which case
+ commit_callback already printed "Committed revision X." and there is
+ nothing more to report. */
+ if (! rb->sb->quiet && rb->post_commit_filter)
SVN_ERR(log_properties_copied(filtered_count > 0, revision, subpool));
svn_pool_destroy(subpool);
@@ -1441,6 +1488,7 @@ do_synchronize(svn_ra_session_t *to_sess
svn_revnum_t to_latest, copying, last_merged;
svn_revnum_t start_revision, end_revision;
replay_baton_t *rb;
+ svn_boolean_t has_commit_revprops, has_commit_preserves_author_date;
int normalized_rev_props_count = 0;
SVN_ERR(open_source_session(&from_session, &last_merged_rev,
@@ -1541,8 +1589,7 @@ do_synchronize(svn_ra_session_t *to_sess
/* For compatibility with older svnserve versions, check first if we
support adding revprops to the commit. */
- SVN_ERR(svn_ra_has_capability(rb->to_session,
- &rb->has_commit_revprops_capability,
+ SVN_ERR(svn_ra_has_capability(rb->to_session, &has_commit_revprops,
SVN_RA_CAPABILITY_COMMIT_REVPROPS,
pool));
@@ -1551,6 +1598,24 @@ do_synchronize(svn_ra_session_t *to_sess
SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
pool));
+ /* Check if the destination supports setting author/date directly in
+ the commit */
+ SVN_ERR(svn_ra_has_capability(rb->to_session,
+ &has_commit_preserves_author_date,
+ SVN_RA_CAPABILITY_COMMIT_PRESERVES_AUTHOR_DATE,
+ pool));
+
+ /* Decide how each revision's properties are split between the
+ commit and a follow-up revprop update. */
+ resolve_revprop_handling(rb, has_commit_preserves_author_date,
+ has_commit_revprops);
+
+ if (! baton->quiet && has_commit_preserves_author_date)
+ SVN_ERR(svn_cmdline_printf(pool,
+ _("Destination supports commit-time author "
+ "and date; no post-commit revprop sync "
+ "needed.\n")));
+
start_revision = last_merged + 1;
end_revision = from_latest;
Modified: subversion/branches/svnsync-atomic-author/subversion/tests/cmdline/svnsync_tests.py
==============================================================================
--- subversion/branches/svnsync-atomic-author/subversion/tests/cmdline/svnsync_tests.py Fri Jul 3 20:20:55 2026 (r1935861)
+++ subversion/branches/svnsync-atomic-author/subversion/tests/cmdline/svnsync_tests.py Fri Jul 3 20:59:45 2026 (r1935862)
@@ -572,6 +572,56 @@ PROPS-END
# Compare the dump produced by the mirror repository with expected
verify_mirror(dest_sbox, dump_out)
+def sync_to_local_preserves_author_date(sbox):
+ "sync to a local mirror preserves author/date"
+
+ # Source repository with one real revision (the greek tree).
+ sbox.build(create_wc=False)
+
+ # Capture the source's r1 svn:author and svn:date.
+ exit_code, src_author, errput = svntest.main.run_svn(
+ None, "propget", "--revprop", "-r", "1", "svn:author", sbox.repo_url)
+ exit_code, src_date, errput = svntest.main.run_svn(
+ None, "propget", "--revprop", "-r", "1", "svn:date", sbox.repo_url)
+
+ # Empty destination mirror, addressed via file:// so ra_local is used.
+ dest_sbox = sbox.clone_dependent()
+ dest_sbox.build(create_wc=False, empty=True)
+
+ exit_code, output, errput = svntest.main.run_svnlook("uuid", sbox.repo_dir)
+ svntest.actions.run_and_verify_svnadmin2(None, None, 0,
+ 'setuuid', dest_sbox.repo_dir,
+ output[0][:-1])
+ svntest.actions.enable_revprop_changes(dest_sbox.repo_dir)
+
+ dest_repo_url = dest_sbox.file_protocol_repo_url()
+ run_init(dest_repo_url, sbox.repo_url)
+
+ # Sync. For an ra_local destination svnsync sets svn:author and svn:date
+ # during the commit itself, so the per-revision "Copied properties ..."
+ # line must NOT appear.
+ exit_code, output, errput = svntest.main.run_svnsync(
+ "synchronize", dest_repo_url, sbox.repo_url)
+ if errput:
+ raise SVNUnexpectedStderr(errput)
+ if [l for l in output if "Copied properties for revision 1" in l]:
+ raise svntest.Failure("svnsync performed a post-commit revprop copy "
+ "for an ra_local destination")
+
+ # WITHOUT a separate copy-revprops step, the mirror must already carry the
+ # original author and date.
+ exit_code, dst_author, errput = svntest.main.run_svn(
+ None, "propget", "--revprop", "-r", "1", "svn:author", dest_repo_url)
+ exit_code, dst_date, errput = svntest.main.run_svn(
+ None, "propget", "--revprop", "-r", "1", "svn:date", dest_repo_url)
+
+ if dst_author != src_author:
+ raise svntest.Failure("svn:author not preserved: expected %s, got %s"
+ % (src_author, dst_author))
+ if dst_date != src_date:
+ raise svntest.Failure("svn:date not preserved: expected %s, got %s"
+ % (src_date, dst_date))
+
def up_to_date_sync(sbox):
"""sync that does nothing"""
@@ -625,6 +675,7 @@ test_list = [ None,
fd_leak_sync_from_serf_to_local, # calls setrlimit
mergeinfo_contains_r0,
up_to_date_sync,
+ sync_to_local_preserves_author_date,
]
if __name__ == '__main__':
Modified: subversion/branches/svnsync-atomic-author/subversion/tests/libsvn_repos/repos-test.c
==============================================================================
--- subversion/branches/svnsync-atomic-author/subversion/tests/libsvn_repos/repos-test.c Fri Jul 3 20:20:55 2026 (r1935861)
+++ subversion/branches/svnsync-atomic-author/subversion/tests/libsvn_repos/repos-test.c Fri Jul 3 20:59:45 2026 (r1935862)
@@ -3326,9 +3326,9 @@ prop_validation_commit_with_revprop(cons
/* Make an arbitrary change and commit using above values... */
- SVN_ERR(svn_repos_get_commit_editor5(&editor, &edit_baton, repos,
+ SVN_ERR(svn_repos_get_commit_editor6(&editor, &edit_baton, repos,
NULL, "file://test", "/",
- revprop_table,
+ revprop_table, 0,
NULL, NULL, NULL, NULL, pool));
SVN_ERR(editor->open_root(edit_baton, 0, pool, &root_baton));
@@ -3411,7 +3411,112 @@ prop_validation(const svn_test_opts_t *o
}
-
+/* Drive the commit editor EDITOR/EDIT_BATON to add a single empty file
+ named FILENAME at the root, basing the txn on BASE_REV. */
+static svn_error_t *
+commit_add_file(const svn_delta_editor_t *editor,
+ void *edit_baton,
+ const char *filename,
+ svn_revnum_t base_rev,
+ apr_pool_t *pool)
+{
+ void *root_baton;
+ void *file_baton;
+
+ SVN_ERR(editor->open_root(edit_baton, base_rev, pool, &root_baton));
+ SVN_ERR(editor->add_file(filename, root_baton, NULL, SVN_INVALID_REVNUM,
+ pool, &file_baton));
+ SVN_ERR(editor->close_file(file_baton, NULL, pool));
+ SVN_ERR(editor->close_directory(root_baton, pool));
+ SVN_ERR(editor->close_edit(edit_baton, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Verify that svn_repos_get_commit_editor6() honors SVN_FS_TXN_CLIENT_DATE:
+ when the flag is set, a caller-supplied svn:author and svn:date survive
+ the commit verbatim; when it is not set, svn:date is overwritten with the
+ commit time. */
+static svn_error_t *
+commit_editor_preserve_revprops(const svn_test_opts_t *opts,
+ apr_pool_t *pool)
+{
+ svn_repos_t *repos;
+ svn_fs_t *fs;
+ const svn_delta_editor_t *editor;
+ void *edit_baton;
+ apr_hash_t *revprop_table;
+ svn_revnum_t youngest_rev;
+ svn_string_t *value;
+ const char *the_author = "the-original-author";
+ /* A fixed date in the past, distinct from the current commit time. */
+ const char *the_date = "2000-01-01T00:00:00.000000Z";
+
+ /* Create a filesystem and repository. */
+ SVN_ERR(svn_test__create_repos(
+ &repos, "test-repo-commit-editor-preserve-revprops", opts, pool));
+ fs = svn_repos_fs(repos);
+
+ /* --- Case 1: with SVN_FS_TXN_CLIENT_DATE the date is preserved. --- */
+ revprop_table = apr_hash_make(pool);
+ svn_hash_sets(revprop_table, SVN_PROP_REVISION_AUTHOR,
+ svn_string_create(the_author, pool));
+ svn_hash_sets(revprop_table, SVN_PROP_REVISION_DATE,
+ svn_string_create(the_date, pool));
+ svn_hash_sets(revprop_table, SVN_PROP_REVISION_LOG,
+ svn_string_create("preserve the date", pool));
+
+ SVN_ERR(svn_repos_get_commit_editor6(&editor, &edit_baton, repos, NULL,
+ "file://test", "/", revprop_table,
+ SVN_FS_TXN_CLIENT_DATE,
+ NULL, NULL, NULL, NULL, pool));
+ SVN_ERR(commit_add_file(editor, edit_baton, "/f1", 0, pool));
+
+ SVN_ERR(svn_fs_youngest_rev(&youngest_rev, fs, pool));
+
+ SVN_ERR(svn_fs_revision_prop(&value, fs, youngest_rev,
+ SVN_PROP_REVISION_AUTHOR, pool));
+ SVN_TEST_ASSERT(value != NULL);
+ SVN_TEST_STRING_ASSERT(value->data, the_author);
+
+ SVN_ERR(svn_fs_revision_prop(&value, fs, youngest_rev,
+ SVN_PROP_REVISION_DATE, pool));
+ SVN_TEST_ASSERT(value != NULL);
+ SVN_TEST_STRING_ASSERT(value->data, the_date);
+
+ /* --- Case 2: without the flag, svn:date is overwritten. --- */
+ revprop_table = apr_hash_make(pool);
+ svn_hash_sets(revprop_table, SVN_PROP_REVISION_AUTHOR,
+ svn_string_create(the_author, pool));
+ svn_hash_sets(revprop_table, SVN_PROP_REVISION_DATE,
+ svn_string_create(the_date, pool));
+ svn_hash_sets(revprop_table, SVN_PROP_REVISION_LOG,
+ svn_string_create("overwrite the date", pool));
+
+ SVN_ERR(svn_repos_get_commit_editor6(&editor, &edit_baton, repos, NULL,
+ "file://test", "/", revprop_table,
+ 0 /* no SVN_FS_TXN_CLIENT_DATE */,
+ NULL, NULL, NULL, NULL, pool));
+ SVN_ERR(commit_add_file(editor, edit_baton, "/f2", youngest_rev, pool));
+
+ SVN_ERR(svn_fs_youngest_rev(&youngest_rev, fs, pool));
+
+ /* The author is still taken verbatim from the revprop table. */
+ SVN_ERR(svn_fs_revision_prop(&value, fs, youngest_rev,
+ SVN_PROP_REVISION_AUTHOR, pool));
+ SVN_TEST_ASSERT(value != NULL);
+ SVN_TEST_STRING_ASSERT(value->data, the_author);
+
+ /* But the date must have been replaced with the commit time. */
+ SVN_ERR(svn_fs_revision_prop(&value, fs, youngest_rev,
+ SVN_PROP_REVISION_DATE, pool));
+ SVN_TEST_ASSERT(value != NULL);
+ SVN_TEST_ASSERT(strcmp(value->data, the_date) != 0);
+
+ return SVN_NO_ERROR;
+}
+
+
/* Tests for svn_repos_get_logsN() */
/* Log receiver which simple increments a counter. */
@@ -4128,8 +4233,8 @@ deprecated_access_context_api(const svn_
SVN_ERR(svn_fs_set_access(svn_repos_fs(repos), access));
/* Commit a new revision. */
- SVN_ERR(svn_repos_fs_begin_txn_for_commit2(&txn, repos, 0,
- apr_hash_make(pool), pool));
+ SVN_ERR(svn_repos_fs_begin_txn_for_commit3(&txn, repos, 0,
+ apr_hash_make(pool), 0, pool));
SVN_ERR(svn_fs_txn_root(&root, txn, pool));
SVN_ERR(svn_fs_make_dir(root, "/whatever", pool));
SVN_ERR(svn_repos_fs_commit_txn(&conflict, repos, &new_rev, txn, pool));
@@ -4391,8 +4496,8 @@ commit_aborted_txn(const svn_test_opts_t
opts, pool));
/* Create and abort the transaction. */
- SVN_ERR(svn_repos_fs_begin_txn_for_commit2(&txn, repos, 0,
- apr_hash_make(pool), pool));
+ SVN_ERR(svn_repos_fs_begin_txn_for_commit3(&txn, repos, 0,
+ apr_hash_make(pool), 0, pool));
SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
SVN_ERR(svn_fs_make_dir(txn_root, "/A", pool));
SVN_ERR(svn_fs_abort_txn(txn, pool));
@@ -4501,6 +4606,8 @@ static struct svn_test_descriptor_t test
"test reporter and svn_depth_exclude"),
SVN_TEST_OPTS_PASS(prop_validation,
"test if revprops are validated by repos"),
+ SVN_TEST_OPTS_PASS(commit_editor_preserve_revprops,
+ "test SVN_FS_TXN_CLIENT_DATE in the commit editor"),
SVN_TEST_OPTS_PASS(get_logs,
"test svn_repos_get_logs ranges and limits"),
SVN_TEST_OPTS_PASS(test_get_file_revs,