[PATCH] hg: add settings to make repositories non-publishing or use changeset evolution

Manuel Jacob me at manueljacob.de
Thu Apr 20 12:55:02 UTC 2023


# HG changeset patch
# User Manuel Jacob <me at manueljacob.de>
# Date 1681995256 -7200
#      Thu Apr 20 14:54:16 2023 +0200
# Node ID 56aad162afcff89ec7e0b8cc7a60fff2bbde6ec1
# Parent  294f5a6814edc267c87a77dad5131c08a50fae1c
# EXP-Topic non_publishing_changeset_evolution
hg: add settings to make repositories non-publishing or use changeset evolution

Setting repositories non-publishing and enabling changeset evolution makes it
possible to refine changesets collaboratively. Doing both at the same time will
therefore probably be the most common use case.

There are some users that push to-be-reviewed changesets to Kallithea
repositories, but don’t need or want obsolescence information to be shared.
Those users can now enable use of phases by setting the repository
non-publishing if they didn’t already do that by modifying the repository’s
hgrc.

Having a publishing repository with changeset evolution can also be useful. For
example, there could be a project that has the committed changesets in a
publishing repository and uses email-based patch contributions. When a
maintainer commits a patch, possibly after some modifications, Mercurial has
the possibility to automatically add and share obsolescence information stating
that the committed changeset is a successor of the contributor’s original local
changeset that the patch was created from.

Previously, there were no settings in the web interface that modify
per-repository Mercurial configs and no per-repository settings that apply to
hg repositories but not git repositories. Therefore, when adding these
settings, there was no pattern to follow and a lot of different design and
implementation choices could have been taken.

diff --git a/kallithea/controllers/api/api.py b/kallithea/controllers/api/api.py
--- a/kallithea/controllers/api/api.py
+++ b/kallithea/controllers/api/api.py
@@ -928,6 +928,8 @@
                     landing_rev='rev:tip',
                     enable_statistics=None,
                     enable_downloads=None,
