diff --git a/heptapod/hooks/perm.py b/heptapod/hooks/perm.py
index d325833962a6d9d9460d6cb4ddb9ddff69b190c7_aGVwdGFwb2QvaG9va3MvcGVybS5weQ==..cac20dfe000d1c5af3221a9e83b3db8c7adf3150_aGVwdGFwb2QvaG9va3MvcGVybS5weQ== 100644
--- a/heptapod/hooks/perm.py
+++ b/heptapod/hooks/perm.py
@@ -7,6 +7,7 @@
 from mercurial import (
     configitems,
     error,
+    pycompat,
     scmutil,
     phases,
 )
@@ -115,6 +116,29 @@
     return 1
 
 
+def extract_published_revs(tr):
+    """Extract revision numbers of revision published by given transaction.
+
+    :returns: iterable of revision numbers, without duplicates.
+    """
+    phaseschanges = tr.changes.get("phases", ())
+    try:
+        # phases changes is a dict in hg < 5.4
+        phaseschanges = pycompat.iteritems(phaseschanges)
+        revs_range = False
+    except AttributeError:
+        # phaseschanges is a list of pairs in hg >= 5.4
+        revs_range = True
+
+    publishing = set(rev for rev, (old, new) in phaseschanges
+                     if new == phases.public and old != phases.public)
+
+    # TODO drop this once we are on hg >= 5.4 only
+    if revs_range:
+        publishing = set(rev for rev_range in publishing for rev in rev_range)
+    return publishing
+
+
 def check_publish(repo, *args, **kwargs):
     validate_hook_type('pretxnclose', **kwargs)
     remoteuser = get_remote_user(repo)
@@ -129,9 +153,7 @@
 
     tr = repo.currenttransaction()
     assert tr is not None and tr.running()
-    phaseschanges = tr.changes.get("phases", {})
-    publishing = set(rev for rev, (old, new) in phaseschanges.iteritems()
-                     if new == phases.public and old != phases.public)
+    publishing = extract_published_revs(tr)
     if publishing:
         node = repo.changelog.node
         nodes = [node(r) for r in sorted(publishing)]
diff --git a/heptapod/hooks/tests/test_check_publish.py b/heptapod/hooks/tests/test_check_publish.py
index d325833962a6d9d9460d6cb4ddb9ddff69b190c7_aGVwdGFwb2QvaG9va3MvdGVzdHMvdGVzdF9jaGVja19wdWJsaXNoLnB5..cac20dfe000d1c5af3221a9e83b3db8c7adf3150_aGVwdGFwb2QvaG9va3MvdGVzdHMvdGVzdF9jaGVja19wdWJsaXNoLnB5 100644
--- a/heptapod/hooks/tests/test_check_publish.py
+++ b/heptapod/hooks/tests/test_check_publish.py
@@ -9,9 +9,10 @@
 from mercurial import (
     error,
     phases,
+    pycompat,
 )
 from heptapod.testhelpers import (
     LocalRepoWrapper,
     )
 
 from .utils import switch_user
@@ -12,9 +13,10 @@
 )
 from heptapod.testhelpers import (
     LocalRepoWrapper,
     )
 
 from .utils import switch_user
+from ..perm import extract_published_revs
 
 
 def init_repo(basedir):
@@ -60,3 +62,25 @@
         wrapper.write_commit('foo')
 
     assert 'precommit' in exc_info.value.args[0]
+
+
+class FakeTransaction(object):
+    def __init__(self, phases):
+        self.changes = dict(phases=phases)
+
+
+def test_extract_published_revs_hg_5_3():
+    txn = FakeTransaction({3: (phases.draft, phases.public),
+                           19: (phases.secret, phases.public),
+                           7: (phases.secret, phases.draft),
+                           })
+    assert set(extract_published_revs(txn)) == {3, 19}
+
+
+def test_extract_published_revs_hg_5_4():
+    txn = FakeTransaction([
+        (pycompat.xrange(3, 5), (phases.draft, phases.public)),
+        (pycompat.xrange(2), (phases.secret, phases.public)),
+        (pycompat.xrange(7, 8), (phases.secret, phases.draft)),
+    ])
+    assert set(extract_published_revs(txn)) == {0, 1, 3, 4}