Repository: twisted/twisted

Issue:
Update towncrier
We use on old version of towncrier with a custom script to detect release branch.

The latest towncrier should have support to checking a release branch.

We should update to towncrier and remove our custom script.

Any good stuff used in twisted/twisted custom towncrier script should be moved upstream.


Discussion:

Resolving patch:
diff --git a/src/twisted/python/_release.py b/src/twisted/python/_release.py
--- a/src/twisted/python/_release.py
+++ b/src/twisted/python/_release.py
@@ -13,7 +13,6 @@
 """
 
 import os
-import sys
 from subprocess import STDOUT, CalledProcessError, check_output
 from typing import Dict
 
@@ -21,9 +20,6 @@
 
 from twisted.python.compat import execfile
 
-# Types of newsfragments.
-NEWSFRAGMENT_TYPES = ["doc", "bugfix", "misc", "feature", "removal"]
-
 
 def runCommand(args, **kwargs):
     """Execute a vector of arguments.
@@ -283,98 +279,3 @@ class NotWorkingDirectory(Exception):
     Raised when a directory does not appear to be a repository directory of a
     supported VCS.
     """
-
-
-class CheckNewsfragmentScript:
-    """
-    A thing for checking whether a checkout has a newsfragment.
-    """
-
-    def __init__(self, _print):
-        self._print = _print
-
-    def main(self, args):
-        """
-        Run the script.
-
-        @type args: L{list} of L{str}
-        @param args: The command line arguments to process. This must contain
-            one string: the path to the root of the Twisted checkout.
-        """
-        if len(args) != 1:
-            sys.exit("Must specify one argument: the Twisted checkout")
-
-        encoding = sys.stdout.encoding or "ascii"
-        location = os.path.abspath(args[0])
-
-        branch = (
-            runCommand([b"git", b"rev-parse", b"--abbrev-ref", "HEAD"], cwd=location)
-            .decode(encoding)
-            .strip()
-        )
-
-        # diff-filter=d to exclude deleted newsfiles (which will happen on the
-        # release branch)
-        r = (
-            runCommand(
-                [
-                    b"git",
-                    b"diff",
-                    b"--name-only",
-                    b"origin/trunk...",
-                    b"--diff-filter=d",
-                ],
-                cwd=location,
-            )
-            .decode(encoding)
-            .strip()
-        )
-
-        if not r:
-            self._print("On trunk or no diffs from trunk; no need to look at this.")
-            sys.exit(0)
-
-        files = r.strip().split(os.linesep)
-
-        self._print("Looking at these files:")
-        for change in files:
-            self._print(change)
-        self._print("----")
-
-        if len(files) == 1:
-            if files[0] == os.sep.join(["docs", "fun", "Twisted.Quotes"]):
-                self._print("Quotes change only; no newsfragment needed.")
-                sys.exit(0)
-
-        newsfragments = []
-
-        for change in files:
-            if os.sep + "newsfragments" + os.sep in change:
-                if "." in change and change.rsplit(".", 1)[1] in NEWSFRAGMENT_TYPES:
-                    newsfragments.append(change)
-
-        if branch.startswith("release-"):
-            if newsfragments:
-                self._print("No newsfragments should be on the release branch.")
-                sys.exit(1)
-            else:
-                self._print("Release branch with no newsfragments, all good.")
-                sys.exit(0)
-
-        if os.environ.get("GITHUB_HEAD_REF", "") == "pre-commit-ci-update-config":
-            # The run was triggered by pre-commit.ci.
-            if newsfragments:
-                self._print(
-                    "No newsfragments should be present on an autoupdated branch."
-                )
-                sys.exit(1)
-            else:
-                self._print("Autoupdated branch with no newsfragments, all good.")
-                sys.exit(0)
-
-        for change in newsfragments:
-            self._print("Found " + change)
-            sys.exit(0)
-
-        self._print("No newsfragment found. Have you committed it?")
-        sys.exit(1)


Test patch:
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -274,9 +274,13 @@ jobs:
     steps:
     - uses: actions/checkout@v3
       with:
-        # Need history on all branches for newsfragment to work.
+        # Need full history for various diff checks to work.
         fetch-depth: 0
 
+    - name: Get main branch for any diff checks
+      run: |
+        git fetch origin trunk:trunk
+
     - name: Set up Python
       uses: actions/setup-python@v4
       with:
@@ -289,20 +293,7 @@ jobs:
     - name: Run the checks
       run: |
         python --version
-        tox --parallel -e lint,mypy
-
-    - name: Revert merge commit for a release branch
-      if: github.event_name == 'pull_request' && startsWith(github.head_ref, 'release-')
-      env:
-        BRANCH_SHA: ${{ github.event.after }}
-        BRANCH_NAME: ${{ github.head_ref }}
-      run: |
-        git checkout -b $BRANCH_NAME
-        git reset --hard $BRANCH_SHA
-
-    - name: Newsfragment
-      run: |
-        tox -q -e newsfragment
+        tox --parallel -e lint,mypy,newsfragment
 
 
   # Used to validate the narrative and API docs.
diff --git a/src/twisted/python/test/test_release.py b/src/twisted/python/test/test_release.py
--- a/src/twisted/python/test/test_release.py
+++ b/src/twisted/python/test/test_release.py
@@ -18,7 +18,6 @@
 
 from twisted.python import release
 from twisted.python._release import (
-    CheckNewsfragmentScript,
     GitCommand,
     IVCSCommand,
     NotWorkingDirectory,
@@ -502,296 +501,3 @@ def test_git(self):
         L{GitCommand} implements L{IVCSCommand}.
         """
         self.assertTrue(IVCSCommand.implementedBy(GitCommand))