+                    publishing=None,
+                    enable_changeset_evolution=None,
                     copy_permissions=False):
         """
         Creates a repository. The repository name contains the full path, but the
@@ -982,6 +984,10 @@
             enable_statistics = defs.get('repo_enable_statistics')
         if enable_downloads is None:
             enable_downloads = defs.get('repo_enable_downloads')
+        if publishing is None:
+            publishing = defs.get('repo_publishing')
+        if enable_changeset_evolution is None:
+            enable_changeset_evolution = defs.get('repo_enable_changeset_evolution')
 
         try:
             data = dict(
@@ -995,6 +1001,8 @@
                 repo_landing_rev=landing_rev,
                 repo_enable_statistics=enable_statistics,
                 repo_enable_downloads=enable_downloads,
+                repo_publishing = publishing,
+                repo_enable_changeset_evolution=enable_changeset_evolution,
                 repo_copy_permissions=copy_permissions,
             )
 
@@ -1017,7 +1025,9 @@
                     description=None, private=None,
                     clone_uri=None, landing_rev=None,
                     enable_statistics=None,
-                    enable_downloads=None):
+                    enable_downloads=None,
+                    publishing=None,
+                    enable_changeset_evolution=None):
         """
         Updates repo
         """
@@ -1055,6 +1065,8 @@
             store_update(updates, landing_rev, 'repo_landing_rev')
             store_update(updates, enable_statistics, 'repo_enable_statistics')
             store_update(updates, enable_downloads, 'repo_enable_downloads')
+            store_update(updates, publishing, 'repo_publishing')
+            store_update(updates, enable_changeset_evolution, 'repo_enable_changeset_evolution')
 
             RepoModel().update(repo, **updates)
             meta.Session().commit()
diff --git a/kallithea/lib/db_manage.py b/kallithea/lib/db_manage.py
--- a/kallithea/lib/db_manage.py
+++ b/kallithea/lib/db_manage.py
@@ -178,6 +178,8 @@
         for k, v, t in [
             ('default_repo_enable_downloads', False, 'bool'),
             ('default_repo_enable_statistics', False, 'bool'),
+            ('default_repo_publishing', True, 'bool'),
+            ('default_repo_enable_changeset_evolution', False, 'bool'),
             ('default_repo_private', False, 'bool'),
             ('default_repo_type', 'hg', 'unicode')
         ]:
diff --git a/kallithea/lib/utils.py b/kallithea/lib/utils.py
--- a/kallithea/lib/utils.py
+++ b/kallithea/lib/utils.py
@@ -287,6 +287,12 @@
         baseui.setconfig(b'hooks', ascii_bytes(db.Ui.HOOK_UPDATE), b'python:kallithea.bin.vcs_hooks.update')
 
     if repo_path is not None:
+        db_repo = db.Repository.get_by_full_path(repo_path)
+        if db_repo is not None:
+            baseui.setconfig(b'phases', b'publish', db_repo.publishing)
+            if db_repo.enable_changeset_evolution:
+                baseui.setconfig(b'experimental', b'evolution', b'all')
+
         # Note: MercurialRepository / mercurial.localrepo.instance will do this too, so it will always be possible to override db settings or what is hardcoded above
         baseui.readconfig(safe_bytes(os.path.join(repo_path, '.hg', 'hgrc')))
 
@@ -403,6 +409,8 @@
     defs = db.Setting.get_default_repo_settings(strip_prefix=True)
     enable_statistics = defs.get('repo_enable_statistics')
     enable_downloads = defs.get('repo_enable_downloads')
+    publishing = defs.get('repo_publishing')
+    enable_changeset_evolution = defs.get('repo_enable_changeset_evolution')
     private = defs.get('repo_private')
 
     for name, repo in sorted(initial_repo_dict.items()):
@@ -425,6 +433,8 @@
                     owner=user,
                     enable_downloads=enable_downloads,
                     enable_statistics=enable_statistics,
+                    publishing=publishing,
+                    enable_changeset_evolution=enable_changeset_evolution,
                     private=private,
                     state=db.Repository.STATE_CREATED
                 )
diff --git a/kallithea/model/db.py b/kallithea/model/db.py
--- a/kallithea/model/db.py
+++ b/kallithea/model/db.py
@@ -922,6 +922,8 @@
     private = Column(Boolean(), nullable=False)
     enable_statistics = Column("statistics", Boolean(), nullable=False, default=True)
     enable_downloads = Column("downloads", Boolean(), nullable=False, default=True)
+    publishing = Column("publishing", Boolean(), nullable=False, default=True)
+    enable_changeset_evolution = Column("changeset_evolution", Boolean(), nullable=False, default=False)
     description = Column(Unicode(10000), nullable=False)
     created_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
     updated_on = Column(DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
diff --git a/kallithea/model/forms.py b/kallithea/model/forms.py
--- a/kallithea/model/forms.py
+++ b/kallithea/model/forms.py
@@ -264,6 +264,8 @@
 
         repo_enable_statistics = v.StringBoolean(if_missing=False)
         repo_enable_downloads = v.StringBoolean(if_missing=False)
+        repo_publishing = v.StringBoolean(if_missing=False)
+        repo_enable_changeset_evolution = v.StringBoolean(if_missing=False)
 
         if edit:
             owner = All(v.UnicodeString(not_empty=True), v.ValidRepoUser())
@@ -440,6 +442,8 @@
         default_repo_private = v.StringBoolean(if_missing=False)
         default_repo_enable_statistics = v.StringBoolean(if_missing=False)
         default_repo_enable_downloads = v.StringBoolean(if_missing=False)
+        default_repo_publishing = v.StringBoolean(if_missing=False)
+        default_repo_enable_changeset_evolution = v.StringBoolean(if_missing=False)
 
     return _DefaultsForm
 
diff --git a/kallithea/model/repo.py b/kallithea/model/repo.py
--- a/kallithea/model/repo.py
+++ b/kallithea/model/repo.py
@@ -217,7 +217,9 @@
         for strip, k in [(0, 'repo_type'), (1, 'repo_enable_downloads'),
                          (1, 'repo_description'),
                          (1, 'repo_landing_rev'), (0, 'clone_uri'),
-                         (1, 'repo_private'), (1, 'repo_enable_statistics')]:
+                         (1, 'repo_private'), (1, 'repo_enable_statistics'),
+                         (1, 'repo_publishing'),
+                         (1, 'repo_enable_changeset_evolution')]:
             attr = k
             if strip:
                 attr = remove_prefix(k, 'repo_')
@@ -266,6 +268,8 @@
                       'repo_landing_rev',
                       'repo_private',
                       'repo_enable_statistics',
+                      'repo_publishing',
+                      'repo_enable_changeset_evolution',
                       ]:
                 if k in kwargs:
                     setattr(cur_repo, remove_prefix(k, 'repo_'), kwargs[k])
@@ -309,7 +313,8 @@
                      private=False, clone_uri=None, repo_group=None,
                      landing_rev='rev:tip', fork_of=None,
                      copy_fork_permissions=False, enable_statistics=False,
-                     enable_downloads=False,
+                     enable_downloads=False, publishing=True,
+                     enable_changeset_evolution=False,
                      copy_group_permissions=False, state=db.Repository.STATE_PENDING):
         """
         Create repository inside database with PENDING state. This should only be
