changeset 6496:fba501baf5cb

topic: find and report topic namespace changes in transactions
author Anton Shestakov <av6@dwimlabs.net>
date Mon, 05 Jun 2023 21:57:13 -0300
parents 499e4f1c83a8
children 4e0f760d332e
files hgext3rd/topic/__init__.py tests/test-namespaces-report.t
diffstat 2 files changed, 245 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/hgext3rd/topic/__init__.py	Wed Jun 07 19:57:24 2023 -0300
+++ b/hgext3rd/topic/__init__.py	Mon Jun 05 21:57:13 2023 -0300
@@ -187,6 +187,7 @@
     pycompat,
     registrar,
     scmutil,
+    smartset,
     templatefilters,
     util,
 )
@@ -269,6 +270,9 @@
 configitem(b'_internal', b'tns-publish',
            default=False,
 )
+configitem(b'devel', b'tns-report-transactions',
+           default=lambda: [],
+)
 configitem(b'experimental', b'topic-mode.server',
            default=configitems.dynamicdefault,
 )
@@ -430,6 +434,53 @@
         or (missing and self.deleted())
     )
 
+def find_affected_tns(repo, tr):
+    origrepolen = tr.changes[b'origrepolen']
+    unfi = repo.unfiltered()
+
+    affected = set()
+    # These are the new changesets that weren't in the repo before this
+    # transaction
+    for rev in smartset.spanset(repo, start=origrepolen):
+        ctx = unfi[rev]
+        tns = ctx.topic_namespace()
+        affected.add(tns)
+
+    # These are the changesets obsoleted by this transaction
+    for rev in obsutil.getobsoleted(repo, tr):
+        ctx = unfi[rev]
+        tns = ctx.topic_namespace()
+        affected.add(tns)
+
+    # Phase movements, we only care about changesets that move from or to
+    # public phase
+    for revs, (old, new) in tr.changes[b'phases']:
+        if old != phases.public and new != phases.public:
+            # new phase is public: publishing changesets
+            # old phase is public: e.g. hg phase --draft --force
+            continue
+        revs = [rev for rev in revs if rev < origrepolen]
+        for rev in revs:
+            ctx = unfi[rev]
+            tns = ctx.topic_namespace(force=True)
+            affected.add(tns)
+
+    # We don't care about changesets without topic namespace
+    affected.discard(b'none')
+
+    tr.changes[b'tns'] = affected
+    report_affected_tns(repo, tr)
+
+def report_affected_tns(repo, tr):
+    report = set(repo.ui.configlist(b'devel', b'tns-report-transactions'))
+    if b'*' not in report:
+        # * matches any transaction
+        if not any(trname in report for trname in tr._names):
+            return
+
+    if tr.changes[b'tns']:
+        repo.ui.status(b'topic namespaces affected: %s\n' % b' '.join(sorted(tr.changes[b'tns'])))
+
 def uisetup(ui):
     destination.modsetup(ui)
     discovery.modsetup(ui)
@@ -742,6 +793,25 @@
                 else:
                     tr.addvalidator(b'000-reject-publish', _validate)
 
+            if util.safehasattr(tr, '_validator'):
+                # hg <= 5.3 (36f08ae87ef6)
+                origvalidator = tr._validator
+
+            def _validate(tr2):
+                repo = reporef()
+                find_affected_tns(repo, tr2)
+
+            def validator(tr2):
+                result = origvalidator(tr2)
+                _validate(tr2)
+                return result
+
+            if util.safehasattr(tr, '_validator'):
+                # hg <= 5.3 (36f08ae87ef6)
+                tr._validator = validator
+            else:
+                tr.addvalidator(b'000-find-affected-tns', _validate)
+
             # real transaction start
             ct = self.currenttopic
             if not ct:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-namespaces-report.t	Mon Jun 05 21:57:13 2023 -0300
