Konstantin Kolinko wrote on Thu, Oct 02, 2014 at 03:40:51 +0400: > 2014-10-02 2:59 GMT+04:00 Daniel Shahaf <d...@daniel.shahaf.name>: > > Julian Foad wrote on Wed, Oct 01, 2014 at 10:52:42 +0100: > >> Daniel Shahaf wrote in the thread "No no-op changes": > >> > Should we provide an "official" way to create an empty revision? That > >> > is, a revision whose changed-paths list is empty? > >> > > >> > Use-cases: > >> > > >> > 1. Suppose last backup is r100 and revisions r101:r105 were lost; then > >> > after restoring the backup, the admin would create 5 empty revisions. > >> > > >> > 2. Force an empty revision for whatever reason, such as to make the > >> > revnums sync to something: > >> > 2.1. See r3 of the regression test merge_tests.py#125 svnmucc_abuse_1(). > >> > 2.2. W hen loading our repository to the ASF repository, if Joe had > >> > created 26 empty revisions, then The Offset would have been 840100 > >> > rather than 840074, which would make our mental math easier. > >> > >> What should the author and log message be on the empty revs? I suppose > >> these need to be optionally specified, defaulting to blank? > >> > > My thought: > > svnadmin bump -m "message" REPOS_PATH > svnrdump bump -m "message" URL > > The command creates 1 empty revision and thus bumps the repository > revision number. It can be repeated in a loop as necessary. >
Two proof-of-concept patches implementing this are attached. They work, but some bells and whistles are missing (e.g., setting the log message from 'svnadmin bump' to a boilerplate value or to a user-provided value). I'm not going to commit them just yet --- I'd rather let the idea soak for a bit. (In part because the 1.9 branch point is around the corner.) > > The author of a revision created by svnadmin is "the administrator"; we > > have not defined a way to represent this value in an svn:author revprop. > > > > If dependent tools can deal with missing svn:author property then it > is OK to do not have one. > > Looking at authors.txt file used to configure svn-git mirroring for > ASF [1], there is the following line at the end of the file: > > (no author) = No Author <dev-n...@apache.org> > > So I think git-svn can deal with revisions that do not have svn:author > property, and thus it is safe to create such revisions. > And even if git-svn didn't implement this, it would _still_ be okay to create revisions with no svn:author revprops, since we stipulated that was permissible a decade+ ago when we defined our API. Naturally, if that were the case, we'd heads-up the git-svn folks and work with them to minimize the impact on users.
>From 4016382c6801028c0cabee63dbf89f885e198b74 Mon Sep 17 00:00:00 2001 From: Daniel Shahaf <d...@daniel.shahaf.name> Date: Thu, 2 Oct 2014 03:29:07 +0000 Subject: [PATCH 1/3] New feature: 'svnmucc bump'. This partially addresses issue #4521. * subversion/svnmucc/svnmucc.c (action_code_t.ACTION_BUMP): New enumerator. (sub_main): Parse 'bump'. (execute): Handle 'bump' (as a no-op). * subversion/libsvn_client/mtcc.c (svn_client__mtcc_commit): Permit the "no changes" case. * subversion/tests/cmdline/svnmucc_tests.py (bump): New unit test. --- subversion/libsvn_client/mtcc.c | 7 ------- subversion/svnmucc/svnmucc.c | 13 +++++++++++-- subversion/tests/cmdline/svnmucc_tests.py | 14 ++++++++++++++ 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/subversion/libsvn_client/mtcc.c b/subversion/libsvn_client/mtcc.c index 2fa40fb..2b3bdb2 100644 --- a/subversion/libsvn_client/mtcc.c +++ b/subversion/libsvn_client/mtcc.c @@ -1312,13 +1312,6 @@ svn_client__mtcc_commit(apr_hash_t *revprop_table, const char *session_url; const char *log_msg; - if (MTCC_UNMODIFIED(mtcc)) - { - /* No changes -> no revision. Easy out */ - svn_pool_destroy(mtcc->pool); - return SVN_NO_ERROR; - } - SVN_ERR(svn_ra_get_session_url(mtcc->ra_session, &session_url, scratch_pool)); if (mtcc->root_op->kind != OP_OPEN_DIR) diff --git a/subversion/svnmucc/svnmucc.c b/subversion/svnmucc/svnmucc.c index 1c4326b..a2df51a 100644 --- a/subversion/svnmucc/svnmucc.c +++ b/subversion/svnmucc/svnmucc.c @@ -88,6 +88,7 @@ commit_callback(const svn_commit_info_t *commit_info, } typedef enum action_code_t { + ACTION_BUMP, ACTION_MV, ACTION_MKDIR, ACTION_CP, @@ -212,6 +213,9 @@ execute(const apr_array_header_t *actions, action->prop_value, FALSE, mtcc, iterpool)); break; + case ACTION_BUMP: + /* Noop. */ + break; case ACTION_PROPSETF: default: SVN_ERR_MALFUNCTION_NO_RETURN(); @@ -280,6 +284,8 @@ help(FILE *stream, apr_pool_t *pool) " propset NAME VALUE URL : set property NAME on URL to VALUE\n" " propsetf NAME FILE URL : set property NAME on URL to value read from FILE\n" " propdel NAME URL : delete property NAME from URL\n" + " bump URL : force a new commit to URL's repository, even if\n" + " : no files or directories would be changed\n" "\n" "Valid options:\n" " -h, -? [--help] : display this text\n" @@ -733,6 +739,8 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) action->action = ACTION_PROPSETF; else if (! strcmp(action_string, "propdel")) action->action = ACTION_PROPDEL; + else if (! strcmp(action_string, "bump")) + action->action = ACTION_BUMP; else if (! strcmp(action_string, "?") || ! strcmp(action_string, "h") || ! strcmp(action_string, "help")) { @@ -836,6 +844,7 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) /* How many URLs does this action expect? */ if (action->action == ACTION_RM + || action->action == ACTION_BUMP || action->action == ACTION_MKDIR || action->action == ACTION_PUT || action->action == ACTION_PROPSET @@ -869,9 +878,9 @@ sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) url = sanitize_url(url, pool); action->path[j] = url; - /* The first URL arguments to 'cp', 'pd', 'ps' could be the anchor, - but the other URLs should be children of the anchor. */ + /* Most URLs should be children of the anchor, except for: */ if (! (action->action == ACTION_CP && j == 0) + && action->action != ACTION_BUMP && action->action != ACTION_PROPDEL && action->action != ACTION_PROPSET && action->action != ACTION_PROPSETF) diff --git a/subversion/tests/cmdline/svnmucc_tests.py b/subversion/tests/cmdline/svnmucc_tests.py index b24ada0..627fae9 100755 --- a/subversion/tests/cmdline/svnmucc_tests.py +++ b/subversion/tests/cmdline/svnmucc_tests.py @@ -452,6 +452,19 @@ rm A/B/C/Y svntest.actions.run_and_verify_svn(None, expected_output, [], 'log', '-qvr3', repo_url) +@Issues(4521) +def bump(sbox): + "svnmucc bump" + + sbox.build(create_wc=False) + svntest.actions.run_and_verify_svnmucc(None, None, [], + '-U', sbox.repo_url, '-m', "log message", + 'bump', "") + + # Verify that the changed-paths list is empty. + expected_output = [] + svntest.actions.run_and_verify_svnlook(None, expected_output, [], + "changed", sbox.repo_dir) ###################################################################### @@ -462,6 +475,7 @@ test_list = [ None, too_many_log_messages, no_log_msg_non_interactive, nested_replaces, + bump, ] if __name__ == '__main__': -- 1.7.10.4
>From bc755c6f20cd6cd31d193ee5e32abf0ef2ffbf14 Mon Sep 17 00:00:00 2001 From: Daniel Shahaf <d...@daniel.shahaf.name> Date: Thu, 2 Oct 2014 10:07:33 +0000 Subject: [PATCH 2/3] New feature: 'svnadmin bump'. This partially addresses issue #4521. * subversion/svnadmin/svnadmin.c (subcommand_bump, cmd_table."bump"): New subcommand. (bump_commit_callback): New helper function. * subversion/tests/cmdline/svnadmin_tests.py (bump): New unit test. --- subversion/svnadmin/svnadmin.c | 76 ++++++++++++++++++++++++++++ subversion/tests/cmdline/svnadmin_tests.py | 14 +++++ 2 files changed, 90 insertions(+) diff --git a/subversion/svnadmin/svnadmin.c b/subversion/svnadmin/svnadmin.c index 9391bac..1385b25 100644 --- a/subversion/svnadmin/svnadmin.c +++ b/subversion/svnadmin/svnadmin.c @@ -161,6 +161,7 @@ check_lib_versions(void) /** Subcommands. **/ static svn_opt_subcommand_t + subcommand_bump, subcommand_crashtest, subcommand_create, subcommand_delrevprop, @@ -342,6 +343,12 @@ static const apr_getopt_option_t options_table[] = */ static const svn_opt_subcommand_desc2_t cmd_table[] = { + {"bump", subcommand_bump, {0}, N_ + ("usage: svnadmin bump REPOS_PATH\n\n" + "Commit an empty revision to REPOS_PATH.\n"), + /* ### TODO: svnadmin__bypass_hooks, 'q', 'F', */ + {0} }, + {"crashtest", subcommand_crashtest, {0}, N_ ("usage: svnadmin crashtest REPOS_PATH\n\n" "Open the repository at REPOS_PATH, then abort, thus simulating\n" @@ -679,6 +686,75 @@ crashtest_malfunction_handler(svn_boolean_t can_return, return SVN_NO_ERROR; /* Not reached. */ } +/* This implements 'svn_error_malfunction_handler_t. */ +static svn_error_t * +bump_commit_callback(const svn_commit_info_t *commit_info, + void *baton, + apr_pool_t *pool) +{ + /* We print the post-commit error and revnum only. */ + + if (commit_info->post_commit_err) + SVN_ERR(svn_cmdline_fprintf(stderr, pool, + _("%swarning: Post-commit hook error: %s\n"), + "svnadmin: ", + commit_info->post_commit_err)); + + SVN_ERR(svn_cmdline_printf(pool, _("Committed empty revision r%ld.\n"), + commit_info->revision)); + + return SVN_NO_ERROR; +} + +/* This implements `svn_opt_subcommand_t'. */ +static svn_error_t * +subcommand_bump(apr_getopt_t *os, void *baton, apr_pool_t *pool) +{ + struct svnadmin_opt_state *opt_state = baton; + svn_repos_t *repos; + + /* Expect no more arguments. */ + SVN_ERR(parse_args(NULL, os, 0, 0, pool)); + + SVN_ERR(open_repos(&repos, opt_state->repository_path, pool)); + + /* Do the commit. */ + { + svn_fs_t *fs = svn_repos_fs(repos); + svn_fs_txn_t *txn; + svn_revnum_t youngest; + apr_hash_t *revprop_table = apr_hash_make(pool); + const svn_delta_editor_t *editor; + void *edit_baton; + void *root_baton; + svn_error_t *err; + + SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool)); + /* ### What should SVN_PROP_REVISION_AUTHOR be set to? */ + /* ### Invent and set an SVN_PROP_REVISION_EMPTY revprop. */ + /* Note that we don't override SVN_PROP_REVISION_DATE. */ + SVN_ERR(svn_repos_fs_begin_txn_for_commit2(&txn, repos, youngest, + revprop_table, pool)); + SVN_ERR(svn_repos_get_commit_editor5(&editor, &edit_baton, repos, txn, + "" /* don't check copyfrom */, + "" /* ignored */, + revprop_table, + bump_commit_callback, NULL, + NULL, NULL, /* no authz */ + pool)); + + err = editor->open_root(edit_baton, youngest, pool, &root_baton); + if (!err) + err = editor->close_edit(edit_baton, pool); + if (err) + return svn_error_compose_create( + editor->abort_edit(edit_baton, pool), + err); + } + + return SVN_NO_ERROR; +} + /* This implements `svn_opt_subcommand_t'. */ static svn_error_t * subcommand_crashtest(apr_getopt_t *os, void *baton, apr_pool_t *pool) diff --git a/subversion/tests/cmdline/svnadmin_tests.py b/subversion/tests/cmdline/svnadmin_tests.py index 5d97058..bee3111 100755 --- a/subversion/tests/cmdline/svnadmin_tests.py +++ b/subversion/tests/cmdline/svnadmin_tests.py @@ -2941,6 +2941,19 @@ def upgrade(sbox): '-m', svntest.main.make_log_msg(), sbox.repo_url + '/dir') +@Issues(4521) +def bump(sbox): + "svnadmin bump" + + sbox.build(create_wc=False) + expected_output = ["Committed empty revision r2.\n"] + svntest.actions.run_and_verify_svnadmin(None, expected_output, [], + 'bump', sbox.repo_dir) + + # Verify that the changed-paths list is empty. + expected_output = [] + svntest.actions.run_and_verify_svnlook(None, expected_output, [], + "changed", sbox.repo_dir) ######################################################################## # Run the tests @@ -2995,6 +3008,7 @@ test_list = [ None, fsfs_hotcopy_progress_old, freeze_same_uuid, upgrade, + bump, ] if __name__ == '__main__': -- 1.7.10.4