@@ -345,6 +350,8 @@
 
             new_repo.enable_statistics = enable_statistics
             new_repo.enable_downloads = enable_downloads
+            new_repo.publishing = publishing
+            new_repo.enable_changeset_evolution = enable_changeset_evolution
 
             if fork_of:
                 parent_repo = fork_of
@@ -713,6 +720,8 @@
     fork_of = form_data.get('fork_parent_id')
     enable_statistics = form_data['repo_enable_statistics']
     enable_downloads = form_data['repo_enable_downloads']
+    publishing = form_data['repo_publishing']
+    enable_changeset_evolution = form_data['repo_enable_changeset_evolution']
     state = form_data.get('repo_state', db.Repository.STATE_PENDING)
 
     try:
@@ -730,6 +739,8 @@
             copy_group_permissions=copy_group_permissions,
             enable_statistics=enable_statistics,
             enable_downloads=enable_downloads,
+            publishing=publishing,
+            enable_changeset_evolution=enable_changeset_evolution,
             state=state
         )
 
diff --git a/kallithea/templates/admin/defaults/defaults.html b/kallithea/templates/admin/defaults/defaults.html
--- a/kallithea/templates/admin/defaults/defaults.html
+++ b/kallithea/templates/admin/defaults/defaults.html
@@ -53,6 +53,20 @@
                     <span class="help-block">${_('Enable download menu on summary page.')}</span>
                 </div>
             </div>
+            <div class="form-group">
+                <label class="control-label" for="default_repo_publishing">${_('Publishing repository (Mercurial-specific)')}:</label>
+                <div>
+                    ${h.checkbox('default_repo_publishing',value="True")}
+                    <span class="help-block">${_('Set phase of changesets pushed to server to public.')}</span>
+                </div>
+            </div>
+            <div class="form-group">
+                <label class="control-label" for="default_repo_enable_changeset_evolution">${_('Enable changeset evolution (Mercurial-specific)')}:</label>
+                <div>
+                    ${h.checkbox('default_repo_enable_changeset_evolution',value="True")}
+                    <span class="help-block">${_('Enable all changeset evolution features.')}</span>
+                </div>
+            </div>
 
             <div class="form-group">
                 <div class="buttons">
diff --git a/kallithea/templates/admin/repos/repo_add_base.html b/kallithea/templates/admin/repos/repo_add_base.html
--- a/kallithea/templates/admin/repos/repo_add_base.html
+++ b/kallithea/templates/admin/repos/repo_add_base.html
@@ -71,6 +71,22 @@
                 <span class="help-block">${_('Enable download menu on summary page.')}</span>
             </div>
         </div>
+        <div id="hg_specific_settings">
+            <div class="form-group">
+                <label class="control-label" for="repo_publishing">${_('Publishing repository')}:</label>
+                <div>
+                    ${h.checkbox('repo_publishing',value="True")}
+                    <span class="help-block">${_('Set phase of changesets pushed to server to public.')}</span>
+                </div>
+            </div>
+            <div class="form-group">
+                <label class="control-label" for="repo_enable_changeset_evolution">${_('Enable changeset evolution')}:</label>
+                <div>
+                    ${h.checkbox('repo_enable_changeset_evolution',value="True")}
+                    <span class="help-block">${_('Enable all changeset evolution features.')}</span>
+                </div>
+            </div>
+        </div>
         <div class="form-group">
             <div class="buttons">
                 ${h.submit('add',_('Add'),class_="btn btn-default")}