@@ -0,0 +1,175 @@
+Reporting affected topic namespaces in transactions
+
+  $ . "$TESTDIR/testlib/common.sh"
+
+  $ cat >> $HGRCPATH << EOF
+  > [extensions]
+  > evolve =
+  > topic =
+  > [phases]
+  > publish = no
+  > [devel]
+  > tns-report-transactions = push
+  > EOF
+
+  $ hg init orig
+
+case 1: new changeset (draft with topic namespace)
+topic namespace of that changeset is reported
+
+  $ hg clone orig case-1 -q
+  $ cd orig
+
+  $ echo apple > a
+  $ hg ci -qAm apple
+
+  $ hg push ../case-1
+  pushing to ../case-1
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+
+  $ echo banana > b
+  $ hg debug-topic-namespace bob
+  marked working directory as topic namespace: bob
+  $ hg ci -qAm 'banana'
+
+XXX: should not require --new-branch
+
+  $ hg push ../case-1 --new-branch
+  pushing to ../case-1
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  topic namespaces affected: bob
+  added 1 changesets with 1 changes to 1 files
+
+  $ cd ..
+
+case 2: obsmarker affecting known changeset
+topic namespaces of both the precursor and the successor are affected
+
+  $ hg clone orig case-2 -q
+  $ cd orig
+
+  $ echo broccoli > b
+  $ hg debug-topic-namespace bruce
+  $ hg ci --amend -m 'broccoli'
+
+XXX: should not require --new-branch
+
+  $ hg push ../case-2 --new-branch
+  pushing to ../case-2
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  topic namespaces affected: bob bruce
+  added 1 changesets with 1 changes to 1 files (+1 heads)
+  1 new obsolescence markers
+  obsoleted 1 changesets
+
+  $ cd ..
+
+3 phase divergence resolution can point to a thing but not affect it (probably not affected)
+
+  $ hg clone orig case-3 -q
+  $ cd orig
+
+  $ hg debug-topic-namespace charlie
+  $ echo coconut > c
+  $ hg ci -qAm 'coconut'
+
+  $ hg debug-topic-namespace carol
+  $ echo cloudberry > c
+  $ hg ci --amend -m 'cloudberry'
+
+  $ hg phase --hidden -r 'desc("coconut")' --public
+  1 new phase-divergent changesets
+
+  $ hg evolve --phase-divergent
+  recreate:[4] cloudberry
+  atop:[3] coconut
+  committed as c398b3caf447
+  working directory is now at c398b3caf447
+
+XXX: should not require --new-branch
+
+  $ hg push ../case-3 --new-branch
+  pushing to ../case-3
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  topic namespaces affected: bruce carol
+  added 2 changesets with 2 changes to 1 files
+  2 new obsolescence markers
+
+  $ cd ..
+
+4 phase movement: publishing drafts
+topic namespaces of published changesets are affected
+
+  $ hg clone orig case-4 -q
+  $ cd orig
+
+  $ hg push ../case-4 --publish
+  pushing to ../case-4
+  searching for changes
+  no changes found
+  topic namespaces affected: carol
+  [1]
+
+  $ cd ..
+
+  $ cd ..
+
+6 phase movement: publishing secret changesets (that are known on the server)
+topic namespaces of published changesets are affected
+
+  $ hg clone orig case-6 -q
+  $ cd orig
+
+  $ hg push ../case-6 -r . --publish
+  pushing to ../case-6
+  searching for changes
+  no changes found
+  topic namespaces affected: dave
+  [1]
+
+previous topic namespace is resurrected...
+
+  $ hg phase --secret --force -r . --config 'devel.tns-report-transactions=phase'
+  topic namespaces affected: dave
+
+...just to disappear again
+
+  $ hg push ../case-6 -r . --config 'devel.tns-report-transactions=*'
+  pushing to ../case-6
+  searching for changes
+  no changes found
+  topic namespaces affected: dave
+  [1]
+
+  $ cd ..
+
+99 pushing obsmarker for an unknown changeset
+doesn't affect any topic namespace, we report nothing
+
+  $ hg clone orig case-99 -q
+  $ cd orig
+
+  $ hg debugobsolete aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa `getid "desc('dragonfruit')"`
+  1 new obsolescence markers
+
+  $ hg push ../case-99
+  pushing to ../case-99
+  searching for changes
+  no changes found
+  1 new obsolescence markers
+  [1]
+
+  $ cd ..