diff --git a/Makefile b/Makefile index 5a2cb0c9..b5b783fe 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,6 @@ release: hatchling build twine upload dist/testtools-$(shell hatchling version)-* gpg -a --detach-sign dist/testtools-$(shell hatchling version).tar.gz - $(PYTHON) scripts/_lp_release.py snapshot: prerelease hatchling build diff --git a/NEWS b/NEWS index ea77c412..a7352d37 100644 --- a/NEWS +++ b/NEWS @@ -3,6 +3,14 @@ testtools NEWS Changes and improvements to testtools_, grouped by release. +2.8.2 +~~~~~ + +Changes +------- + +Fully migrate away from Launchpad to GitHub. + 2.8.1 ~~~~~ diff --git a/README.rst b/README.rst index 2895048a..76685242 100644 --- a/README.rst +++ b/README.rst @@ -51,24 +51,17 @@ If you would like to use our Twisted support, then you will need the ``testtools[twisted]`` extra. If you want to use ``fixtures`` then you can either install fixtures (e.g. from -https://launchpad.net/python-fixtures or https://pypi.python.org/pypi/fixtures) +https://github.com/testing-cabalfixtures or https://pypi.python.org/pypi/fixtures) or alternatively just make sure your fixture objects obey the same protocol. Bug reports and patches ----------------------- -Please report bugs using Launchpad at . +Please report bugs using GitHub at . Patches should be submitted as GitHub pull requests, or mailed to the authors. See ``doc/hacking.rst`` for more details. -There's no mailing list for this project yet, however the testing-in-python -mailing list may be a useful resource: - -* Address: testing-in-python@lists.idyll.org -* Subscription link: http://lists.idyll.org/listinfo/testing-in-python - - History ------- diff --git a/disperse.conf b/disperse.conf index b2da1e2e..8c81a6f8 100644 --- a/disperse.conf +++ b/disperse.conf @@ -1,4 +1,3 @@ # See https://github.com/jelmer/disperse timeout_days: 5 tag_name: "$VERSION" -launchpad_project: "testtools" diff --git a/doc/for-test-authors.rst b/doc/for-test-authors.rst index b081fb1c..403b388e 100644 --- a/doc/for-test-authors.rst +++ b/doc/for-test-authors.rst @@ -1422,7 +1422,7 @@ The solution is to use ``Nullary`` instead:: Here, ``repr(nullary)`` will be the same as ``repr(f)``. -.. _testrepository: https://launchpad.net/testrepository +.. _testrepository: https://github.com/testing-cabal/testrepository .. _Trial: http://twistedmatrix.com/documents/current/core/howto/testing.html .. _nose: http://somethingaboutorange.com/mrl/projects/nose/ .. _zope.testrunner: http://pypi.python.org/pypi/zope.testrunner/ diff --git a/doc/hacking.rst b/doc/hacking.rst index a40ba704..c407d63e 100644 --- a/doc/hacking.rst +++ b/doc/hacking.rst @@ -5,7 +5,7 @@ Contributing to testtools Bugs and patches ---------------- -`File bugs ` on Launchpad, and +`File bugs ` on GitHub, and `send patches ` on GitHub. @@ -80,12 +80,6 @@ good idea. For crasher bugs, this is generally a no-brainer, but for UI bugs & API tweaks, the reason something is an improvement might not be obvious, so it's worth spelling out. -If you are thinking of implementing a new feature, you might want to have that -discussion on the [mailing list](testtools-dev@lists.launchpad.net) before the -patch goes up for review. This is not at all mandatory, but getting feedback -early can help avoid dead ends. - - Documentation ------------- @@ -178,10 +172,6 @@ Membership in the testing-cabal org on GitHub as committer. Membership in the pypi testtools project as maintainer. -Membership in the https://launchpad.net/~testtools-committers. - -No in-progress Critical bugs on the next_ milestone. - Tasks +++++ @@ -190,14 +180,7 @@ Tasks #. Possibly write a blurb into NEWS. #. Commit the changes. #. Tag the release, ``git tag -s X.Y.Z -m "Releasing X.Y.Z"`` -#. Run 'make release', this: - #. Creates a source distribution and uploads to PyPI - #. Ensures all Fix Committed bugs are in the release milestone - #. Makes a release on Launchpad and uploads the tarball - #. Marks all the Fix Committed bugs as Fix Released - #. Creates a new milestone -#. If a new series has been created (e.g. 0.10.0), make the series on Launchpad. -#. Push trunk to GitHub, ``git push --tags origin master`` +#. Push master to GitHub, ``git push --tags origin master`` .. _PEP 8: http://www.python.org/dev/peps/pep-0008/ .. _unittest: http://docs.python.org/library/unittest.html @@ -205,4 +188,3 @@ Tasks .. _Sphinx: http://sphinx.pocoo.org/ .. _restructuredtext: http://docutils.sourceforge.net/rst.html .. _testing-cabal: https://github.com/organizations/testing-cabal/ -.. _next: https://launchpad.net/testtools/+milestone/next diff --git a/scripts/_lp_release.py b/scripts/_lp_release.py deleted file mode 100644 index ef15baef..00000000 --- a/scripts/_lp_release.py +++ /dev/null @@ -1,235 +0,0 @@ -#!/usr/bin/python - -"""Release testtools on Launchpad. - -Steps: - 1. Make sure all "Fix committed" bugs are assigned to 'next' - 2. Rename 'next' to the new version - 3. Release the milestone - 4. Upload the tarball - 5. Create a new 'next' milestone - 6. Mark all "Fix committed" bugs in the milestone as "Fix released" - -Assumes that NEWS is in the parent directory, that the release sections are -underlined with '~' and the subsections are underlined with '-'. - -Assumes that this file is in the 'scripts' directory a testtools tree that has -already had a tarball built and uploaded with 'python setup.py sdist upload ---sign'. -""" - -import logging -import os -import sys -from datetime import datetime, timedelta, tzinfo - -from launchpadlib import uris -from launchpadlib.launchpad import Launchpad - -APP_NAME = "testtools-lp-release" -CACHE_DIR = os.path.expanduser("~/.launchpadlib/cache") -SERVICE_ROOT = uris.LPNET_SERVICE_ROOT - -FIX_COMMITTED = "Fix Committed" -FIX_RELEASED = "Fix Released" - -# Launchpad file type for a tarball upload. -CODE_RELEASE_TARBALL = "Code Release Tarball" - -PROJECT_NAME = "testtools" -NEXT_MILESTONE_NAME = "next" - - -class _UTC(tzinfo): - """UTC""" - - def utcoffset(self, dt): - return timedelta(0) - - def tzname(self, dt): - return "UTC" - - def dst(self, dt): - return timedelta(0) - - -UTC = _UTC() - - -def configure_logging(): - level = logging.INFO - log = logging.getLogger(APP_NAME) - log.setLevel(level) - handler = logging.StreamHandler() - handler.setLevel(level) - formatter = logging.Formatter("%(levelname)s: %(message)s") - handler.setFormatter(formatter) - log.addHandler(handler) - return log - - -LOG = configure_logging() - - -def get_path(relpath): - """Get the absolute path for something relative to this file.""" - return os.path.abspath( - os.path.join(os.path.dirname(os.path.dirname(__file__)), relpath) - ) - - -def assign_fix_committed_to_next(testtools, next_milestone): - """Find all 'Fix Committed' and make sure they are in 'next'.""" - fixed_bugs = list(testtools.searchTasks(status=FIX_COMMITTED)) - for task in fixed_bugs: - LOG.debug(f"{task.title}") - if task.milestone != next_milestone: - task.milestone = next_milestone - LOG.info(f"Re-assigning {task.title}") - task.lp_save() - - -def rename_milestone(next_milestone, new_name): - """Rename 'next_milestone' to 'new_name'.""" - LOG.info(f"Renaming {next_milestone.name} to {new_name}") - next_milestone.name = new_name - next_milestone.lp_save() - - -def get_release_notes_and_changelog(news_path): - release_notes = [] - changelog = [] - state = None - last_line = None - - def is_heading_marker(line, marker_char): - return line and line == marker_char * len(line) - - LOG.debug(f"Loading NEWS from {news_path}") - with open(news_path) as news: - for line in news: - line = line.strip() - if state is None: - if is_heading_marker(line, "~") and not last_line.startswith("NEXT"): - milestone_name = last_line - state = "release-notes" - else: - last_line = line - elif state == "title": - # The line after the title is a heading marker line, so we - # ignore it and change state. That which follows are the - # release notes. - state = "release-notes" - elif state == "release-notes": - if is_heading_marker(line, "-"): - state = "changelog" - # Last line in the release notes is actually the first - # line of the changelog. - changelog = [release_notes.pop(), line] - else: - release_notes.append(line) - elif state == "changelog": - if is_heading_marker(line, "~"): - # Last line in changelog is actually the first line of the - # next section. - changelog.pop() - break - else: - changelog.append(line) - else: - raise ValueError("Couldn't parse NEWS") - - release_notes = "\n".join(release_notes).strip() + "\n" - changelog = "\n".join(changelog).strip() + "\n" - return milestone_name, release_notes, changelog - - -def release_milestone(milestone, release_notes, changelog): - date_released = datetime.now(tz=UTC) - LOG.info(f"Releasing milestone: {milestone.name}, date {date_released}") - release = milestone.createProductRelease( - date_released=date_released, - changelog=changelog, - release_notes=release_notes, - ) - milestone.is_active = False - milestone.lp_save() - return release - - -def create_milestone(series, name): - """Create a new milestone in the same series as 'release_milestone'.""" - LOG.info(f"Creating milestone {name} in series {series.name}") - return series.newMilestone(name=name) - - -def close_fixed_bugs(milestone): - tasks = list(milestone.searchTasks()) - for task in tasks: - LOG.debug(f"Found {task.title}") - if task.status == FIX_COMMITTED: - LOG.info(f"Closing {task.title}") - task.status = FIX_RELEASED - else: - LOG.warning(f"Bug not fixed, removing from milestone: {task.title}") - task.milestone = None - task.lp_save() - - -def upload_tarball(release, tarball_path): - with open(tarball_path) as tarball: - tarball_content = tarball.read() - sig_path = tarball_path + ".asc" - with open(sig_path) as sig: - sig_content = sig.read() - tarball_name = os.path.basename(tarball_path) - LOG.info(f"Uploading tarball: {tarball_path}") - release.add_file( - file_type=CODE_RELEASE_TARBALL, - file_content=tarball_content, - filename=tarball_name, - signature_content=sig_content, - signature_filename=sig_path, - content_type="application/x-gzip; charset=binary", - ) - - -def release_project(launchpad, project_name, next_milestone_name): - testtools = launchpad.projects[project_name] - next_milestone = testtools.getMilestone(name=next_milestone_name) - release_name, release_notes, changelog = get_release_notes_and_changelog( - get_path("NEWS") - ) - LOG.info(f"Releasing {project_name} {release_name}") - # Since reversing these operations is hard, and inspecting errors from - # Launchpad is also difficult, do some looking before leaping. - errors = [] - tarball_path = get_path(f"dist/{project_name}-{release_name}.tar.gz") - if not os.path.isfile(tarball_path): - errors.append(f"{tarball_path} does not exist") - if not os.path.isfile(tarball_path + ".asc"): - errors.append("{} does not exist".format(tarball_path + ".asc")) - if testtools.getMilestone(name=release_name): - errors.append(f"Milestone {release_name} exists on {project_name}") - if errors: - for error in errors: - LOG.error(error) - return 1 - assign_fix_committed_to_next(testtools, next_milestone) - rename_milestone(next_milestone, release_name) - release = release_milestone(next_milestone, release_notes, changelog) - upload_tarball(release, tarball_path) - create_milestone(next_milestone.series_target, next_milestone_name) - close_fixed_bugs(next_milestone) - return 0 - - -def main(args): - launchpad = Launchpad.login_with( - APP_NAME, SERVICE_ROOT, CACHE_DIR, credentials_file=".lp_creds" - ) - return release_project(launchpad, PROJECT_NAME, NEXT_MILESTONE_NAME) - - -if __name__ == "__main__": - sys.exit(main(sys.argv))