@@ -103,6 +119,20 @@
             setCopyPermsOption(e.val);
         });
 
+        function setVcsSpecificSettings(repo_type){
+            if(repo_type == "hg"){
+                $('#hg_specific_settings').show();
+            }
+            else{
+                $('#hg_specific_settings').hide();
+            }
+        }
+
+        setVcsSpecificSettings($('#repo_type').val());
+        $('#repo_type').on("change", function(e) {
+            setVcsSpecificSettings(e.val);
+        });
+
         $('#repo_landing_rev').select2({
             'minimumResultsForSearch': -1
         });
diff --git a/kallithea/templates/admin/repos/repo_edit_settings.html b/kallithea/templates/admin/repos/repo_edit_settings.html
--- a/kallithea/templates/admin/repos/repo_edit_settings.html
+++ b/kallithea/templates/admin/repos/repo_edit_settings.html
@@ -76,6 +76,22 @@
                 <span class="help-block">${_('Enable download menu on summary page.')}</span>
             </div>
         </div>
+        %if c.repo_info.repo_type == 'hg':
+          <div class="form-group">
+              <label class="control-label" for="repo_publishing">${_('Publishing repository')}:</label>
+              <div>
+                  ${h.checkbox('repo_publishing',value="True")}
+                  <span class="help-block">${_('Set phase of changesets pushed to server to public.')}</span>
+              </div>
+          </div>
+          <div class="form-group">
+              <label class="control-label" for="repo_enable_changeset_evolution">${_('Enable changeset evolution')}:</label>
+              <div>
+                  ${h.checkbox('repo_enable_changeset_evolution',value="True")}
+                  <span class="help-block">${_('Enable all changeset evolution features.')}</span>
+              </div>
+          </div>
+        %endif
 
         %if c.visual.repository_fields:
           ## EXTRA FIELDS
diff --git a/kallithea/tests/conftest.py b/kallithea/tests/conftest.py
--- a/kallithea/tests/conftest.py
+++ b/kallithea/tests/conftest.py
@@ -75,11 +75,12 @@
     if not int(os.environ.get('KALLITHEA_NO_TMP_PATH', 0)):
         create_test_env(TESTS_TMP_PATH, context.config(), reuse_database=bool(reuse_database))
 
+    kallithea.tests.base.testapp = context.create()
+
     # set KALLITHEA_WHOOSH_TEST_DISABLE=1 to disable whoosh index during tests
     if not int(os.environ.get('KALLITHEA_WHOOSH_TEST_DISABLE', 0)):
         create_test_index(TESTS_TMP_PATH, context.config(), True)
 
-    kallithea.tests.base.testapp = context.create()
     # do initial repo scan
     repo2db_mapper(ScmModel().repo_scan(TESTS_TMP_PATH))
 
diff --git a/kallithea/tests/fixture.py b/kallithea/tests/fixture.py
--- a/kallithea/tests/fixture.py
+++ b/kallithea/tests/fixture.py
@@ -97,6 +97,8 @@
             repo_private=False,
             repo_enable_statistics=False,
             repo_enable_downloads=False,
+            repo_publishing=True,
+            repo_enable_changeset_evolution=False,
             repo_landing_rev='rev:tip',
             repo_copy_permissions=False,
             repo_state=db.Repository.STATE_CREATED,
diff --git a/kallithea/tests/functional/test_admin_defaults.py b/kallithea/tests/functional/test_admin_defaults.py
--- a/kallithea/tests/functional/test_admin_defaults.py
+++ b/kallithea/tests/functional/test_admin_defaults.py
@@ -10,12 +10,16 @@
         response.mustcontain('default_repo_private')
         response.mustcontain('default_repo_enable_statistics')
         response.mustcontain('default_repo_enable_downloads')