-
-
-class CheckNewsfragmentScriptTests(ExternalTempdirTestCase):
-    """
-    L{CheckNewsfragmentScript}.
-    """
-
-    def setUp(self):
-        self.origin = FilePath(self.mktemp())
-        _gitInit(self.origin)
-        runCommand(["git", "checkout", "-b", "trunk"], cwd=self.origin.path)
-        self.origin.child("test").setContent(b"test!")
-        runCommand(["git", "add", self.origin.child("test").path], cwd=self.origin.path)
-        runCommand(["git", "commit", "-m", "initial"], cwd=self.origin.path)
-
-        self.repo = FilePath(self.mktemp())
-
-        runCommand(["git", "clone", self.origin.path, self.repo.path])
-        _gitConfig(self.repo)
-        # Inject a rigged environment to have full control over the test execution.
-        self.patch(os, "environ", GENERIC_CI_ENVIRON)
-
-    def test_noArgs(self):
-        """
-        Too few arguments returns a failure.
-        """
-        logs = []
-
-        with self.assertRaises(SystemExit) as e:
-            CheckNewsfragmentScript(logs.append).main([])
-
-        self.assertEqual(
-            e.exception.args, ("Must specify one argument: the Twisted checkout",)
-        )
-
-    def test_diffFromTrunkNoNewsfragments(self):
-        """
-        If there are changes from trunk, then there should also be a
-        newsfragment.
-        """
-        runCommand(["git", "checkout", "-b", "mypatch"], cwd=self.repo.path)
-        somefile = self.repo.child("somefile")
-        somefile.setContent(b"change")
-
-        runCommand(["git", "add", somefile.path, somefile.path], cwd=self.repo.path)
-        runCommand(["git", "commit", "-m", "some file"], cwd=self.repo.path)
-
-        logs = []
-
-        with self.assertRaises(SystemExit) as e:
-            CheckNewsfragmentScript(logs.append).main([self.repo.path])
-
-        self.assertEqual(e.exception.args, (1,))
-        self.assertEqual(logs[-1], "No newsfragment found. Have you committed it?")
-
-    def test_noChangeFromTrunk(self):
-        """
-        If there are no changes from trunk, then no need to check the
-        newsfragments
-        """
-        runCommand(["git", "checkout", "-b", "mypatch"], cwd=self.repo.path)
-
-        logs = []
-
-        with self.assertRaises(SystemExit) as e:
-            CheckNewsfragmentScript(logs.append).main([self.repo.path])
-
-        self.assertEqual(e.exception.args, (0,))
-        self.assertEqual(
-            logs[-1], "On trunk or no diffs from trunk; no need to look at this."
-        )
-
-    def test_trunk(self):
-        """
-        Running it on trunk always gives green.
-        """
-        logs = []
-
-        with self.assertRaises(SystemExit) as e:
-            CheckNewsfragmentScript(logs.append).main([self.repo.path])
-
-        self.assertEqual(e.exception.args, (0,))
-        self.assertEqual(
-            logs[-1], "On trunk or no diffs from trunk; no need to look at this."
-        )
-
-    def test_release(self):
-        """
-        Running it on a release branch returns green if there is no
-        newsfragments even if there are changes.
-        """
-        runCommand(
-            ["git", "checkout", "-b", "release-16.11111-9001"], cwd=self.repo.path
-        )
-
-        somefile = self.repo.child("somefile")
-        somefile.setContent(b"change")
-
-        runCommand(["git", "add", somefile.path, somefile.path], cwd=self.repo.path)
-        runCommand(["git", "commit", "-m", "some file"], cwd=self.repo.path)
-
-        logs = []
-
-        with self.assertRaises(SystemExit) as e:
-            CheckNewsfragmentScript(logs.append).main([self.repo.path])
-
-        self.assertEqual(e.exception.args, (0,))
-        self.assertEqual(logs[-1], "Release branch with no newsfragments, all good.")
-
-    def test_releaseWithNewsfragments(self):
-        """
-        Running it on a release branch returns red if there are new
-        newsfragments.
-        """
-        runCommand(
-            ["git", "checkout", "-b", "release-16.11111-9001"], cwd=self.repo.path
-        )
-
-        newsfragments = self.repo.child("twisted").child("newsfragments")
-        newsfragments.makedirs()
-        fragment = newsfragments.child("1234.misc")
-        fragment.setContent(b"")
-
-        unrelated = self.repo.child("somefile")
-        unrelated.setContent(b"Boo")
-
-        runCommand(["git", "add", fragment.path, unrelated.path], cwd=self.repo.path)
-        runCommand(["git", "commit", "-m", "fragment"], cwd=self.repo.path)
-
-        logs = []
-
-        with self.assertRaises(SystemExit) as e:
-            CheckNewsfragmentScript(logs.append).main([self.repo.path])
-
-        self.assertEqual(e.exception.args, (1,))
-        self.assertEqual(logs[-1], "No newsfragments should be on the release branch.")
-
-    def test_preCommitAutoupdate(self):
-        """
-        Running it on the autoupdate branch returns green if there is no
-        newsfragments even if there are changes.
-        """
-        runCommand(
-            ["git", "checkout", "-b", "pre-commit-ci-update-config"], cwd=self.repo.path
-        )
-
-        somefile = self.repo.child("somefile")
-        somefile.setContent(b"change")
-
-        runCommand(["git", "add", somefile.path, somefile.path], cwd=self.repo.path)
-        runCommand(["git", "commit", "-m", "some file"], cwd=self.repo.path)
-
-        logs = []
-        self.patch(os, "environ", PRECOMMIT_CI_ENVIRON)
-
-        with self.assertRaises(SystemExit) as e:
-            CheckNewsfragmentScript(logs.append).main([self.repo.path])
-
-        self.assertEqual(e.exception.args, (0,))
-        self.assertEqual(
-            logs[-1], "Autoupdated branch with no newsfragments, all good."
-        )
-
-    def test_preCommitAutoupdateWithNewsfragments(self):
-        """
-        Running it on the autoupdate branch returns red if there are new
-        newsfragments.
-        """
-        runCommand(
-            ["git", "checkout", "-b", "pre-commit-ci-update-config"], cwd=self.repo.path
-        )
-
-        newsfragments = self.repo.child("twisted").child("newsfragments")
-        newsfragments.makedirs()
-        fragment = newsfragments.child("1234.misc")
-        fragment.setContent(b"")
-
-        unrelated = self.repo.child("somefile")
-        unrelated.setContent(b"Boo")
-
-        runCommand(["git", "add", fragment.path, unrelated.path], cwd=self.repo.path)
-        runCommand(["git", "commit", "-m", "fragment"], cwd=self.repo.path)
-
-        logs = []
-        self.patch(os, "environ", PRECOMMIT_CI_ENVIRON)
-
-        with self.assertRaises(SystemExit) as e:
-            CheckNewsfragmentScript(logs.append).main([self.repo.path])
-
-        self.assertEqual(e.exception.args, (1,))
-        self.assertEqual(
-            logs[-1], "No newsfragments should be present on an autoupdated branch."
-        )
-
-    def test_onlyQuotes(self):
-        """
-        Running it on a branch with only a quotefile change gives green.
-        """
-        runCommand(["git", "checkout", "-b", "quotefile"], cwd=self.repo.path)
-
-        fun = self.repo.child("docs").child("fun")
-        fun.makedirs()
-        quotes = fun.child("Twisted.Quotes")
-        quotes.setContent(b"Beep boop")
-
-        runCommand(["git", "add", quotes.path], cwd=self.repo.path)
-        runCommand(["git", "commit", "-m", "quotes"], cwd=self.repo.path)
-
-        logs = []
-
-        with self.assertRaises(SystemExit) as e:
-            CheckNewsfragmentScript(logs.append).main([self.repo.path])
-
-        self.assertEqual(e.exception.args, (0,))
-        self.assertEqual(logs[-1], "Quotes change only; no newsfragment needed.")
-
-    def test_newsfragmentAdded(self):
-        """
-        Running it on a branch with a fragment in the newsfragments dir added
-        returns green.
-        """
-        runCommand(["git", "checkout", "-b", "quotefile"], cwd=self.repo.path)
-
-        newsfragments = self.repo.child("twisted").child("newsfragments")
-        newsfragments.makedirs()
-        fragment = newsfragments.child("1234.misc")
-        fragment.setContent(b"")
-
-        unrelated = self.repo.child("somefile")
-        unrelated.setContent(b"Boo")
-
-        runCommand(["git", "add", fragment.path, unrelated.path], cwd=self.repo.path)
-        runCommand(["git", "commit", "-m", "newsfragment"], cwd=self.repo.path)
-
-        logs = []
-
-        with self.assertRaises(SystemExit) as e:
-            CheckNewsfragmentScript(logs.append).main([self.repo.path])
-
-        self.assertEqual(e.exception.args, (0,))
-        self.assertEqual(logs[-1], "Found twisted/newsfragments/1234.misc")
-
-    def test_topfileButNotFragmentAdded(self):
-        """
-        Running it on a branch with a non-fragment in the topfiles dir does not
-        return green.
-        """
-        runCommand(["git", "checkout", "-b", "quotefile"], cwd=self.repo.path)
-
-        topfiles = self.repo.child("twisted").child("topfiles")
-        topfiles.makedirs()
-        notFragment = topfiles.child("1234.txt")
-        notFragment.setContent(b"")
-
-        unrelated = self.repo.child("somefile")
-        unrelated.setContent(b"Boo")
-
-        runCommand(["git", "add", notFragment.path, unrelated.path], cwd=self.repo.path)
-        runCommand(["git", "commit", "-m", "not topfile"], cwd=self.repo.path)
-
-        logs = []
-
-        with self.assertRaises(SystemExit) as e:
-            CheckNewsfragmentScript(logs.append).main([self.repo.path])
-
-        self.assertEqual(e.exception.args, (1,))
-        self.assertEqual(logs[-1], "No newsfragment found. Have you committed it?")
-
-    def test_newsfragmentAddedButWithOtherNewsfragments(self):
-        """
-        Running it on a branch with a fragment in the topfiles dir added
-        returns green, even if there are other files in the topfiles dir.
-        """
-        runCommand(["git", "checkout", "-b", "quotefile"], cwd=self.repo.path)
-
-        newsfragments = self.repo.child("twisted").child("newsfragments")
-        newsfragments.makedirs()
-        fragment = newsfragments.child("1234.misc")
-        fragment.setContent(b"")
-
-        unrelated = newsfragments.child("somefile")
-        unrelated.setContent(b"Boo")
-
-        runCommand(["git", "add", fragment.path, unrelated.path], cwd=self.repo.path)
-        runCommand(["git", "commit", "-m", "newsfragment"], cwd=self.repo.path)
-
-        logs = []
-
-        with self.assertRaises(SystemExit) as e:
-            CheckNewsfragmentScript(logs.append).main([self.repo.path])
-
-        self.assertEqual(e.exception.args, (0,))
-        self.assertEqual(logs[-1], "Found twisted/newsfragments/1234.misc")