# HG changeset patch # User Nikita Slyusarev # Date 1610399476 -10800 # Node ID 7525e77b5eac36f88b655a6067fe1969af733003 # Parent 1d6d1a15a96344a505645991794333f26761ca79 convert: option to set date and time for svn commits Converting to subversion repository is not preserving original commit dates as it may break some subversion functionality if commit dates are not monotonically increasing. This patch adds `convert.svn.dangerous-set-commit-dates` configuration option to change this behaviour and enable commit dates convertion for those who want to take risks. Subversion always uses commit dates with UTC timezone, so only timestamps are used. Test `test-convert-svn-sink.t` uses `svnxml.py` script to dump history of svn repositories. Atm the script is not printing `date` field from svn log. This patch changes this to allow checks on correctness of date and time convertion. Documentation is updated. Additional test case is added to test commit dates convertion. Differential Revision: https://phab.mercurial-scm.org/D9721 diff -r 1d6d1a15a963 -r 7525e77b5eac hgext/convert/__init__.py --- a/hgext/convert/__init__.py Fri Jan 15 23:58:41 2021 +0100 +++ b/hgext/convert/__init__.py Tue Jan 12 00:11:16 2021 +0300 @@ -491,6 +491,22 @@ :convert.skiptags: does not convert tags from the source repo to the target repo. The default is False. + + Subversion Destination + ###################### + + Original commit dates are not preserved by default. + + :convert.svn.dangerous-set-commit-dates: preserve original commit dates, + forcefully setting ``svn:date`` revision properties. This option is + DANGEROUS and may break some subversion functionality for the resulting + repository (e.g. filtering revisions with date ranges in ``svn log``), + as original commit dates are not guaranteed to be monotonically + increasing. + + For commit dates setting to work destination repository must have + ``pre-revprop-change`` hook configured to allow setting of ``svn:date`` + revision properties. See Subversion documentation for more details. """ return convcmd.convert(ui, src, dest, revmapfile, **opts) diff -r 1d6d1a15a963 -r 7525e77b5eac hgext/convert/subversion.py --- a/hgext/convert/subversion.py Fri Jan 15 23:58:41 2021 +0100 +++ b/hgext/convert/subversion.py Tue Jan 12 00:11:16 2021 +0300 @@ -97,6 +97,17 @@ return s.decode(fsencoding).encode('utf-8') +def formatsvndate(date): + return dateutil.datestr(date, b'%Y-%m-%dT%H:%M:%S.000000Z') + + +def parsesvndate(s): + # Example SVN datetime. Includes microseconds. + # ISO-8601 conformant + # '2007-01-04T17:35:00.902377Z' + return dateutil.parsedate(s[:19] + b' UTC', [b'%Y-%m-%dT%H:%M:%S']) + + class SvnPathNotFound(Exception): pass @@ -1158,12 +1169,7 @@ continue paths.append((path, ent)) - # Example SVN datetime. Includes microseconds. - # ISO-8601 conformant - # '2007-01-04T17:35:00.902377Z' - date = dateutil.parsedate( - date[:19] + b" UTC", [b"%Y-%m-%dT%H:%M:%S"] - ) + date = parsesvndate(date) if self.ui.configbool(b'convert', b'localtimezone'): date = makedatetimestamp(date[0]) @@ -1380,7 +1386,7 @@ return logstream(stdout) -pre_revprop_change = b'''#!/bin/sh +pre_revprop_change_template = b'''#!/bin/sh REPOS="$1" REV="$2" @@ -1388,15 +1394,26 @@ PROPNAME="$4" ACTION="$5" -if [ "$ACTION" = "M" -a "$PROPNAME" = "svn:log" ]; then exit 0; fi -if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-branch" ]; then exit 0; fi -if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-rev" ]; then exit 0; fi +%(rules)s echo "Changing prohibited revision property" >&2 exit 1 ''' +def gen_pre_revprop_change_hook(prop_actions_allowed): + rules = [] + for action, propname in prop_actions_allowed: + rules.append( + ( + b'if [ "$ACTION" = "%s" -a "$PROPNAME" = "%s" ]; ' + b'then exit 0; fi' + ) + % (action, propname) + ) + return pre_revprop_change_template % {b'rules': b'\n'.join(rules)} + + class svn_sink(converter_sink, commandline): commit_re = re.compile(br'Committed revision (\d+).', re.M) uuid_re = re.compile(br'Repository UUID:\s*(\S+)', re.M) @@ -1470,9 +1487,20 @@ self.is_exec = None if created: + prop_actions_allowed = [ + (b'M', b'svn:log'), + (b'A', b'hg:convert-branch'), + (b'A', b'hg:convert-rev'), + ] + + if self.ui.configbool( + b'convert', b'svn.dangerous-set-commit-dates' + ): + prop_actions_allowed.append((b'M', b'svn:date')) + hook = os.path.join(created, b'hooks', b'pre-revprop-change') fp = open(hook, b'wb') - fp.write(pre_revprop_change) + fp.write(gen_pre_revprop_change_hook(prop_actions_allowed)) fp.close() util.setflags(hook, False, True) @@ -1667,6 +1695,23 @@ revprop=True, revision=rev, ) + + if self.ui.configbool( + b'convert', b'svn.dangerous-set-commit-dates' + ): + # Subverson always uses UTC to represent date and time + date = dateutil.parsedate(commit.date) + date = (date[0], 0) + + # The only way to set date and time for svn commit is to use propset after commit is done + self.run( + b'propset', + b'svn:date', + formatsvndate(date), + revprop=True, + revision=rev, + ) + for parent in parents: self.addchild(parent, rev) return self.revid(rev) diff -r 1d6d1a15a963 -r 7525e77b5eac mercurial/configitems.py --- a/mercurial/configitems.py Fri Jan 15 23:58:41 2021 +0100 +++ b/mercurial/configitems.py Tue Jan 12 00:11:16 2021 +0300 @@ -570,6 +570,11 @@ default=0, ) coreconfigitem( + b'convert', + b'svn.dangerous-set-commit-dates', + default=False, +) +coreconfigitem( b'debug', b'dirstate.delaywrite', default=0, diff -r 1d6d1a15a963 -r 7525e77b5eac tests/svnxml.py --- a/tests/svnxml.py Fri Jan 15 23:58:41 2021 +0100 +++ b/tests/svnxml.py Tue Jan 12 00:11:16 2021 +0300 @@ -15,6 +15,7 @@ e['revision'] = entry.getAttribute('revision') e['author'] = xmltext(entry.getElementsByTagName('author')[0]) e['msg'] = xmltext(entry.getElementsByTagName('msg')[0]) + e['date'] = xmltext(entry.getElementsByTagName('date')[0]) e['paths'] = [] paths = entry.getElementsByTagName('paths') if paths: @@ -42,7 +43,7 @@ except AttributeError: fp = sys.stdout for e in entries: - for k in ('revision', 'author', 'msg'): + for k in ('revision', 'author', 'date', 'msg'): fp.write(('%s: %s\n' % (k, e[k])).encode('utf-8')) for path, action, fpath, frev in sorted(e['paths']): frominfo = b'' diff -r 1d6d1a15a963 -r 7525e77b5eac tests/test-convert-svn-sink.t --- a/tests/test-convert-svn-sink.t Fri Jan 15 23:58:41 2021 +0100 +++ b/tests/test-convert-svn-sink.t Tue Jan 12 00:11:16 2021 +0300 @@ -54,10 +54,12 @@ 2 2 test a revision: 2 author: test + date: * (glob) msg: modify a file M /a revision: 1 author: test + date: * (glob) msg: add a file A /a A /d1 @@ -95,6 +97,7 @@ 3 3 test b revision: 3 author: test + date: * (glob) msg: rename a file D /a A /b (from /a@2) @@ -131,6 +134,7 @@ 4 4 test c revision: 4 author: test + date: * (glob) msg: copy a file A /c (from /b@3) $ ls a a-hg-wc @@ -167,6 +171,7 @@ 5 5 test . revision: 5 author: test + date: * (glob) msg: remove a file D /b $ ls a a-hg-wc @@ -209,6 +214,7 @@ 6 6 test c revision: 6 author: test + date: * (glob) msg: make a file executable M /c #if execbit @@ -247,6 +253,7 @@ 8 8 test newlink revision: 8 author: test + date: * (glob) msg: move symlink D /link A /newlink (from /link@7) @@ -278,6 +285,7 @@ 7 7 test f revision: 7 author: test + date: * (glob) msg: f D /c A /d @@ -315,6 +323,7 @@ 1 1 test d1/a revision: 1 author: test + date: * (glob) msg: add executable file in new directory A /d1 A /d1/a @@ -343,6 +352,7 @@ 2 2 test d2/a revision: 2 author: test + date: * (glob) msg: copy file to new directory A /d2 A /d2/a (from /d1/a@1) @@ -416,21 +426,25 @@ 4 4 test right-2 revision: 4 author: test + date: * (glob) msg: merge A /right-1 A /right-2 revision: 3 author: test + date: * (glob) msg: left-2 M /b A /left-2 revision: 2 author: test + date: * (glob) msg: left-1 M /b A /left-1 revision: 1 author: test + date: * (glob) msg: base A /b @@ -459,10 +473,12 @@ 2 2 test .hgtags revision: 2 author: test + date: * (glob) msg: Tagged as v1.0 A /.hgtags revision: 1 author: test + date: * (glob) msg: Add file a A /a $ rm -rf a a-hg a-hg-wc @@ -494,10 +510,12 @@ 2 2 test exec revision: 2 author: test + date: * (glob) msg: remove executable bit M /exec revision: 1 author: test + date: * (glob) msg: create executable A /exec $ test ! -x a-hg-wc/exec @@ -540,11 +558,77 @@ 2 2 test b revision: 2 author: test + date: * (glob) msg: Another change A /b revision: 1 author: test + date: * (glob) msg: Some change A /a $ rm -rf a a-hg a-hg-wc + +Commit dates convertion + + $ hg init a + + $ echo a >> a/a + $ hg add a + adding a/a + $ hg --cwd a ci -d '1 0' -A -m 'Change 1' + + $ echo a >> a/a + $ hg --cwd a ci -d '2 0' -m 'Change 2' + + $ echo a >> a/a + $ hg --cwd a ci -d '2 0' -m 'Change at the same time' + + $ echo a >> a/a + $ hg --cwd a ci -d '1 0' -m 'Change in the past' + + $ echo a >> a/a + $ hg --cwd a ci -d '3 0' -m 'Change in the future' + + $ hg convert --config convert.svn.dangerous-set-commit-dates=true -d svn a + assuming destination a-hg + initializing svn repository 'a-hg' + initializing svn working copy 'a-hg-wc' + scanning source... + sorting... + converting... + 4 Change 1 + 3 Change 2 + 2 Change at the same time + 1 Change in the past + 0 Change in the future + $ svnupanddisplay a-hg-wc 0 + 5 5 test . + 5 5 test a + revision: 5 + author: test + date: 1970-01-01T00:00:03.000000Z + msg: Change in the future + M /a + revision: 4 + author: test + date: 1970-01-01T00:00:01.000000Z + msg: Change in the past + M /a + revision: 3 + author: test + date: 1970-01-01T00:00:02.000000Z + msg: Change at the same time + M /a + revision: 2 + author: test + date: 1970-01-01T00:00:02.000000Z + msg: Change 2 + M /a + revision: 1 + author: test + date: 1970-01-01T00:00:01.000000Z + msg: Change 1 + A /a + + $ rm -rf a a-hg a-hg-wc diff -r 1d6d1a15a963 -r 7525e77b5eac tests/test-convert.t --- a/tests/test-convert.t Fri Jan 15 23:58:41 2021 +0100 +++ b/tests/test-convert.t Tue Jan 12 00:11:16 2021 +0300 @@ -388,6 +388,23 @@ does not convert tags from the source repo to the target repo. The default is False. + Subversion Destination + ###################### + + Original commit dates are not preserved by default. + + convert.svn.dangerous-set-commit-dates + preserve original commit dates, forcefully setting + "svn:date" revision properties. This option is DANGEROUS and + may break some subversion functionality for the resulting + repository (e.g. filtering revisions with date ranges in + "svn log"), as original commit dates are not guaranteed to + be monotonically increasing. + + For commit dates setting to work destination repository must have "pre- + revprop-change" hook configured to allow setting of "svn:date" revision + properties. See Subversion documentation for more details. + options ([+] can be repeated): -s --source-type TYPE source repository type