+        response.mustcontain('default_repo_publishing')
+        response.mustcontain('default_repo_enable_changeset_evolution')
 
     def test_update_params_true_hg(self):
         self.log_user()
         params = {
             'default_repo_enable_downloads': True,
             'default_repo_enable_statistics': True,
+            'default_repo_publishing': True,
+            'default_repo_enable_changeset_evolution': True,
             'default_repo_private': True,
             'default_repo_type': 'hg',
             '_session_csrf_secret_token': self.session_csrf_secret_token(),
@@ -32,6 +36,8 @@
         params = {
             'default_repo_enable_downloads': False,
             'default_repo_enable_statistics': False,
+            'default_repo_publishing': False,
+            'default_repo_enable_changeset_evolution': False,
             'default_repo_private': False,
             'default_repo_type': 'git',
             '_session_csrf_secret_token': self.session_csrf_secret_token(),
diff --git a/kallithea/tests/other/test_vcs_operations.py b/kallithea/tests/other/test_vcs_operations.py
--- a/kallithea/tests/other/test_vcs_operations.py
+++ b/kallithea/tests/other/test_vcs_operations.py
@@ -329,7 +329,9 @@
             "method": 'create_repo',
             "args": dict(repo_name=repo_name,
                          owner=base.TEST_USER_ADMIN_LOGIN,
-                         repo_type=vt.repo_type),
+                         repo_type=vt.repo_type,
+                         publishing=True,
+                         enable_changeset_evolution=False),
         }
         req = urllib.request.Request(
             'http://%s:%s/_admin/api' % webserver.server_address,
@@ -356,6 +358,8 @@
         elif vt.repo_type == 'hg':
             assert 'pushing to ' in stdout
             assert 'remote: added ' in stdout
+            stdout, stderr = Command(local_clone_dir).execute('hg', 'phase')
+            assert stdout == '2: public\n'
 
         # Verify that we got the right events in UserLog. Expect something like:
         # <UserLog('id:new_git_XXX:started_following_repo')>
@@ -375,6 +379,47 @@
             # (u'pull', 0), # Mercurial outgoing hook is not called for empty clones
             ('push', 3)])
 
+    @parametrize_vcs_test_hg
+    def test_push_new_repo_non_publishing_changeset_evolution(self, webserver, vt):
+        # Create an empty server repo using the API
+        repo_name = 'new_%s_%s' % (vt.repo_type, next(_RandomNameSequence()))
+        usr = db.User.get_by_username(base.TEST_USER_ADMIN_LOGIN)
+        params = {
+            "id": 7,
+            "api_key": usr.api_key,
+            "method": 'create_repo',
+            "args": dict(repo_name=repo_name,
+                         owner=base.TEST_USER_ADMIN_LOGIN,
+                         repo_type=vt.repo_type,
+                         publishing=False,
+                         enable_changeset_evolution=True),
+        }
+        req = urllib.request.Request(
+            'http://%s:%s/_admin/api' % webserver.server_address,
+            data=ascii_bytes(json.dumps(params)),
+            headers={'content-type': 'application/json'})
+        response = urllib.request.urlopen(req)
+        result = json.loads(response.read())
+        assert result['result']['success']
+
+        # Create local clone of the empty server repo
+        local_clone_dir = _get_tmp_dir()
+        clone_url = vt.repo_url_param(webserver, repo_name)
+        stdout, stderr = Command(base.TESTS_TMP_PATH).execute(vt.repo_type, 'clone', clone_url, local_clone_dir)
+
+        stdout, stderr = _add_files_and_push(webserver, vt, local_clone_dir, clone_url=clone_url, files_no=1)
+        stdout, stderr = Command(local_clone_dir).execute('hg', 'phase')
+        assert stdout == '0: draft\n'
+
+        with open(os.path.join(local_clone_dir, '.hg', 'hgrc'), 'a') as hgrc:
+            hgrc.write('[experimental]\n')
+            hgrc.write('evolution=all\n')
+        stdout, stderr = _commit('hg', local_clone_dir, 'amend', '--amend')
+        stdout, stderr = Command(local_clone_dir).execute('hg', 'push', '--force', clone_url)
+        assert 'remote: obsoleted 1 changesets' in stdout
+        stdout, stderr = Command(local_clone_dir).execute('hg', 'phase')
+        assert stdout == '1: draft\n'
+
     @parametrize_vcs_test
     def test_push_new_file(self, webserver, testfork, vt):
         db.UserLog.query().delete()


More information about the kallithea-general mailing list